diff --git a/registry.json b/registry.json index ce1fc2fbba5..87f01e776f9 100644 --- a/registry.json +++ b/registry.json @@ -4180,7 +4180,7 @@ "coinId": 5600, "symbol": "BNB", "decimals": 18, - "chainId": "9000", + "chainId": "1017", "blockchain": "Greenfield", "derivation": [ { diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 8954202beeb..efbc436e0d4 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1750,6 +1750,7 @@ dependencies = [ "tw_cosmos", "tw_ethereum", "tw_evm", + "tw_greenfield", "tw_hash", "tw_internet_computer", "tw_keypair", @@ -1838,6 +1839,24 @@ dependencies = [ "tw_proto", ] +[[package]] +name = "tw_greenfield" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "tw_coin_entry", + "tw_cosmos_sdk", + "tw_encoding", + "tw_evm", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_number", + "tw_proto", +] + [[package]] name = "tw_hash" version = "0.1.0" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 6b80ffb89e6..5896e9dcb5e 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -2,6 +2,7 @@ members = [ "chains/tw_binance", "chains/tw_cosmos", + "chains/tw_greenfield", "chains/tw_native_evmos", "chains/tw_native_injective", "chains/tw_thorchain", diff --git a/rust/chains/tw_greenfield/Cargo.toml b/rust/chains/tw_greenfield/Cargo.toml new file mode 100644 index 00000000000..ab4ee6f224f --- /dev/null +++ b/rust/chains/tw_greenfield/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "tw_greenfield" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_cosmos_sdk = { path = "../../tw_cosmos_sdk" } +tw_encoding = { path = "../../tw_encoding" } +tw_evm = { path = "../../tw_evm" } +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" } +tw_proto = { path = "../../tw_proto" } + +[dev-dependencies] +tw_coin_entry = { path = "../../tw_coin_entry", features = ["test-utils"] } +tw_misc = { path = "../../tw_misc", features = ["test-utils"] } diff --git a/rust/chains/tw_greenfield/fuzz/.gitignore b/rust/chains/tw_greenfield/fuzz/.gitignore new file mode 100644 index 00000000000..5c404b9583f --- /dev/null +++ b/rust/chains/tw_greenfield/fuzz/.gitignore @@ -0,0 +1,5 @@ +target +corpus +artifacts +coverage +Cargo.lock diff --git a/rust/chains/tw_greenfield/fuzz/Cargo.toml b/rust/chains/tw_greenfield/fuzz/Cargo.toml new file mode 100644 index 00000000000..fbc0f5790cc --- /dev/null +++ b/rust/chains/tw_greenfield/fuzz/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "tw_greenfield-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +tw_any_coin = { path = "../../../tw_any_coin", features = ["test-utils"] } +tw_coin_registry = { path = "../../../tw_coin_registry" } +tw_proto = { path = "../../../tw_proto", features = ["fuzz"] } + +[dependencies.tw_greenfield] +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/chains/tw_greenfield/fuzz/fuzz_targets/sign.rs b/rust/chains/tw_greenfield/fuzz/fuzz_targets/sign.rs new file mode 100644 index 00000000000..f27808e5e42 --- /dev/null +++ b/rust/chains/tw_greenfield/fuzz/fuzz_targets/sign.rs @@ -0,0 +1,11 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use tw_any_coin::test_utils::sign_utils::AnySignerHelper; +use tw_coin_registry::coin_type::CoinType; +use tw_proto::Greenfield::Proto; + +fuzz_target!(|input: Proto::SigningInput<'_>| { + let mut signer = AnySignerHelper::::default(); + let _ = signer.sign(CoinType::Greenfield, input); +}); diff --git a/rust/chains/tw_greenfield/src/address.rs b/rust/chains/tw_greenfield/src/address.rs new file mode 100644 index 00000000000..679da7b4e35 --- /dev/null +++ b/rust/chains/tw_greenfield/src/address.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 serde::Serialize; +use std::fmt; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::{AddressError, AddressResult}; +use tw_cosmos_sdk::address::CosmosAddress; +use tw_evm::address::Address as EthereumAddress; +use tw_keypair::ecdsa::secp256k1; +use tw_memory::Data; + +#[derive(Clone, Serialize)] +pub struct GreenfieldAddress(EthereumAddress); + +impl GreenfieldAddress { + /// Initializes an address with a `secp256k1` public key. + pub fn with_secp256k1_pubkey(pubkey: &secp256k1::PublicKey) -> GreenfieldAddress { + GreenfieldAddress(EthereumAddress::with_secp256k1_pubkey(pubkey)) + } +} + +impl CosmosAddress for GreenfieldAddress { + fn from_str_with_coin(_coin: &dyn CoinContext, addr: &str) -> AddressResult + where + Self: Sized, + { + GreenfieldAddress::from_str(addr) + } +} + +impl CoinAddress for GreenfieldAddress { + #[inline] + fn data(&self) -> Data { + self.0.data() + } +} + +impl FromStr for GreenfieldAddress { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + EthereumAddress::from_str(s).map(GreenfieldAddress) + } +} + +impl fmt::Display for GreenfieldAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/rust/chains/tw_greenfield/src/compiler.rs b/rust/chains/tw_greenfield/src/compiler.rs new file mode 100644 index 00000000000..e1133c119d1 --- /dev/null +++ b/rust/chains/tw_greenfield/src/compiler.rs @@ -0,0 +1,111 @@ +// 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::GreenfieldContext; +use crate::modules::eip712_signer::{Eip712Signer, Eip712TxPreimage}; +use crate::modules::tx_builder::TxBuilder; +use crate::public_key::GreenfieldPublicKey; +use crate::signature::GreenfieldSignature; +use std::borrow::Cow; +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_cosmos_sdk::modules::broadcast_msg::{BroadcastMode, BroadcastMsg}; +use tw_cosmos_sdk::modules::serializer::json_serializer::JsonSerializer; +use tw_cosmos_sdk::modules::serializer::protobuf_serializer::ProtobufSerializer; +use tw_cosmos_sdk::public_key::CosmosPublicKey; +use tw_misc::traits::ToBytesVec; +use tw_proto::Greenfield::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct GreenfieldCompiler; + +impl GreenfieldCompiler { + /// 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. + #[inline] + pub fn preimage_hashes( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> CompilerProto::PreSigningOutput<'static> { + Self::preimage_hashes_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(CompilerProto::PreSigningOutput, e)) + } + + fn preimage_hashes_impl( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let unsigned = TxBuilder::unsigned_tx_from_proto(coin, &input)?; + let Eip712TxPreimage { eip712_tx, tx_hash } = Eip712Signer::preimage_hash(&unsigned)?; + + Ok(CompilerProto::PreSigningOutput { + data: Cow::from(eip712_tx.to_vec()), + data_hash: Cow::from(tx_hash.to_vec()), + ..CompilerProto::PreSigningOutput::default() + }) + } + + #[inline] + pub fn compile( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Proto::SigningOutput<'static> { + Self::compile_impl(coin, input, signatures, public_keys) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + pub(crate) fn compile_impl( + coin: &dyn CoinContext, + mut input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> SigningResult> { + let SingleSignaturePubkey { + signature: raw_signature, + public_key, + } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; + + let public_key = GreenfieldPublicKey::from_bytes(coin, &public_key)?; + let signature = GreenfieldSignature::try_from(raw_signature.as_slice())?; + let signature_bytes = signature.to_vec(); + + // Set the public key. It will be used to construct a signer info. + input.public_key = Cow::from(public_key.to_bytes()); + let unsigned = TxBuilder::unsigned_tx_from_proto(coin, &input)?; + + let signed_tx = unsigned.into_signed(signature); + let signed_tx_raw = 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_bytes.clone(), + ); + let signature_json = serde_json::to_string(&[signature_json]) + .map_err(|_| SigningError(SigningErrorType::Error_internal))?; + + Ok(Proto::SigningOutput { + signature: Cow::from(signature_bytes), + signature_json: Cow::from(signature_json), + serialized: Cow::from(broadcast_tx), + ..Proto::SigningOutput::default() + }) + } + + fn broadcast_mode(input: Proto::BroadcastMode) -> BroadcastMode { + match input { + Proto::BroadcastMode::SYNC => BroadcastMode::Sync, + Proto::BroadcastMode::ASYNC => BroadcastMode::Async, + } + } +} diff --git a/rust/chains/tw_greenfield/src/context.rs b/rust/chains/tw_greenfield/src/context.rs new file mode 100644 index 00000000000..5bc6bbfcd72 --- /dev/null +++ b/rust/chains/tw_greenfield/src/context.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::address::GreenfieldAddress; +use crate::public_key::GreenfieldPublicKey; +use tw_cosmos_sdk::context::CosmosContext; +use tw_cosmos_sdk::hasher::keccak256_hasher::Keccak256Hasher; +use tw_cosmos_sdk::private_key::secp256k1::Secp256PrivateKey; +use tw_cosmos_sdk::signature::secp256k1::Secp256k1Signature; + +pub struct GreenfieldContext; + +impl CosmosContext for GreenfieldContext { + type Address = GreenfieldAddress; + /// Greenfield uses EIP712 message signing algorithm built upon `keccak256` hash. + type TxHasher = Keccak256Hasher; + type PrivateKey = Secp256PrivateKey; + type PublicKey = GreenfieldPublicKey; + type Signature = Secp256k1Signature; +} diff --git a/rust/chains/tw_greenfield/src/eip712_types.rs b/rust/chains/tw_greenfield/src/eip712_types.rs new file mode 100644 index 00000000000..6cb4807080f --- /dev/null +++ b/rust/chains/tw_greenfield/src/eip712_types.rs @@ -0,0 +1,321 @@ +// 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::GreenfieldFee; +use core::fmt; +use serde::{Serialize, Serializer}; +use serde_json::Value as Json; +use std::collections::BTreeMap; +use tw_cosmos_sdk::transaction::message::JsonMessage; +use tw_cosmos_sdk::transaction::Coin; +use tw_evm::abi::param_type::constructor::TypeConstructor; +use tw_evm::message::eip712::message_types::MessageTypesBuilder; +use tw_evm::message::eip712::property::PropertyType; +use tw_number::U256; + +const DOMAIN_NAME: &str = "Greenfield Tx"; +const DOMAIN_VERSION: &str = "1.0.0"; +const DOMAIN_VERIFY_CONTRACT: &str = "greenfield"; +const DOMAIN_SALT: &str = "0"; + +#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct MsgPropertyName(pub usize); + +impl fmt::Display for MsgPropertyName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "msg{}", self.0) + } +} + +pub struct MsgPropertyType(pub usize); + +impl fmt::Display for MsgPropertyType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Msg{}", self.0) + } +} + +impl Serialize for MsgPropertyName { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.to_string().serialize(serializer) + } +} + +/// # EIP712 type +/// +/// ```json +/// { +/// "EIP712Domain": [ +/// { +/// "name": "chainId", +/// "type": "uint256" +/// }, +/// { +/// "name": "name", +/// "type": "string" +/// }, +/// { +/// "name": "salt", +/// "type": "string" +/// }, +/// { +/// "name": "verifyingContract", +/// "type": "string" +/// }, +/// { +/// "name": "version", +/// "type": "string" +/// } +/// ] +/// } +/// ``` +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Eip712Domain { + pub name: String, + pub version: String, + #[serde(serialize_with = "U256::as_decimal_str")] + pub chain_id: U256, + pub verifying_contract: String, + pub salt: String, +} + +impl Eip712Domain { + const TYPE_NAME: &'static str = "EIP712Domain"; + + /// Returns a Serializable data of the `EIP712Domain` type. + /// https://github.com/bnb-chain/greenfield-cosmos-sdk/blob/b48770f5e210b28536f92734b6228913666d4da1/x/auth/tx/eip712.go#L35-L40 + pub fn new(chain_id: U256) -> Eip712Domain { + Eip712Domain { + name: DOMAIN_NAME.to_string(), + version: DOMAIN_VERSION.to_string(), + chain_id, + verifying_contract: DOMAIN_VERIFY_CONTRACT.to_string(), + salt: DOMAIN_SALT.to_string(), + } + } + + pub fn declare_eip712_types(&self, builder: &mut MessageTypesBuilder) { + if let Some(mut domain_builder) = builder.add_custom_type(Self::TYPE_NAME.to_string()) { + domain_builder + .add_property("chainId", PropertyType::Uint) + .add_property("name", PropertyType::String) + .add_property("salt", PropertyType::String) + .add_property("verifyingContract", PropertyType::String) + .add_property("version", PropertyType::String); + } + } +} + +/// # EIP712 type +/// +/// ```json +/// { +/// "Coin": [ +/// { +/// "name": "amount", +/// "type": "uint256" +/// }, +/// { +/// "name": "denom", +/// "type": "string" +/// } +/// ] +/// } +/// ``` +pub struct Eip712Coin; + +impl Eip712Coin { + const TYPE_NAME: &'static str = "Coin"; + + pub fn declare_eip712_types(builder: &mut MessageTypesBuilder) { + if let Some(mut coin_builder) = builder.add_custom_type(Self::TYPE_NAME.to_string()) { + coin_builder + .add_property("amount", PropertyType::Uint) + .add_property("denom", PropertyType::String); + } + } +} + +/// # EIP712 type +/// +/// ```json +/// { +/// "Fee": [ +/// { +/// "name": "amount", +/// "type": "Coin[]" +/// }, +/// { +/// "name": "gas_limit", +/// "type": "uint256" +/// }, +/// { +/// "name": "granter", +/// "type": "string" +/// }, +/// { +/// "name": "payer", +/// "type": "string" +/// } +/// ] +/// } +/// ``` +#[derive(Serialize)] +pub struct Eip712Fee { + pub amount: Vec, + #[serde(serialize_with = "U256::as_decimal_str")] + pub gas_limit: U256, + pub payer: String, + pub granter: String, +} + +impl Eip712Fee { + const TYPE_NAME: &'static str = "Fee"; + + pub fn declare_eip712_types(builder: &mut MessageTypesBuilder) { + // `Tx` depends on `Coin` and `Fee` custom types. + Eip712Coin::declare_eip712_types(builder); + + if let Some(mut fee_builder) = builder.add_custom_type(Self::TYPE_NAME.to_string()) { + let amount_type = PropertyType::Custom(Eip712Coin::TYPE_NAME.to_string()); + fee_builder + .add_property("amount", PropertyType::array(amount_type)) + .add_property("gas_limit", PropertyType::Uint) + .add_property("granter", PropertyType::String) + .add_property("payer", PropertyType::String); + } + } +} + +impl From for Eip712Fee { + fn from(fee: GreenfieldFee) -> Self { + let payer = fee + .payer + .as_ref() + .map(|addr| addr.to_string()) + .unwrap_or_default(); + let granter = fee + .granter + .as_ref() + .map(|addr| addr.to_string()) + .unwrap_or_default(); + + Eip712Fee { + amount: fee.amounts.clone(), + gas_limit: U256::from(fee.gas_limit), + payer, + granter, + } + } +} + +#[derive(Clone, Serialize)] +pub struct Eip712TypedMsg { + #[serde(rename = "type")] + pub msg_type: String, + #[serde(flatten)] + pub value: Json, +} + +impl From for Eip712TypedMsg { + fn from(msg: JsonMessage) -> Self { + Eip712TypedMsg { + msg_type: msg.msg_type, + value: msg.value, + } + } +} + +/// # EIP712 type +/// +/// ```json +/// { +/// "Tx": [ +/// { +/// "name": "account_number", +/// "type": "uint256" +/// }, +/// { +/// "name": "chain_id", +/// "type": "uint256" +/// }, +/// { +/// "name": "fee", +/// "type": "Fee" +/// }, +/// { +/// "name": "memo", +/// "type": "string" +/// }, +/// { +/// "name": "msg1", +/// "type": "Msg1" +/// }, +/// { +/// "name": "sequence", +/// "type": "uint256" +/// }, +/// { +/// "name": "timeout_height", +/// "type": "uint256" +/// } +/// ] +/// } +/// ``` +#[derive(Serialize)] +pub struct Eip712Transaction { + #[serde(serialize_with = "U256::as_decimal_str")] + pub account_number: U256, + #[serde(serialize_with = "U256::as_decimal_str")] + pub chain_id: U256, + pub fee: Eip712Fee, + pub memo: String, + /// Will be flatten as `"msg1": { ... }, "msg2": { ... }`. + #[serde(flatten)] + pub msgs: BTreeMap, + #[serde(serialize_with = "U256::as_decimal_str")] + pub sequence: U256, + #[serde(serialize_with = "U256::as_decimal_str")] + pub timeout_height: U256, +} + +impl Eip712Transaction { + /// cbindgen::ignore + pub const TYPE_NAME: &'static str = "Tx"; + + pub fn declare_eip712_types(&self, builder: &mut MessageTypesBuilder) { + Eip712Fee::declare_eip712_types(builder); + + let Some(mut tx_builder) = builder.add_custom_type(Self::TYPE_NAME.to_string()) else { + return; + }; + + tx_builder + .add_property("account_number", PropertyType::Uint) + .add_property("chain_id", PropertyType::Uint) + .add_property( + "fee", + PropertyType::Custom(Eip712Fee::TYPE_NAME.to_string()), + ) + .add_property("memo", PropertyType::String) + .add_property("sequence", PropertyType::Uint) + .add_property("timeout_height", PropertyType::Uint); + + for (msg_property_name, _msg) in self.msgs.iter() { + let msg_property_type = MsgPropertyType(msg_property_name.0).to_string(); + let msg_property_type = PropertyType::Custom(msg_property_type.to_string()); + + let msg_property_name = msg_property_name.to_string(); + tx_builder.add_property(&msg_property_name, msg_property_type); + } + + tx_builder.sort_by_names(); + } +} diff --git a/rust/chains/tw_greenfield/src/entry.rs b/rust/chains/tw_greenfield/src/entry.rs new file mode 100644 index 00000000000..c78823b9b27 --- /dev/null +++ b/rust/chains/tw_greenfield/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::address::GreenfieldAddress; +use crate::compiler::GreenfieldCompiler; +use crate::signer::GreenfieldSigner; +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::{AddressError, AddressResult}; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::prefix::NoPrefix; +use tw_keypair::tw::PublicKey; +use tw_proto::Greenfield::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct GreenfieldEntry; + +impl CoinEntry for GreenfieldEntry { + type AddressPrefix = NoPrefix; + type Address = GreenfieldAddress; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + + #[inline] + fn parse_address( + &self, + _coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + GreenfieldAddress::from_str(address) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + GreenfieldAddress::from_str(address) + } + + #[inline] + fn derive_address( + &self, + _coin: &dyn CoinContext, + public_key: PublicKey, + _derivation: Derivation, + _prefix: Option, + ) -> AddressResult { + let public_key = public_key + .to_secp256k1() + .ok_or(AddressError::PublicKeyTypeMismatch)?; + Ok(GreenfieldAddress::with_secp256k1_pubkey(public_key)) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + GreenfieldSigner::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + GreenfieldCompiler::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + GreenfieldCompiler::compile(coin, input, signatures, public_keys) + } +} diff --git a/rust/chains/tw_greenfield/src/lib.rs b/rust/chains/tw_greenfield/src/lib.rs new file mode 100644 index 00000000000..8c018a0cdba --- /dev/null +++ b/rust/chains/tw_greenfield/src/lib.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. + +pub mod address; +pub mod compiler; +pub mod context; +pub mod eip712_types; +pub mod entry; +pub mod modules; +pub mod public_key; +pub mod signature; +pub mod signer; +pub mod transaction; diff --git a/rust/chains/tw_greenfield/src/modules/eip712_signer.rs b/rust/chains/tw_greenfield/src/modules/eip712_signer.rs new file mode 100644 index 00000000000..a44796c821b --- /dev/null +++ b/rust/chains/tw_greenfield/src/modules/eip712_signer.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::eip712_types::{ + Eip712Domain, Eip712Fee, Eip712Transaction, Eip712TypedMsg, MsgPropertyName, +}; +use crate::transaction::GreenfieldUnsignedTransaction; +use std::collections::BTreeMap; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_evm::message::eip712::eip712_message::Eip712Message; +use tw_evm::message::eip712::message_types::MessageTypesBuilder; +use tw_evm::message::EthMessage; +use tw_hash::H256; +use tw_number::U256; + +pub struct Eip712TxPreimage { + pub eip712_tx: String, + pub tx_hash: H256, +} + +pub struct Eip712Signer; + +impl Eip712Signer { + pub fn preimage_hash( + unsigned: &GreenfieldUnsignedTransaction, + ) -> SigningResult { + // `types_builder` will be used to declare all custom types like `Tx`, `Fee`, `Msg1` etc. + let mut types_builder = MessageTypesBuilder::default(); + + // Step 1: Convert all [`TxBody::messages`] to a map of `msg1: GreenfieldTypedMsg`, `msg2: GreenfieldTypedMsg`. + // at the same time, declare the message custom types. + let mut msgs = BTreeMap::new(); + for (msg_idx, msg) in unsigned.tx_body.messages.iter().enumerate() { + // Index of the transaction messages starts from 1. + let msg_idx = msg_idx + 1; + + let property_name = MsgPropertyName(msg_idx); + let property_value = Eip712TypedMsg::from(msg.to_json()?); + + msgs.insert(property_name, property_value); + + // Declare message custom types like `Msg1`, `TypeMsg1Amount`, etc. + msg.declare_eip712_type(msg_idx, &mut types_builder); + } + + // Step 2: Generate `Tx` and `Domain` objects - the main parts of the EIP712 message. + let tx_to_sign = Eip712Transaction { + account_number: U256::from(unsigned.account_number), + chain_id: unsigned.eth_chain_id, + fee: Eip712Fee::from(unsigned.fee.clone()), + memo: unsigned.tx_body.memo.clone(), + msgs, + sequence: U256::from(unsigned.signer.sequence), + timeout_height: U256::zero(), + }; + let domain = Eip712Domain::new(unsigned.eth_chain_id); + + // Step 3: Declare `Tx`, `Domain` and all types they depend on. + tx_to_sign.declare_eip712_types(&mut types_builder); + domain.declare_eip712_types(&mut types_builder); + + // Step 4: Generate EIP712 message with all declared custom types, `Domain` and `Tx`, + // and compute the EIP712 message hash. + let msg_to_sign = Eip712Message { + types: types_builder.build(), + domain: serde_json::to_value(domain) + .map_err(|_| SigningError(SigningErrorType::Error_internal))?, + primary_type: Eip712Transaction::TYPE_NAME.to_string(), + message: serde_json::to_value(tx_to_sign) + .map_err(|_| SigningError(SigningErrorType::Error_internal))?, + }; + + let tx_hash = msg_to_sign.hash()?; + let eip712_tx = serde_json::to_string(&msg_to_sign) + .map_err(|_| SigningError(SigningErrorType::Error_internal))?; + + Ok(Eip712TxPreimage { eip712_tx, tx_hash }) + } +} diff --git a/rust/chains/tw_greenfield/src/modules/mod.rs b/rust/chains/tw_greenfield/src/modules/mod.rs new file mode 100644 index 00000000000..4ca8bc01e20 --- /dev/null +++ b/rust/chains/tw_greenfield/src/modules/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 eip712_signer; +pub mod tx_builder; diff --git a/rust/chains/tw_greenfield/src/modules/tx_builder.rs b/rust/chains/tw_greenfield/src/modules/tx_builder.rs new file mode 100644 index 00000000000..78ae1a73811 --- /dev/null +++ b/rust/chains/tw_greenfield/src/modules/tx_builder.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::address::GreenfieldAddress; +use crate::public_key::GreenfieldPublicKey; +use crate::transaction::message::GreenfieldMessageBox; +use crate::transaction::{ + GreenfieldFee, GreenfieldSignMode, GreenfieldSignerInfo, GreenfieldTxBody, + GreenfieldUnsignedTransaction, +}; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_cosmos_sdk::public_key::CosmosPublicKey; +use tw_cosmos_sdk::transaction::{Coin, SignerInfo}; +use tw_misc::traits::OptionalEmpty; +use tw_number::U256; +use tw_proto::Greenfield::Proto; + +const DEFAULT_TIMEOUT_HEIGHT: u64 = 0; + +/// [`TxBuilder`] is used to build `UnsignedTransaction` +/// from the `TW::Greenfield::Proto::SigningInput` protobuf message. +pub struct TxBuilder; + +impl TxBuilder { + /// 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 signer = Self::signer_info_from_proto(coin, input)?; + + let fee = input + .fee + .as_ref() + .ok_or(SigningError(SigningErrorType::Error_wrong_fee))?; + let fee = Self::fee_from_proto(fee, &signer)?; + + let eth_chain_id = U256::from_str(&input.eth_chain_id) + .map_err(|_| SigningError(SigningErrorType::Error_invalid_params))?; + + Ok(GreenfieldUnsignedTransaction { + signer, + fee, + cosmos_chain_id: input.cosmos_chain_id.to_string(), + eth_chain_id, + account_number: input.account_number, + tx_body: Self::tx_body_from_proto(input)?, + }) + } + + pub fn signer_info_from_proto( + coin: &dyn CoinContext, + input: &Proto::SigningInput, + ) -> SigningResult { + let public_key = GreenfieldPublicKey::from_bytes(coin, &input.public_key)?; + let sign_mode = match input.signing_mode { + Proto::SigningMode::Eip712 => GreenfieldSignMode::Eip712, + }; + Ok(SignerInfo { + public_key, + sequence: input.sequence, + sign_mode: sign_mode.into(), + }) + } + + fn fee_from_proto( + input: &Proto::Fee, + signer: &GreenfieldSignerInfo, + ) -> SigningResult { + let payer = GreenfieldAddress::with_secp256k1_pubkey(&signer.public_key.0); + + let amounts = input + .amounts + .iter() + .map(Self::coin_from_proto) + .collect::>()?; + Ok(GreenfieldFee { + amounts, + gas_limit: input.gas, + payer: Some(payer), + 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(input: &Proto::SigningInput<'_>) -> SigningResult { + if input.messages.is_empty() { + return Err(SigningError(SigningErrorType::Error_invalid_params)); + } + + let messages = input + .messages + .iter() + .map(Self::tx_message_from_proto) + .collect::>()?; + + Ok(GreenfieldTxBody { + messages, + memo: input.memo.to_string(), + timeout_height: DEFAULT_TIMEOUT_HEIGHT, + }) + } + + pub fn tx_message_from_proto(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(send), + MessageEnum::bridge_transfer_out(ref transfer_out) => { + Self::bridge_transfer_out_from_proto(transfer_out) + }, + MessageEnum::None => Err(SigningError(SigningErrorType::Error_invalid_params)), + } + } + + pub fn send_msg_from_proto( + send: &Proto::mod_Message::Send<'_>, + ) -> SigningResult { + use crate::transaction::message::send_order::GreenfieldSendMessage; + use tw_cosmos_sdk::transaction::message::cosmos_bank_message::SendMessage; + + let amounts = send + .amounts + .iter() + .map(Self::coin_from_proto) + .collect::>()?; + let msg = SendMessage { + custom_type_prefix: send.type_prefix.to_string().empty_or_some(), + from_address: GreenfieldAddress::from_str(&send.from_address)?, + to_address: GreenfieldAddress::from_str(&send.to_address)?, + amount: amounts, + }; + Ok(Box::new(GreenfieldSendMessage(msg))) + } + + pub fn bridge_transfer_out_from_proto( + transfer_out: &Proto::mod_Message::BridgeTransferOut<'_>, + ) -> SigningResult { + use crate::transaction::message::transfer_out::GreenfieldTransferOut; + + let amount = transfer_out + .amount + .as_ref() + .ok_or(SigningError(SigningErrorType::Error_wrong_fee))?; + + let msg = GreenfieldTransferOut { + custom_type_prefix: transfer_out.type_prefix.to_string().empty_or_some(), + amount: Self::coin_from_proto(amount)?, + from: GreenfieldAddress::from_str(&transfer_out.from_address)?, + to: GreenfieldAddress::from_str(&transfer_out.to_address)?, + }; + Ok(Box::new(msg)) + } +} diff --git a/rust/chains/tw_greenfield/src/public_key.rs b/rust/chains/tw_greenfield/src/public_key.rs new file mode 100644 index 00000000000..8486fef04e0 --- /dev/null +++ b/rust/chains/tw_greenfield/src/public_key.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 tw_coin_entry::coin_context::CoinContext; +use tw_cosmos_sdk::public_key::{CosmosPublicKey, JsonPublicKey, ProtobufPublicKey}; +use tw_keypair::ecdsa::secp256k1; +use tw_keypair::tw::{PrivateKey, PublicKeyType}; +use tw_keypair::{KeyPairError, KeyPairResult}; +use tw_memory::Data; +use tw_proto::{google, to_any}; + +pub struct GreenfieldPublicKey(pub secp256k1::PublicKey); + +impl JsonPublicKey for GreenfieldPublicKey { + fn public_key_type(&self) -> String { + "/cosmos.crypto.eth.ethsecp256k1.PubKey".to_string() + } +} + +impl ProtobufPublicKey for GreenfieldPublicKey { + fn to_proto(&self) -> google::protobuf::Any { + let proto = tw_cosmos_sdk::proto::cosmos::crypto::eth::ethsecp256k1::PubKey { + key: self.0.compressed().to_vec(), + }; + to_any(&proto) + } +} + +impl CosmosPublicKey for GreenfieldPublicKey { + fn from_private_key(_coin: &dyn CoinContext, private_key: &PrivateKey) -> KeyPairResult { + let public_key = private_key + .get_public_key_by_type(PublicKeyType::Secp256k1)? + .to_secp256k1() + .ok_or(KeyPairError::InvalidPublicKey)? + .clone(); + Ok(GreenfieldPublicKey(public_key)) + } + + fn from_bytes(_coin: &dyn CoinContext, public_key_bytes: &[u8]) -> KeyPairResult { + secp256k1::PublicKey::try_from(public_key_bytes).map(GreenfieldPublicKey) + } + + fn to_bytes(&self) -> Data { + self.0.compressed().to_vec() + } +} diff --git a/rust/chains/tw_greenfield/src/signature.rs b/rust/chains/tw_greenfield/src/signature.rs new file mode 100644 index 00000000000..bee96a99b9f --- /dev/null +++ b/rust/chains/tw_greenfield/src/signature.rs @@ -0,0 +1,42 @@ +// 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::signature::CosmosSignature; +use tw_evm::message::signature::{MessageSignature, SignatureType}; +use tw_keypair::ecdsa::secp256k1; +use tw_keypair::KeyPairError; +use tw_misc::traits::ToBytesVec; + +/// secp256k1 signature with the legacy ETH replay protection. +#[derive(Clone)] +pub struct GreenfieldSignature(MessageSignature); + +impl TryFrom for GreenfieldSignature { + type Error = KeyPairError; + + fn try_from(sign: secp256k1::Signature) -> Result { + MessageSignature::prepared(sign, SignatureType::Legacy).map(GreenfieldSignature) + } +} + +impl CosmosSignature for GreenfieldSignature {} + +/// [`GreenfieldSignature::try_from`] tries to parse a standard secp256k1 signature from the given bytes, +/// and applies the legacy ETH replay protection. +impl<'a> TryFrom<&'a [u8]> for GreenfieldSignature { + type Error = KeyPairError; + + fn try_from(bytes: &'a [u8]) -> Result { + let standard_sign = secp256k1::Signature::try_from(bytes)?; + GreenfieldSignature::try_from(standard_sign) + } +} + +impl ToBytesVec for GreenfieldSignature { + fn to_vec(&self) -> Vec { + self.0.to_bytes().to_vec() + } +} diff --git a/rust/chains/tw_greenfield/src/signer.rs b/rust/chains/tw_greenfield/src/signer.rs new file mode 100644 index 00000000000..43b1900c66f --- /dev/null +++ b/rust/chains/tw_greenfield/src/signer.rs @@ -0,0 +1,50 @@ +// 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::GreenfieldCompiler; +use crate::modules::eip712_signer::{Eip712Signer, Eip712TxPreimage}; +use crate::modules::tx_builder::TxBuilder; +use std::borrow::Cow; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::SigningResult; +use tw_coin_entry::signing_output_error; +use tw_keypair::ecdsa::secp256k1; +use tw_keypair::traits::{KeyPairTrait, SigningKeyTrait}; +use tw_misc::traits::ToBytesVec; +use tw_proto::Greenfield::Proto; + +pub struct GreenfieldSigner; + +impl GreenfieldSigner { + 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 key_pair = secp256k1::KeyPair::try_from(input.private_key.as_ref())?; + let public_key = key_pair.public().compressed().to_vec(); + + // Set the public key. It will be used to construct a signer info. + input.public_key = Cow::from(public_key.clone()); + let unsigned_tx = TxBuilder::unsigned_tx_from_proto(coin, &input)?; + + let Eip712TxPreimage { tx_hash, .. } = Eip712Signer::preimage_hash(&unsigned_tx)?; + // Get the standard secp256k1 signature. It will be EIP155 protected at the `GreenfieldCompiler::compile_impl`. + let signature = key_pair.sign(tx_hash)?; + + let signatures = vec![signature.to_vec()]; + let public_keys = vec![public_key]; + + GreenfieldCompiler::compile_impl(coin, input, signatures, public_keys) + } +} diff --git a/rust/chains/tw_greenfield/src/transaction/message/mod.rs b/rust/chains/tw_greenfield/src/transaction/message/mod.rs new file mode 100644 index 00000000000..2c7f8430c65 --- /dev/null +++ b/rust/chains/tw_greenfield/src/transaction/message/mod.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::eip712_types::MsgPropertyType; +use tw_cosmos_sdk::transaction::message::{CosmosMessage, CosmosMessageBox}; +use tw_evm::message::eip712::message_types::MessageTypesBuilder; + +pub mod send_order; +pub mod transfer_out; +pub mod type_msg_amount; + +pub type GreenfieldMessageBox = Box; + +pub trait GreenfieldMessage: CosmosMessage { + fn eip712_type(&self, msg_idx: usize) -> String { + MsgPropertyType(msg_idx).to_string() + } + + fn declare_eip712_type(&self, msg_idx: usize, message_types: &mut MessageTypesBuilder); + + fn to_cosmos_message(&self) -> CosmosMessageBox; +} diff --git a/rust/chains/tw_greenfield/src/transaction/message/send_order.rs b/rust/chains/tw_greenfield/src/transaction/message/send_order.rs new file mode 100644 index 00000000000..85fb9da35f3 --- /dev/null +++ b/rust/chains/tw_greenfield/src/transaction/message/send_order.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::address::GreenfieldAddress; +use crate::transaction::message::type_msg_amount::TypeMsgAmount; +use crate::transaction::message::GreenfieldMessage; +use tw_coin_entry::error::SigningResult; +use tw_cosmos_sdk::proto::cosmos as CosmosProto; +use tw_cosmos_sdk::transaction::message::cosmos_bank_message::SendMessage; +use tw_cosmos_sdk::transaction::message::{ + message_to_json, CosmosMessage, CosmosMessageBox, JsonMessage, ProtobufMessage, +}; +use tw_evm::abi::param_type::constructor::TypeConstructor; +use tw_evm::message::eip712::message_types::MessageTypesBuilder; +use tw_evm::message::eip712::property::PropertyType; +use tw_proto::type_url; + +/// cosmos.bank.v1beta1.MsgSend +/// +/// # EIP712 custom types +/// +/// ```json +/// { +/// "TypeMsgAmount": [ +/// { +/// "name": "amount", +/// "type": "string" +/// }, +/// { +/// "name": "denom", +/// "type": "string" +/// } +/// ], +/// "Msg": [ +/// { +/// "name": "amount", +/// "type": "TypeMsgAmount[]" +/// }, +/// { +/// "name": "from_address", +/// "type": "string" +/// }, +/// { +/// "name": "to_address", +/// "type": "string" +/// }, +/// { +/// "name": "type", +/// "type": "string" +/// } +/// ] +/// } +/// ``` +#[derive(Clone)] +pub struct GreenfieldSendMessage(pub SendMessage); + +impl CosmosMessage for GreenfieldSendMessage { + fn to_proto(&self) -> SigningResult { + self.0.to_proto() + } + + /// [`GreenfieldSendMessage::to_json`] implementation differs from the original [`SendMessage::to_json`]: + /// * [`JsonMessage::msg_type`] should be "cosmos.bank.v1beta1.MsgSend" if other is not specified. + /// * [`JsonMessage::value`] is the same. + fn to_json(&self) -> SigningResult { + let msg_type = self + .0 + .custom_type_prefix + .clone() + .unwrap_or_else(type_url::); + message_to_json(&msg_type, &self.0) + } +} + +impl GreenfieldMessage for GreenfieldSendMessage { + fn declare_eip712_type(&self, msg_idx: usize, message_types: &mut MessageTypesBuilder) { + let this_msg_type_name = self.eip712_type(msg_idx); + + TypeMsgAmount::declare_eip712_type(msg_idx, message_types); + + if let Some(mut builder) = message_types.add_custom_type(this_msg_type_name) { + let amount_msg_type = PropertyType::Custom(TypeMsgAmount::eip712_type(msg_idx)); + builder + .add_property("amount", PropertyType::array(amount_msg_type)) + .add_property("from_address", PropertyType::String) + .add_property("to_address", PropertyType::String) + .add_property("type", PropertyType::String); + } + } + + fn to_cosmos_message(&self) -> CosmosMessageBox { + Box::new(self.clone()) + } +} diff --git a/rust/chains/tw_greenfield/src/transaction/message/transfer_out.rs b/rust/chains/tw_greenfield/src/transaction/message/transfer_out.rs new file mode 100644 index 00000000000..dec3e261fac --- /dev/null +++ b/rust/chains/tw_greenfield/src/transaction/message/transfer_out.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::GreenfieldAddress; +use crate::transaction::message::type_msg_amount::TypeMsgAmount; +use crate::transaction::message::GreenfieldMessage; +use serde::Serialize; +use tw_coin_entry::error::SigningResult; +use tw_cosmos_sdk::modules::serializer::protobuf_serializer::build_coin; +use tw_cosmos_sdk::proto::greenfield as GreenfieldProto; +use tw_cosmos_sdk::transaction::message::{ + message_to_json, CosmosMessage, CosmosMessageBox, JsonMessage, ProtobufMessage, +}; +use tw_cosmos_sdk::transaction::Coin; +use tw_evm::message::eip712::message_types::MessageTypesBuilder; +use tw_evm::message::eip712::property::PropertyType; +use tw_proto::{to_any, type_url}; + +/// greenfield.bridge.MsgTransferOut +/// +/// # EIP712 custom types +/// +/// ```json +/// { +/// "TypeMsgAmount": [ +/// { +/// "name": "amount", +/// "type": "string" +/// }, +/// { +/// "name": "denom", +/// "type": "string" +/// } +/// ], +/// "Msg": [ +/// { +/// "name": "amount", +/// "type": "TypeMsgAmount" +/// }, +/// { +/// "name": "from", +/// "type": "string" +/// }, +/// { +/// "name": "to", +/// "type": "string" +/// }, +/// { +/// "name": "type", +/// "type": "string" +/// } +/// ] +/// } +/// ``` +#[derive(Clone, Serialize)] +pub struct GreenfieldTransferOut { + #[serde(skip)] + pub custom_type_prefix: Option, + pub amount: Coin, + pub from: GreenfieldAddress, + pub to: GreenfieldAddress, +} + +impl CosmosMessage for GreenfieldTransferOut { + fn to_proto(&self) -> SigningResult { + let msg = GreenfieldProto::bridge::MsgTransferOut { + from: self.from.to_string(), + to: self.to.to_string(), + amount: Some(build_coin(&self.amount)), + }; + Ok(to_any(&msg)) + } + + fn to_json(&self) -> SigningResult { + let msg_type = self + .custom_type_prefix + .clone() + .unwrap_or_else(type_url::); + message_to_json(&msg_type, self) + } +} + +impl GreenfieldMessage for GreenfieldTransferOut { + fn declare_eip712_type(&self, msg_idx: usize, message_types: &mut MessageTypesBuilder) { + let this_msg_type_name = self.eip712_type(msg_idx); + + TypeMsgAmount::declare_eip712_type(msg_idx, message_types); + + if let Some(mut builder) = message_types.add_custom_type(this_msg_type_name) { + let amount_msg_type = PropertyType::Custom(TypeMsgAmount::eip712_type(msg_idx)); + builder + .add_property("amount", amount_msg_type) + .add_property("from", PropertyType::String) + .add_property("to", PropertyType::String) + .add_property("type", PropertyType::String); + } + } + + fn to_cosmos_message(&self) -> CosmosMessageBox { + Box::new(self.clone()) + } +} diff --git a/rust/chains/tw_greenfield/src/transaction/message/type_msg_amount.rs b/rust/chains/tw_greenfield/src/transaction/message/type_msg_amount.rs new file mode 100644 index 00000000000..3b338c3acb7 --- /dev/null +++ b/rust/chains/tw_greenfield/src/transaction/message/type_msg_amount.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 tw_evm::message::eip712::message_types::MessageTypesBuilder; +use tw_evm::message::eip712::property::PropertyType; + +/// Represents an amount type that belongs to a particular order. +pub struct TypeMsgAmount; + +impl TypeMsgAmount { + pub fn eip712_type(msg_idx: usize) -> String { + format!("TypeMsg{msg_idx}Amount") + } + + pub fn declare_eip712_type(msg_idx: usize, message_types: &mut MessageTypesBuilder) { + let type_msg_amount_type = Self::eip712_type(msg_idx); + if let Some(mut builder) = message_types.add_custom_type(type_msg_amount_type) { + builder.add_property("amount", PropertyType::String); + builder.add_property("denom", PropertyType::String); + } + } +} diff --git a/rust/chains/tw_greenfield/src/transaction/mod.rs b/rust/chains/tw_greenfield/src/transaction/mod.rs new file mode 100644 index 00000000000..718cb9fd0a3 --- /dev/null +++ b/rust/chains/tw_greenfield/src/transaction/mod.rs @@ -0,0 +1,83 @@ +// 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::GreenfieldAddress; +use crate::context::GreenfieldContext; +use crate::public_key::GreenfieldPublicKey; +use crate::signature::GreenfieldSignature; +use crate::transaction::message::GreenfieldMessageBox; +use tw_cosmos_sdk::transaction::{ + Fee, SignMode, SignedTransaction, SignerInfo, TxBody, UnsignedTransaction, +}; +use tw_misc::traits::ToBytesVec; +use tw_number::U256; + +pub type GreenfieldSignerInfo = SignerInfo; +pub type GreenfieldFee = Fee; +pub type GreenfieldSignedTransaction = SignedTransaction; + +pub mod message; + +pub enum GreenfieldSignMode { + Eip712, +} + +impl From for SignMode { + fn from(mode: GreenfieldSignMode) -> Self { + use tw_cosmos_sdk::proto::cosmos::signing::v1beta1 as signing_proto; + + match mode { + GreenfieldSignMode::Eip712 => { + SignMode::Other(signing_proto::SignMode::SIGN_MODE_EIP_712 as i32) + }, + } + } +} + +pub struct GreenfieldTxBody { + pub messages: Vec, + pub memo: String, + pub timeout_height: u64, +} + +impl GreenfieldTxBody { + fn into_cosmos_tx_body(self) -> TxBody { + TxBody { + messages: self + .messages + .into_iter() + .map(|greenfield_msg| greenfield_msg.to_cosmos_message()) + .collect(), + memo: self.memo, + timeout_height: self.timeout_height, + } + } +} + +pub struct GreenfieldUnsignedTransaction { + pub signer: GreenfieldSignerInfo, + pub fee: GreenfieldFee, + pub cosmos_chain_id: String, + pub eth_chain_id: U256, + pub account_number: u64, + pub tx_body: GreenfieldTxBody, +} + +impl GreenfieldUnsignedTransaction { + pub fn into_signed(self, signature: GreenfieldSignature) -> GreenfieldSignedTransaction { + self.into_cosmos_unsigned().into_signed(signature.to_vec()) + } + + fn into_cosmos_unsigned(self) -> UnsignedTransaction { + UnsignedTransaction { + signer: self.signer, + fee: self.fee, + chain_id: self.cosmos_chain_id, + account_number: self.account_number, + tx_body: self.tx_body.into_cosmos_tx_body(), + } + } +} diff --git a/rust/chains/tw_greenfield/tests/data/send_order_eip712.json b/rust/chains/tw_greenfield/tests/data/send_order_eip712.json new file mode 100644 index 00000000000..01f861be82b --- /dev/null +++ b/rust/chains/tw_greenfield/tests/data/send_order_eip712.json @@ -0,0 +1,149 @@ +{ + "types": { + "Coin": [ + { + "name": "amount", + "type": "uint256" + }, + { + "name": "denom", + "type": "string" + } + ], + "EIP712Domain": [ + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "salt", + "type": "string" + }, + { + "name": "verifyingContract", + "type": "string" + }, + { + "name": "version", + "type": "string" + } + ], + "Fee": [ + { + "name": "amount", + "type": "Coin[]" + }, + { + "name": "gas_limit", + "type": "uint256" + }, + { + "name": "granter", + "type": "string" + }, + { + "name": "payer", + "type": "string" + } + ], + "Msg1": [ + { + "name": "amount", + "type": "TypeMsg1Amount[]" + }, + { + "name": "from_address", + "type": "string" + }, + { + "name": "to_address", + "type": "string" + }, + { + "name": "type", + "type": "string" + } + ], + "Tx": [ + { + "name": "account_number", + "type": "uint256" + }, + { + "name": "chain_id", + "type": "uint256" + }, + { + "name": "fee", + "type": "Fee" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "msg1", + "type": "Msg1" + }, + { + "name": "sequence", + "type": "uint256" + }, + { + "name": "timeout_height", + "type": "uint256" + } + ], + "TypeMsg1Amount": [ + { + "name": "amount", + "type": "string" + }, + { + "name": "denom", + "type": "string" + } + ] + }, + "primaryType": "Tx", + "domain": { + "name": "Greenfield Tx", + "version": "1.0.0", + "chainId": "5600", + "verifyingContract": "greenfield", + "salt": "0" + }, + "message": { + "account_number": "15560", + "chain_id": "5600", + "fee": { + "amount": [ + { + "amount": "2000000000000000", + "denom": "BNB" + } + ], + "gas_limit": "200000", + "granter": "", + "payer": "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1" + }, + "memo": "", + "msg1": { + "amount": [ + { + "amount": "1000000000000000", + "denom": "BNB" + } + ], + "from_address": "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1", + "to_address": "0x280b27f3676db1C4475EE10F75D510Eb527fd155", + "type": "/cosmos.bank.v1beta1.MsgSend" + }, + "sequence": "2", + "timeout_height": "0" + } +} diff --git a/rust/chains/tw_greenfield/tests/data/transfer_out_eip712.json b/rust/chains/tw_greenfield/tests/data/transfer_out_eip712.json new file mode 100644 index 00000000000..c17ecd1bea2 --- /dev/null +++ b/rust/chains/tw_greenfield/tests/data/transfer_out_eip712.json @@ -0,0 +1,147 @@ +{ + "types": { + "Coin": [ + { + "name": "amount", + "type": "uint256" + }, + { + "name": "denom", + "type": "string" + } + ], + "EIP712Domain": [ + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "salt", + "type": "string" + }, + { + "name": "verifyingContract", + "type": "string" + }, + { + "name": "version", + "type": "string" + } + ], + "Fee": [ + { + "name": "amount", + "type": "Coin[]" + }, + { + "name": "gas_limit", + "type": "uint256" + }, + { + "name": "granter", + "type": "string" + }, + { + "name": "payer", + "type": "string" + } + ], + "Msg1": [ + { + "name": "amount", + "type": "TypeMsg1Amount" + }, + { + "name": "from", + "type": "string" + }, + { + "name": "to", + "type": "string" + }, + { + "name": "type", + "type": "string" + } + ], + "Tx": [ + { + "name": "account_number", + "type": "uint256" + }, + { + "name": "chain_id", + "type": "uint256" + }, + { + "name": "fee", + "type": "Fee" + }, + { + "name": "memo", + "type": "string" + }, + { + "name": "msg1", + "type": "Msg1" + }, + { + "name": "sequence", + "type": "uint256" + }, + { + "name": "timeout_height", + "type": "uint256" + } + ], + "TypeMsg1Amount": [ + { + "name": "amount", + "type": "string" + }, + { + "name": "denom", + "type": "string" + } + ] + }, + "primaryType": "Tx", + "domain": { + "name": "Greenfield Tx", + "version": "1.0.0", + "chainId": "5600", + "verifyingContract": "greenfield", + "salt": "0" + }, + "message": { + "account_number": "15560", + "chain_id": "5600", + "fee": { + "amount": [ + { + "amount": "2000000000000000", + "denom": "BNB" + } + ], + "gas_limit": "200000", + "granter": "", + "payer": "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1" + }, + "memo": "", + "msg1": { + "amount": { + "amount": "1000000000000000", + "denom": "BNB" + }, + "from": "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1", + "to": "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1", + "type": "/greenfield.bridge.MsgTransferOut" + }, + "sequence": "2", + "timeout_height": "0" + } +} \ No newline at end of file diff --git a/rust/chains/tw_greenfield/tests/eip712_signer.rs b/rust/chains/tw_greenfield/tests/eip712_signer.rs new file mode 100644 index 00000000000..75d77eb1ba4 --- /dev/null +++ b/rust/chains/tw_greenfield/tests/eip712_signer.rs @@ -0,0 +1,109 @@ +// 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::test_utils::test_context::TestCoinContext; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_greenfield::modules::eip712_signer::{Eip712Signer, Eip712TxPreimage}; +use tw_greenfield::modules::tx_builder::TxBuilder; +use tw_misc::assert_eq_json; +use tw_proto::Greenfield::Proto; +use Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +const SEND_ORDER_EIP712: &str = include_str!("data/send_order_eip712.json"); +const TRANSFER_OUT_EIP712: &str = include_str!("data/transfer_out_eip712.json"); + +const PUBLIC_KEY_15560: &str = "0279ef34064da10db0463c70480616ba020703ec3a45026def7bebd2082f5d6fc8"; + +fn make_amount<'a>(denom: &'a str, amount: &'a str) -> Proto::Amount<'a> { + Proto::Amount { + denom: denom.into(), + amount: amount.into(), + } +} + +/// Testnet transaction: +/// https://greenfieldscan.com/tx/0x9f895cf2dd64fb1f428cefcf2a6585a813c3540fc9fe1ef42db1da2cb1df55ab +#[test] +fn test_eip712_signer_encode_send() { + let coin = TestCoinContext::default(); + + let send_order = Proto::mod_Message::Send { + from_address: "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1".into(), + to_address: "0x280b27f3676db1C4475EE10F75D510Eb527fd155".into(), + amounts: vec![make_amount("BNB", "1000000000000000")], + ..Proto::mod_Message::Send::default() + }; + + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Eip712, + account_number: 15560, + eth_chain_id: "5600".into(), + cosmos_chain_id: "greenfield_5600-1".into(), + fee: Some(Proto::Fee { + amounts: vec![make_amount("BNB", "2000000000000000")], + gas: 200000, + }), + sequence: 2, + messages: vec![Proto::Message { + message_oneof: MessageEnum::send_coins_message(send_order), + }], + public_key: PUBLIC_KEY_15560.decode_hex().unwrap().into(), + ..Proto::SigningInput::default() + }; + + let unsigned_tx = TxBuilder::unsigned_tx_from_proto(&coin, &input) + .expect("Error on generating an unsigned transaction"); + + let Eip712TxPreimage { eip712_tx, tx_hash } = + Eip712Signer::preimage_hash(&unsigned_tx).expect("Error on TX preimage"); + + assert_eq_json!(eip712_tx, SEND_ORDER_EIP712); + assert_eq!( + tx_hash.to_hex(), + "b8c62654582ca96b37ca94966199682bf70ed934e740d2f874ff54675a0ac344" + ); +} + +#[test] +fn test_eip712_signer_encode_transfer_out() { + let coin = TestCoinContext::default(); + + let send_order = Proto::mod_Message::BridgeTransferOut { + from_address: "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1".into(), + to_address: "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1".into(), + amount: Some(make_amount("BNB", "1000000000000000")), + ..Proto::mod_Message::BridgeTransferOut::default() + }; + + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Eip712, + account_number: 15560, + eth_chain_id: "5600".into(), + cosmos_chain_id: "greenfield_5600-1".into(), + fee: Some(Proto::Fee { + amounts: vec![make_amount("BNB", "2000000000000000")], + gas: 200000, + }), + sequence: 2, + messages: vec![Proto::Message { + message_oneof: MessageEnum::bridge_transfer_out(send_order), + }], + public_key: PUBLIC_KEY_15560.decode_hex().unwrap().into(), + ..Proto::SigningInput::default() + }; + + let unsigned_tx = TxBuilder::unsigned_tx_from_proto(&coin, &input) + .expect("Error on generating an unsigned transaction"); + + let Eip712TxPreimage { eip712_tx, tx_hash } = + Eip712Signer::preimage_hash(&unsigned_tx).expect("Error on TX preimage"); + + assert_eq_json!(eip712_tx, TRANSFER_OUT_EIP712); + assert_eq!( + tx_hash.to_hex(), + "ea7731461041f5f652ab424bb767c670e484cfe1f4a85179deba8a6596873af4" + ); +} diff --git a/rust/tw_any_coin/tests/chains/binance/binance_sign.rs b/rust/tw_any_coin/tests/chains/binance/binance_sign.rs index 564b750971e..de227f4efc5 100644 --- a/rust/tw_any_coin/tests/chains/binance/binance_sign.rs +++ b/rust/tw_any_coin/tests/chains/binance/binance_sign.rs @@ -10,6 +10,7 @@ use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::{DecodeHex, ToHex}; use tw_proto::Binance::Proto; use tw_proto::Binance::Proto::mod_SigningInput::OneOforder_oneof as OrderEnum; +use tw_proto::Common::Proto::SigningError; const ACCOUNT_12_PRIVATE_KEY: &str = "90335b9d2153ad1a9799a3ccc070bd64b4164e9642ee1dd48053c33f9a3a05e9"; @@ -51,6 +52,7 @@ fn test_binance_sign_trade_order() { let mut signer = AnySignerHelper::::default(); let output = signer.sign(CoinType::Binance, input); + assert_eq!(output.error, SigningError::OK); assert_eq!( output.encoded.to_hex(), "dc01f0625dee0a64ce6dc0430a14ba36f0fad74d8f41045463e4774f328f4af779e5122b424133364630464144373444384634313034353436334534373734463332384634414637373945352d33361a0b4e4e422d3333385f424e422002280130b09282413880c2d72f4001126e0a26eb5ae98721029729a52e4e3c2b4a4e52aa74033eedaf8ba1df5ab6d1f518fd69e67bbd309b0e12409123cb6906bb20aeb753f4a121d4d88ff0e9750ba75b0c4e10d76caee1e7d2481290fa3b9887a6225d6997f5f939ef834ea61d596a314237c48e560da9e17b5a180c20232001" @@ -83,6 +85,7 @@ fn test_binance_sign_cancel_trade_order() { let mut signer = AnySignerHelper::::default(); let output = signer.sign(CoinType::Binance, input); + assert_eq!(output.error, SigningError::OK); assert_eq!( output.encoded.to_hex(), "cc01f0625dee0a54166e681b0a14ba36f0fad74d8f41045463e4774f328f4af779e5120b4e4e422d3333385f424e421a2b424133364630464144373444384634313034353436334534373734463332384634414637373945352d3336126e0a26eb5ae98721029729a52e4e3c2b4a4e52aa74033eedaf8ba1df5ab6d1f518fd69e67bbd309b0e12403df6603426b991f7040bce22ce0137c12137df01e1d4d425cf3d9104103aec6335ac05c825e08ba26b9f72aa4cc45aa75cacfb6082df86b00692fef9701eb0f5180c20242001" @@ -138,6 +141,7 @@ fn test_binance_sign_send_order() { "1a04", "74657374", "2001", ); + assert_eq!(output.error, SigningError::OK); assert_eq!(output.encoded.to_hex(), expected_encoded); } @@ -172,6 +176,7 @@ fn test_binance_sign_token_freeze_order() { "6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162", "722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001", ); + assert_eq!(output.error, SigningError::OK); assert_eq!(output.encoded.to_hex(), expected_encoded); } @@ -206,6 +211,7 @@ fn test_binance_sign_token_unfreeze_order() { "6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162", "722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001", ); + assert_eq!(output.error, SigningError::OK); assert_eq!(output.encoded.to_hex(), expected_encoded); } @@ -242,6 +248,7 @@ fn test_binance_sign_token_issue_order() { "4e4e422d3333385f424e42", "208094ebdc032801126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc712401fbb993d643f03b3e8e757a502035f58c4c45aaaa6e107a3059ab7c6164283c10f1254e87feee21477c64c87b1a27d8481048533ae2f685b3ac0dc66e4edbc0b180f2001", ); + assert_eq!(output.error, SigningError::OK); assert_eq!(output.encoded.to_hex(), expected_encoded); } @@ -274,6 +281,7 @@ fn test_binance_sign_token_mint_order() { "4e4e422d3333385f424e42", "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001", ); + assert_eq!(output.error, SigningError::OK); assert_eq!(output.encoded.to_hex(), expected_encoded); } @@ -306,6 +314,7 @@ fn test_binance_sign_token_burn_order() { "4e4e422d3333385f424e42", "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001", ); + assert_eq!(output.error, SigningError::OK); assert_eq!(output.encoded.to_hex(), expected_encoded); } @@ -347,6 +356,7 @@ fn test_binance_sign_htlt_order() { "6b6a8fc7124051439de2da19fe9fd22137c903cfc5dc87553bf05dca0bb202c0e07c47f9b51269efa272", "43eb7b55888f5384a84ac1eac6d325c830d1be0ed042838e2dc0f6a9180f", ); + assert_eq!(output.error, SigningError::OK); assert_eq!(output.encoded.to_hex(), expected_encoded); } @@ -380,6 +390,7 @@ fn test_binance_sign_deposit_htlt_order() { "12400ca4144c6818e2836d09b4faf3161781d85f9adfc00badb2eaa0953174610a233b81413dafcf8471", "6abad48a4ed3aeb9884d90eb8416eec5d5c0c6930ab60bd01810", ); + assert_eq!(output.error, SigningError::OK); assert_eq!(output.encoded.to_hex(), expected_encoded); } @@ -414,6 +425,7 @@ fn test_binance_sign_claim_htlt_order() { "ac993dbe0f6b6a8fc71240fa30ba50111aa31d8329dacb6d044c1c7d54f1cb782bc9aa2a50c3fabce02a4579d7", "5b76ca69a9fab11b676d9da66b5af7aa4c9ad3d18e24fffeb16433be39fb180f2001", ); + assert_eq!(output.error, SigningError::OK); assert_eq!(output.encoded.to_hex(), expected_encoded); } @@ -446,6 +458,7 @@ fn test_binance_sign_refund_htlt_order() { "70b32206a2d15198b7165acf1e2a18952c9e4570b0f862e1ab7bb868c30781a42c9e3ec0ae08982e8d6c", "91c55b83c71b7b1e180f2001", ); + assert_eq!(output.error, SigningError::OK); assert_eq!(output.encoded.to_hex(), expected_encoded); } @@ -480,6 +493,7 @@ fn test_binance_sign_transfer_out_order() { "71a788ccf4e3eade1c7e1773e9d2093982d7f802f8f85f35ef550049011728206e4eda1a272f9e96fd95", "ef3983cad85a29cd14262c22e0180f2001", ); + assert_eq!(output.error, SigningError::OK); assert_eq!(output.encoded.to_hex(), expected_encoded); } @@ -515,6 +529,7 @@ fn test_binance_sign_side_chain_delegate_order() { "2a09ac2b6b6fb1d3b9fb5b4c03630d3d7a7da42b1c6736d6127142a3fcdca0b70a3d065da8d4f4df8b5d", "9d8f46aeb3627a7d7aa901fe186af34c180f2001", ); + assert_eq!(output.error, SigningError::OK); assert_eq!(output.encoded.to_hex(), expected_encoded); } @@ -553,6 +568,7 @@ fn test_binance_sign_side_chain_redelegate_order() { "af44ea561aac993dbe0f6b6a8fc71240114c6927423e95ecc831ec763b629b3a40db8feeb330528a941f", "d74843c0d63b4271b23916770d4901988c1f56b20086e5768a12290ebec265e30a80f8f3d88e180f2001", ); + assert_eq!(output.error, SigningError::OK); assert_eq!(output.encoded.to_hex(), expected_encoded); } @@ -588,6 +604,7 @@ fn test_binance_sign_side_chain_undelegate_order() { "75e5eaa675a5ed976b2ec3b8ca055a2b05e7fb471d328bd04df854789437dd06407e41ebb1e5a345604c", "93663dfb660e223800636c0b94c2e798180f2001", ); + assert_eq!(output.error, SigningError::OK); assert_eq!(output.encoded.to_hex(), expected_encoded); } @@ -619,6 +636,7 @@ fn test_binance_sign_time_lock_order() { "07921531", "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1121c4465736372697074696f6e206c6f636b656420666f72206f666665721a090a03424e4210c0843d20dbaaf8fa05126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240c270822b9515ba486c6a6b3472d388a5aea872ed960c0b53de0fafdc8682ef473a126f01e7dd2c00f04a0138a601b9540f54b14026846de362f7ab7f9fed948b180f2001", ); + assert_eq!(output.error, SigningError::OK); assert_eq!(output.encoded.to_hex(), expected_encoded); } @@ -653,6 +671,7 @@ fn test_binance_sign_time_relock_order() { "124086ddaa077c8ae551d402fa409cf7e91663982b0542200967c03c0b5876b181353250f689d342f221", "7624a077b671ce7d09649187e29879f40abbbee9de7ab27c180f2001", ); + assert_eq!(output.error, SigningError::OK); assert_eq!(output.encoded.to_hex(), expected_encoded); } @@ -683,5 +702,6 @@ fn test_binance_sign_time_unlock_order() { "fd2032834f59ec9fe69fd6eaa4aca24242dfbc5ec4ef8c435cb9da7eb05ab78e1b8ca9f109657cb77996", "898f1b59137b3d8f1e00f842e409e18033b347180f2001", ); + assert_eq!(output.error, SigningError::OK); assert_eq!(output.encoded.to_hex(), expected_encoded); } diff --git a/rust/tw_any_coin/tests/chains/greenfield/greenfield_address.rs b/rust/tw_any_coin/tests/chains/greenfield/greenfield_address.rs new file mode 100644 index 00000000000..6223ac6218d --- /dev/null +++ b/rust/tw_any_coin/tests/chains/greenfield/greenfield_address.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 tw_any_coin::test_utils::address_utils::{ + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, +}; +use tw_coin_registry::coin_type::CoinType; + +#[test] +fn test_greenfield_address_normalization() { + test_address_normalization( + CoinType::Greenfield, + "0xb16db98b365b1f89191996942612b14f1da4bd5f", + "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f", + ); +} + +#[test] +fn test_greenfield_address_is_valid() { + test_address_valid( + CoinType::Greenfield, + "0xb16db98b365b1f89191996942612b14f1da4bd5f", + ); +} + +#[test] +fn test_greenfield_address_invalid() { + test_address_invalid( + CoinType::Greenfield, + "b16Db98B365B1f89191996942612B14F1Da4Bd5f", + ); + test_address_invalid( + CoinType::Greenfield, + "bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", + ); +} + +#[test] +fn test_greenfield_address_get_data() { + test_address_get_data( + CoinType::Greenfield, + "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f", + "b16Db98B365B1f89191996942612B14F1Da4Bd5f", + ); +} diff --git a/rust/tw_any_coin/tests/chains/greenfield/greenfield_compile.rs b/rust/tw_any_coin/tests/chains/greenfield/greenfield_compile.rs new file mode 100644 index 00000000000..a48363ef791 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/greenfield/greenfield_compile.rs @@ -0,0 +1,78 @@ +// 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::greenfield::{make_amount, PUBLIC_KEY_15560}; +use std::borrow::Cow; +use tw_any_coin::test_utils::sign_utils::{CompilerHelper, PreImageHelper}; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_proto::Common::Proto::SigningError; +use tw_proto::Greenfield::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; +use Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +#[test] +fn test_greenfield_compile() { + let public_key_data = PUBLIC_KEY_15560.decode_hex().unwrap(); + + let send_order = Proto::mod_Message::Send { + from_address: "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1".into(), + to_address: "0x280b27f3676db1C4475EE10F75D510Eb527fd155".into(), + amounts: vec![make_amount("BNB", "1000000000000000")], + ..Proto::mod_Message::Send::default() + }; + + let mut input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Eip712, + account_number: 15560, + eth_chain_id: "5600".into(), + cosmos_chain_id: "greenfield_5600-1".into(), + fee: Some(Proto::Fee { + amounts: vec![make_amount("BNB", "2000000000000000")], + gas: 200000, + }), + sequence: 2, + messages: vec![Proto::Message { + message_oneof: MessageEnum::send_coins_message(send_order), + }], + mode: Proto::BroadcastMode::ASYNC, + public_key: public_key_data.clone().into(), + ..Proto::SigningInput::default() + }; + + // Step 2: Obtain preimage hash + let mut pre_imager = PreImageHelper::::default(); + let preimage_output = pre_imager.pre_image_hashes(CoinType::Greenfield, &input); + + assert_eq!(preimage_output.error, SigningError::OK); + assert_eq!( + preimage_output.data_hash.to_hex(), + "b8c62654582ca96b37ca94966199682bf70ed934e740d2f874ff54675a0ac344" + ); + + // Step 3: Compile transaction info + + // Simulate signature, normally obtained from signature server. + let signature = "cb3a4684a991014a387a04a85b59227ebb79567c2025addcb296b4ca856e9f810d3b526f2a0d0fad6ad1b126b3b9516f8b3be020a7cca9c03ce3cf47f4199b6d00"; + let protected_signature = "cb3a4684a991014a387a04a85b59227ebb79567c2025addcb296b4ca856e9f810d3b526f2a0d0fad6ad1b126b3b9516f8b3be020a7cca9c03ce3cf47f4199b6d1b"; + let signature_bytes = signature.decode_hex().unwrap(); + + // Reset the `SigningInput::public_key`. + // The public key argument of the `TWTransactionCompilerCompileWithSignatures` should be used instead. + input.public_key = Cow::default(); + let mut compiler = CompilerHelper::::default(); + let output = compiler.compile( + CoinType::Greenfield, + &input, + vec![signature_bytes], + vec![public_key_data], + ); + + let expected = r#"{"mode":"BROADCAST_MODE_ASYNC","tx_bytes":"CpQBCpEBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnEKKjB4OWQxZDk3YURGY2QzMjRCYmQ2MDNEMzg3MkJENzhlMDQwOTg1MTBiMRIqMHgyODBiMjdmMzY3NmRiMUM0NDc1RUUxMEY3NUQ1MTBFYjUyN2ZkMTU1GhcKA0JOQhIQMTAwMDAwMDAwMDAwMDAwMBJ5ClgKTQomL2Nvc21vcy5jcnlwdG8uZXRoLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohAnnvNAZNoQ2wRjxwSAYWugIHA+w6RQJt73vr0ggvXW/IEgUKAwjIBRgCEh0KFwoDQk5CEhAyMDAwMDAwMDAwMDAwMDAwEMCaDBpByzpGhKmRAUo4egSoW1kifrt5VnwgJa3cspa0yoVun4ENO1JvKg0PrWrRsSazuVFvizvgIKfMqcA8489H9BmbbRs="}"#; + assert_eq!(output.error, SigningError::OK); + assert_eq!(output.serialized, expected); + assert_eq!(output.signature.to_hex(), protected_signature); +} diff --git a/rust/tw_any_coin/tests/chains/greenfield/greenfield_sign.rs b/rust/tw_any_coin/tests/chains/greenfield/greenfield_sign.rs new file mode 100644 index 00000000000..f6469f95194 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/greenfield/greenfield_sign.rs @@ -0,0 +1,250 @@ +// 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::greenfield::{make_amount, PRIVATE_KEY_15560, PRIVATE_KEY_1686}; +use tw_any_coin::test_utils::sign_utils::AnySignerHelper; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_proto::Common::Proto::SigningError; +use tw_proto::Greenfield::Proto; +use Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + +/// **Testnet** +/// Successfully broadcasted: https://greenfieldscan.com/tx/0x9f895cf2dd64fb1f428cefcf2a6585a813c3540fc9fe1ef42db1da2cb1df55ab +#[test] +fn test_greenfield_sign_send_order_9f895c() { + let send_order = Proto::mod_Message::Send { + from_address: "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1".into(), + to_address: "0x280b27f3676db1C4475EE10F75D510Eb527fd155".into(), + amounts: vec![make_amount("BNB", "1000000000000000")], + ..Proto::mod_Message::Send::default() + }; + + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Eip712, + account_number: 15560, + eth_chain_id: "5600".into(), + cosmos_chain_id: "greenfield_5600-1".into(), + fee: Some(Proto::Fee { + amounts: vec![make_amount("BNB", "2000000000000000")], + gas: 200000, + }), + sequence: 2, + messages: vec![Proto::Message { + message_oneof: MessageEnum::send_coins_message(send_order), + }], + private_key: PRIVATE_KEY_15560.decode_hex().unwrap().into(), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Greenfield, input); + + let expected = r#"{"mode":"BROADCAST_MODE_SYNC","tx_bytes":"CpQBCpEBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnEKKjB4OWQxZDk3YURGY2QzMjRCYmQ2MDNEMzg3MkJENzhlMDQwOTg1MTBiMRIqMHgyODBiMjdmMzY3NmRiMUM0NDc1RUUxMEY3NUQ1MTBFYjUyN2ZkMTU1GhcKA0JOQhIQMTAwMDAwMDAwMDAwMDAwMBJ5ClgKTQomL2Nvc21vcy5jcnlwdG8uZXRoLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohAnnvNAZNoQ2wRjxwSAYWugIHA+w6RQJt73vr0ggvXW/IEgUKAwjIBRgCEh0KFwoDQk5CEhAyMDAwMDAwMDAwMDAwMDAwEMCaDBpByzpGhKmRAUo4egSoW1kifrt5VnwgJa3cspa0yoVun4ENO1JvKg0PrWrRsSazuVFvizvgIKfMqcA8489H9BmbbRs="}"#; + assert_eq!(output.error, SigningError::OK); + assert_eq!(output.serialized, expected); + assert_eq!(output.signature.to_hex(), "cb3a4684a991014a387a04a85b59227ebb79567c2025addcb296b4ca856e9f810d3b526f2a0d0fad6ad1b126b3b9516f8b3be020a7cca9c03ce3cf47f4199b6d1b"); +} + +/// Successfully broadcasted: https://greenfieldscan.com/tx/DA50E269A13D2C42770CD6A4C35EA7EBEE46C677465ED92985215E67C340E28D +#[test] +fn test_greenfield_sign_send_order_b798ab() { + let send_order = Proto::mod_Message::Send { + from_address: "0xF1DB7D5256d721fE3C144F5c1ed4b3A3A94Dc444".into(), + to_address: "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1".into(), + amounts: vec![make_amount("BNB", "100000000000000")], + ..Proto::mod_Message::Send::default() + }; + + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Eip712, + account_number: 1686, + eth_chain_id: "1017".into(), + cosmos_chain_id: "greenfield_1017-1".into(), + fee: Some(Proto::Fee { + amounts: vec![make_amount("BNB", "6000000000000")], + gas: 1200, + }), + sequence: 0, + messages: vec![Proto::Message { + message_oneof: MessageEnum::send_coins_message(send_order), + }], + private_key: PRIVATE_KEY_1686.decode_hex().unwrap().into(), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Greenfield, input); + + let expected = r#"{"mode":"BROADCAST_MODE_SYNC","tx_bytes":"CpMBCpABChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnAKKjB4RjFEQjdENTI1NmQ3MjFmRTNDMTQ0RjVjMWVkNGIzQTNBOTREYzQ0NBIqMHg5ZDFkOTdhREZjZDMyNEJiZDYwM0QzODcyQkQ3OGUwNDA5ODUxMGIxGhYKA0JOQhIPMTAwMDAwMDAwMDAwMDAwEnMKVgpNCiYvY29zbW9zLmNyeXB0by5ldGguZXRoc2VjcDI1NmsxLlB1YktleRIjCiEDCbufQiEfB0Z6DbH3BQTjvz/DjTWbiziPi702Ws2BbJYSBQoDCMgFEhkKFAoDQk5CEg02MDAwMDAwMDAwMDAwELAJGkGvQ8FyO09ECauebgRGWal94uZtp0K1vkqnl0xHCzzEylWLS7PJcBepUCkIkG4xnswnN4n7EqHZoZ+qVTH8Ue7fGw=="}"#; + assert_eq!(output.error, SigningError::OK); + assert_eq!(output.serialized, expected); + assert_eq!(output.signature.to_hex(), "af43c1723b4f4409ab9e6e044659a97de2e66da742b5be4aa7974c470b3cc4ca558b4bb3c97017a9502908906e319ecc273789fb12a1d9a19faa5531fc51eedf1b"); + + let expected_signature_json = r#"[{"pub_key":{"type":"/cosmos.crypto.eth.ethsecp256k1.PubKey","value":"Awm7n0IhHwdGeg2x9wUE478/w401m4s4j4u9NlrNgWyW"},"signature":"r0PBcjtPRAmrnm4ERlmpfeLmbadCtb5Kp5dMRws8xMpVi0uzyXAXqVApCJBuMZ7MJzeJ+xKh2aGfqlUx/FHu3xs="}]"#; + assert_eq!(output.signature_json, expected_signature_json); +} + +/// **Testnet** +/// Successfully broadcasted Greenfield: https://greenfieldscan.com/tx/38C29C530A74946CFD22EE07DA642F5EF9399BC9AEA59EC56A9B5BE16DE16CE7 +/// BSC (parent transaction): https://testnet.bscscan.com/tx/0x7f73c8a362e14e58cb5e0ec17616afc50eff7aa398db472383a6d017c8a5861a +#[test] +fn test_greenfield_sign_transfer_out() { + let transfer_out = Proto::mod_Message::BridgeTransferOut { + from_address: "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1".into(), + to_address: "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1".into(), + amount: Some(make_amount("BNB", "5670000000000000")), + ..Proto::mod_Message::BridgeTransferOut::default() + }; + + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Eip712, + account_number: 15560, + eth_chain_id: "5600".into(), + cosmos_chain_id: "greenfield_5600-1".into(), + fee: Some(Proto::Fee { + amounts: vec![make_amount("BNB", "6000000000000")], + gas: 1200, + }), + sequence: 7, + messages: vec![Proto::Message { + message_oneof: MessageEnum::bridge_transfer_out(transfer_out), + }], + private_key: PRIVATE_KEY_15560.decode_hex().unwrap().into(), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Greenfield, input); + + let expected = r#"{"mode":"BROADCAST_MODE_SYNC","tx_bytes":"CpkBCpYBCiEvZ3JlZW5maWVsZC5icmlkZ2UuTXNnVHJhbnNmZXJPdXQScQoqMHg5ZDFkOTdhREZjZDMyNEJiZDYwM0QzODcyQkQ3OGUwNDA5ODUxMGIxEioweDlkMWQ5N2FERmNkMzI0QmJkNjAzRDM4NzJCRDc4ZTA0MDk4NTEwYjEaFwoDQk5CEhA1NjcwMDAwMDAwMDAwMDAwEnUKWApNCiYvY29zbW9zLmNyeXB0by5ldGguZXRoc2VjcDI1NmsxLlB1YktleRIjCiECee80Bk2hDbBGPHBIBha6AgcD7DpFAm3ve+vSCC9db8gSBQoDCMgFGAcSGQoUCgNCTkISDTYwMDAwMDAwMDAwMDAQsAkaQc4DDByhu80Uy/M3sQePvAmhmbFWZeGq359rugtskEiXKfCzSB86XmBi+l+Q5ETDS2folMxbufHSE8gM4WBCHzgc"}"#; + assert_eq!(output.error, SigningError::OK); + assert_eq!(output.serialized, expected); + assert_eq!(output.signature.to_hex(), "ce030c1ca1bbcd14cbf337b1078fbc09a199b15665e1aadf9f6bba0b6c90489729f0b3481f3a5e6062fa5f90e444c34b67e894cc5bb9f1d213c80ce160421f381c"); +} + +#[test] +fn test_greenfield_sign_no_messages() { + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Eip712, + account_number: 15560, + eth_chain_id: "5600".into(), + cosmos_chain_id: "greenfield_5600-1".into(), + fee: Some(Proto::Fee { + amounts: vec![make_amount("BNB", "6000000000000")], + gas: 1200, + }), + sequence: 3, + messages: Vec::default(), + private_key: PRIVATE_KEY_15560.decode_hex().unwrap().into(), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Greenfield, input); + + assert_eq!(output.error, SigningError::Error_invalid_params); +} + +/// Successfully broadcasted: https://greenfieldscan.com/tx/82E967B702AFFA9227C3E987CD35B85DD862AE1C49F3027A639E354480138C40 +#[test] +fn test_greenfield_sign_multiple_messages_82e967() { + let send_order1 = Proto::mod_Message::Send { + from_address: "0xF1DB7D5256d721fE3C144F5c1ed4b3A3A94Dc444".into(), + to_address: "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1".into(), + amounts: vec![make_amount("BNB", "100000000000000")], + ..Proto::mod_Message::Send::default() + }; + let send_order2 = Proto::mod_Message::Send { + from_address: "0xF1DB7D5256d721fE3C144F5c1ed4b3A3A94Dc444".into(), + to_address: "0x280b27f3676db1C4475EE10F75D510Eb527fd155".into(), + amounts: vec![make_amount("BNB", "90000000000000")], + ..Proto::mod_Message::Send::default() + }; + + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Eip712, + account_number: 1686, + eth_chain_id: "1017".into(), + cosmos_chain_id: "greenfield_1017-1".into(), + fee: Some(Proto::Fee { + amounts: vec![make_amount("BNB", "15000000000000")], + gas: 3000, + }), + sequence: 1, + messages: vec![ + Proto::Message { + message_oneof: MessageEnum::send_coins_message(send_order1), + }, + Proto::Message { + message_oneof: MessageEnum::send_coins_message(send_order2), + }, + ], + private_key: PRIVATE_KEY_1686.decode_hex().unwrap().into(), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Greenfield, input); + + let expected = r#"{"mode":"BROADCAST_MODE_SYNC","tx_bytes":"CqUCCpABChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnAKKjB4RjFEQjdENTI1NmQ3MjFmRTNDMTQ0RjVjMWVkNGIzQTNBOTREYzQ0NBIqMHg5ZDFkOTdhREZjZDMyNEJiZDYwM0QzODcyQkQ3OGUwNDA5ODUxMGIxGhYKA0JOQhIPMTAwMDAwMDAwMDAwMDAwCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KKjB4RjFEQjdENTI1NmQ3MjFmRTNDMTQ0RjVjMWVkNGIzQTNBOTREYzQ0NBIqMHgyODBiMjdmMzY3NmRiMUM0NDc1RUUxMEY3NUQ1MTBFYjUyN2ZkMTU1GhUKA0JOQhIOOTAwMDAwMDAwMDAwMDASdgpYCk0KJi9jb3Ntb3MuY3J5cHRvLmV0aC5ldGhzZWNwMjU2azEuUHViS2V5EiMKIQMJu59CIR8HRnoNsfcFBOO/P8ONNZuLOI+LvTZazYFslhIFCgMIyAUYARIaChUKA0JOQhIOMTUwMDAwMDAwMDAwMDAQuBcaQS23kxqz7R/QZTegc1Ic1U0g+YUm9r8Y8Zx0+sHzKREYWg04yxy/EKvVY7LIqATQ2zi4rb9TZSIyXuMcyGk2JQYb"}"#; + assert_eq!(output.error, SigningError::OK); + assert_eq!(output.serialized, expected); + assert_eq!(output.signature.to_hex(), "2db7931ab3ed1fd06537a073521cd54d20f98526f6bf18f19c74fac1f32911185a0d38cb1cbf10abd563b2c8a804d0db38b8adbf536522325ee31cc8693625061b"); + + let expected_signature_json = r#"[{"pub_key":{"type":"/cosmos.crypto.eth.ethsecp256k1.PubKey","value":"Awm7n0IhHwdGeg2x9wUE478/w401m4s4j4u9NlrNgWyW"},"signature":"LbeTGrPtH9BlN6BzUhzVTSD5hSb2vxjxnHT6wfMpERhaDTjLHL8Qq9VjssioBNDbOLitv1NlIjJe4xzIaTYlBhs="}]"#; + assert_eq!(output.signature_json, expected_signature_json); +} + +/// Successfully broadcasted: https://greenfieldscan.com/tx/E3539ED65D195BE8CE43C1A6567384C758E40432B8FDC7C25841E21F5F53375B +#[test] +fn test_greenfield_sign_multiple_messages_e3539e() { + let send_order = Proto::mod_Message::Send { + from_address: "0xF1DB7D5256d721fE3C144F5c1ed4b3A3A94Dc444".into(), + to_address: "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1".into(), + amounts: vec![make_amount("BNB", "100000000000000")], + ..Proto::mod_Message::Send::default() + }; + let transfer_out = Proto::mod_Message::BridgeTransferOut { + from_address: "0xF1DB7D5256d721fE3C144F5c1ed4b3A3A94Dc444".into(), + to_address: "0xF1DB7D5256d721fE3C144F5c1ed4b3A3A94Dc444".into(), + amount: Some(make_amount("BNB", "200000000000000")), + ..Proto::mod_Message::BridgeTransferOut::default() + }; + + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Eip712, + account_number: 1686, + eth_chain_id: "1017".into(), + cosmos_chain_id: "greenfield_1017-1".into(), + fee: Some(Proto::Fee { + amounts: vec![make_amount("BNB", "15000000000000")], + gas: 3000, + }), + sequence: 2, + messages: vec![ + Proto::Message { + message_oneof: MessageEnum::send_coins_message(send_order), + }, + Proto::Message { + message_oneof: MessageEnum::bridge_transfer_out(transfer_out), + }, + ], + private_key: PRIVATE_KEY_1686.decode_hex().unwrap().into(), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Greenfield, input); + + let expected = r#"{"mode":"BROADCAST_MODE_SYNC","tx_bytes":"CqsCCpABChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnAKKjB4RjFEQjdENTI1NmQ3MjFmRTNDMTQ0RjVjMWVkNGIzQTNBOTREYzQ0NBIqMHg5ZDFkOTdhREZjZDMyNEJiZDYwM0QzODcyQkQ3OGUwNDA5ODUxMGIxGhYKA0JOQhIPMTAwMDAwMDAwMDAwMDAwCpUBCiEvZ3JlZW5maWVsZC5icmlkZ2UuTXNnVHJhbnNmZXJPdXQScAoqMHhGMURCN0Q1MjU2ZDcyMWZFM0MxNDRGNWMxZWQ0YjNBM0E5NERjNDQ0EioweEYxREI3RDUyNTZkNzIxZkUzQzE0NEY1YzFlZDRiM0EzQTk0RGM0NDQaFgoDQk5CEg8yMDAwMDAwMDAwMDAwMDASdgpYCk0KJi9jb3Ntb3MuY3J5cHRvLmV0aC5ldGhzZWNwMjU2azEuUHViS2V5EiMKIQMJu59CIR8HRnoNsfcFBOO/P8ONNZuLOI+LvTZazYFslhIFCgMIyAUYAhIaChUKA0JOQhIOMTUwMDAwMDAwMDAwMDAQuBcaQfkHNYUs2oMR0/ZauYXv8Nd/9bbrQ4w323P4GpyEO0j8IadMVE8TkmI6lIx4H/hPSbpG9wRrqhZZfzTsqSv5rDgb"}"#; + assert_eq!(output.error, SigningError::OK); + assert_eq!(output.serialized, expected); + assert_eq!(output.signature.to_hex(), "f90735852cda8311d3f65ab985eff0d77ff5b6eb438c37db73f81a9c843b48fc21a74c544f1392623a948c781ff84f49ba46f7046baa16597f34eca92bf9ac381b"); + + let expected_signature_json = r#"[{"pub_key":{"type":"/cosmos.crypto.eth.ethsecp256k1.PubKey","value":"Awm7n0IhHwdGeg2x9wUE478/w401m4s4j4u9NlrNgWyW"},"signature":"+Qc1hSzagxHT9lq5he/w13/1tutDjDfbc/ganIQ7SPwhp0xUTxOSYjqUjHgf+E9Jukb3BGuqFll/NOypK/msOBs="}]"#; + assert_eq!(output.signature_json, expected_signature_json); +} diff --git a/rust/tw_any_coin/tests/chains/greenfield/mod.rs b/rust/tw_any_coin/tests/chains/greenfield/mod.rs new file mode 100644 index 00000000000..a14f03efddd --- /dev/null +++ b/rust/tw_any_coin/tests/chains/greenfield/mod.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_proto::Greenfield::Proto; + +mod greenfield_address; +mod greenfield_compile; +mod greenfield_sign; + +const PRIVATE_KEY_15560: &str = "9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0"; +const PUBLIC_KEY_15560: &str = "0279ef34064da10db0463c70480616ba020703ec3a45026def7bebd2082f5d6fc8"; +const PRIVATE_KEY_1686: &str = "6f96f3aa7e8052170f1864f72a9a53606ee9c0d185188266cab895512a4bcf84"; + +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_any_coin/tests/chains/mod.rs b/rust/tw_any_coin/tests/chains/mod.rs index 7cbe502a6d2..cafbd33253c 100644 --- a/rust/tw_any_coin/tests/chains/mod.rs +++ b/rust/tw_any_coin/tests/chains/mod.rs @@ -9,6 +9,7 @@ mod binance; mod bitcoin; mod cosmos; mod ethereum; +mod greenfield; mod internet_computer; mod native_evmos; mod native_injective; diff --git a/rust/tw_coin_registry/Cargo.toml b/rust/tw_coin_registry/Cargo.toml index bd289c150a1..35dd638a29d 100644 --- a/rust/tw_coin_registry/Cargo.toml +++ b/rust/tw_coin_registry/Cargo.toml @@ -16,6 +16,7 @@ tw_coin_entry = { path = "../tw_coin_entry" } tw_cosmos = { path = "../chains/tw_cosmos" } tw_ethereum = { path = "../tw_ethereum" } tw_evm = { path = "../tw_evm" } +tw_greenfield = { path = "../chains/tw_greenfield" } tw_hash = { path = "../tw_hash" } tw_internet_computer = { path = "../tw_internet_computer" } tw_keypair = { path = "../tw_keypair" } diff --git a/rust/tw_coin_registry/src/blockchain_type.rs b/rust/tw_coin_registry/src/blockchain_type.rs index 28780562cd8..b6a27712ea7 100644 --- a/rust/tw_coin_registry/src/blockchain_type.rs +++ b/rust/tw_coin_registry/src/blockchain_type.rs @@ -16,6 +16,7 @@ pub enum BlockchainType { Bitcoin, Cosmos, Ethereum, + Greenfield, InternetComputer, NativeEvmos, NativeInjective, diff --git a/rust/tw_coin_registry/src/dispatcher.rs b/rust/tw_coin_registry/src/dispatcher.rs index d6e8fed9043..29e17dcd870 100644 --- a/rust/tw_coin_registry/src/dispatcher.rs +++ b/rust/tw_coin_registry/src/dispatcher.rs @@ -16,6 +16,7 @@ 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_greenfield::entry::GreenfieldEntry; use tw_internet_computer::entry::InternetComputerEntry; use tw_native_evmos::entry::NativeEvmosEntry; use tw_native_injective::entry::NativeInjectiveEntry; @@ -31,6 +32,7 @@ const BINANCE: BinanceEntry = BinanceEntry; const BITCOIN: BitcoinEntry = BitcoinEntry; const COSMOS: CosmosEntry = CosmosEntry; const ETHEREUM: EthereumEntry = EthereumEntry; +const GREENFIELD: GreenfieldEntry = GreenfieldEntry; const INTERNET_COMPUTER: InternetComputerEntry = InternetComputerEntry; const NATIVE_EVMOS: NativeEvmosEntry = NativeEvmosEntry; const NATIVE_INJECTIVE: NativeInjectiveEntry = NativeInjectiveEntry; @@ -46,6 +48,7 @@ pub fn blockchain_dispatcher(blockchain: BlockchainType) -> RegistryResult Ok(&BITCOIN), BlockchainType::Cosmos => Ok(&COSMOS), BlockchainType::Ethereum => Ok(ÐEREUM), + BlockchainType::Greenfield => Ok(&GREENFIELD), BlockchainType::InternetComputer => Ok(&INTERNET_COMPUTER), BlockchainType::NativeEvmos => Ok(&NATIVE_EVMOS), BlockchainType::NativeInjective => Ok(&NATIVE_INJECTIVE), diff --git a/src/Cosmos/Protobuf/authz_tx.proto b/rust/tw_cosmos_sdk/Protobuf/authz_tx.proto similarity index 100% rename from src/Cosmos/Protobuf/authz_tx.proto rename to rust/tw_cosmos_sdk/Protobuf/authz_tx.proto diff --git a/src/Cosmos/Protobuf/bank_tx.proto b/rust/tw_cosmos_sdk/Protobuf/bank_tx.proto similarity index 100% rename from src/Cosmos/Protobuf/bank_tx.proto rename to rust/tw_cosmos_sdk/Protobuf/bank_tx.proto diff --git a/src/Cosmos/Protobuf/coin.proto b/rust/tw_cosmos_sdk/Protobuf/coin.proto similarity index 100% rename from src/Cosmos/Protobuf/coin.proto rename to rust/tw_cosmos_sdk/Protobuf/coin.proto diff --git a/src/Cosmos/Protobuf/cosmwasm_wasm_v1_tx.proto b/rust/tw_cosmos_sdk/Protobuf/cosmwasm_wasm_v1_tx.proto similarity index 100% rename from src/Cosmos/Protobuf/cosmwasm_wasm_v1_tx.proto rename to rust/tw_cosmos_sdk/Protobuf/cosmwasm_wasm_v1_tx.proto diff --git a/src/Cosmos/Protobuf/crypto_multisig.proto b/rust/tw_cosmos_sdk/Protobuf/crypto_multisig.proto similarity index 100% rename from src/Cosmos/Protobuf/crypto_multisig.proto rename to rust/tw_cosmos_sdk/Protobuf/crypto_multisig.proto diff --git a/src/Cosmos/Protobuf/crypto_secp256k1_keys.proto b/rust/tw_cosmos_sdk/Protobuf/crypto_secp256k1_keys.proto similarity index 100% rename from src/Cosmos/Protobuf/crypto_secp256k1_keys.proto rename to rust/tw_cosmos_sdk/Protobuf/crypto_secp256k1_keys.proto diff --git a/src/Cosmos/Protobuf/distribution_tx.proto b/rust/tw_cosmos_sdk/Protobuf/distribution_tx.proto similarity index 100% rename from src/Cosmos/Protobuf/distribution_tx.proto rename to rust/tw_cosmos_sdk/Protobuf/distribution_tx.proto diff --git a/src/Cosmos/Protobuf/ethermint_keys.proto b/rust/tw_cosmos_sdk/Protobuf/ethermint_keys.proto similarity index 100% rename from src/Cosmos/Protobuf/ethermint_keys.proto rename to rust/tw_cosmos_sdk/Protobuf/ethermint_keys.proto diff --git a/src/Cosmos/Protobuf/gov_tx.proto b/rust/tw_cosmos_sdk/Protobuf/gov_tx.proto similarity index 100% rename from src/Cosmos/Protobuf/gov_tx.proto rename to rust/tw_cosmos_sdk/Protobuf/gov_tx.proto diff --git a/src/Cosmos/Protobuf/greenfield_ethsecp256k1.proto b/rust/tw_cosmos_sdk/Protobuf/greenfield_ethsecp256k1.proto similarity index 100% rename from src/Cosmos/Protobuf/greenfield_ethsecp256k1.proto rename to rust/tw_cosmos_sdk/Protobuf/greenfield_ethsecp256k1.proto diff --git a/src/Cosmos/Protobuf/greenfield_tx.proto b/rust/tw_cosmos_sdk/Protobuf/greenfield_tx.proto similarity index 100% rename from src/Cosmos/Protobuf/greenfield_tx.proto rename to rust/tw_cosmos_sdk/Protobuf/greenfield_tx.proto diff --git a/src/Cosmos/Protobuf/ibc_applications_transfer_tx.proto b/rust/tw_cosmos_sdk/Protobuf/ibc_applications_transfer_tx.proto similarity index 100% rename from src/Cosmos/Protobuf/ibc_applications_transfer_tx.proto rename to rust/tw_cosmos_sdk/Protobuf/ibc_applications_transfer_tx.proto diff --git a/src/Cosmos/Protobuf/ibc_core_client.proto b/rust/tw_cosmos_sdk/Protobuf/ibc_core_client.proto similarity index 100% rename from src/Cosmos/Protobuf/ibc_core_client.proto rename to rust/tw_cosmos_sdk/Protobuf/ibc_core_client.proto diff --git a/src/Cosmos/Protobuf/injective_keys.proto b/rust/tw_cosmos_sdk/Protobuf/injective_keys.proto similarity index 100% rename from src/Cosmos/Protobuf/injective_keys.proto rename to rust/tw_cosmos_sdk/Protobuf/injective_keys.proto diff --git a/src/Cosmos/Protobuf/staking_tx.proto b/rust/tw_cosmos_sdk/Protobuf/staking_tx.proto similarity index 100% rename from src/Cosmos/Protobuf/staking_tx.proto rename to rust/tw_cosmos_sdk/Protobuf/staking_tx.proto diff --git a/src/Cosmos/Protobuf/stride_liquid_staking.proto b/rust/tw_cosmos_sdk/Protobuf/stride_liquid_staking.proto similarity index 100% rename from src/Cosmos/Protobuf/stride_liquid_staking.proto rename to rust/tw_cosmos_sdk/Protobuf/stride_liquid_staking.proto diff --git a/src/Cosmos/Protobuf/terra_wasm_v1beta1_tx.proto b/rust/tw_cosmos_sdk/Protobuf/terra_wasm_v1beta1_tx.proto similarity index 100% rename from src/Cosmos/Protobuf/terra_wasm_v1beta1_tx.proto rename to rust/tw_cosmos_sdk/Protobuf/terra_wasm_v1beta1_tx.proto diff --git a/src/Cosmos/Protobuf/thorchain_bank_tx.proto b/rust/tw_cosmos_sdk/Protobuf/thorchain_bank_tx.proto similarity index 100% rename from src/Cosmos/Protobuf/thorchain_bank_tx.proto rename to rust/tw_cosmos_sdk/Protobuf/thorchain_bank_tx.proto diff --git a/src/Cosmos/Protobuf/tx.proto b/rust/tw_cosmos_sdk/Protobuf/tx.proto similarity index 100% rename from src/Cosmos/Protobuf/tx.proto rename to rust/tw_cosmos_sdk/Protobuf/tx.proto diff --git a/src/Cosmos/Protobuf/tx_signing.proto b/rust/tw_cosmos_sdk/Protobuf/tx_signing.proto similarity index 100% rename from src/Cosmos/Protobuf/tx_signing.proto rename to rust/tw_cosmos_sdk/Protobuf/tx_signing.proto diff --git a/rust/tw_cosmos_sdk/build.rs b/rust/tw_cosmos_sdk/build.rs index a434de70fb2..06fdb15ce6b 100644 --- a/rust/tw_cosmos_sdk/build.rs +++ b/rust/tw_cosmos_sdk/build.rs @@ -15,12 +15,7 @@ fn main() { 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 = cargo_manifest_dir.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); diff --git a/rust/tw_cosmos_sdk/src/modules/serializer/protobuf_serializer.rs b/rust/tw_cosmos_sdk/src/modules/serializer/protobuf_serializer.rs index ee67300b747..6f77b43c9db 100644 --- a/rust/tw_cosmos_sdk/src/modules/serializer/protobuf_serializer.rs +++ b/rust/tw_cosmos_sdk/src/modules/serializer/protobuf_serializer.rs @@ -138,28 +138,19 @@ impl ProtobufSerializer { } 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, + // Ignore `payer` and `granter` even if they set. + payer: String::default(), + granter: String::default(), } } fn build_sign_mode(sign_mode: SignMode) -> signing_proto::SignMode { match sign_mode { SignMode::Direct => signing_proto::SignMode::SIGN_MODE_DIRECT, + SignMode::Other(other) => signing_proto::SignMode::from(other), } } } diff --git a/rust/tw_cosmos_sdk/src/modules/tx_builder.rs b/rust/tw_cosmos_sdk/src/modules/tx_builder.rs index da5e87a94d1..a673bc1124d 100644 --- a/rust/tw_cosmos_sdk/src/modules/tx_builder.rs +++ b/rust/tw_cosmos_sdk/src/modules/tx_builder.rs @@ -15,7 +15,7 @@ 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_misc::traits::{OptionalEmpty, ToBytesVec}; use tw_number::U256; use tw_proto::Cosmos::Proto; use tw_proto::{google, serialize}; @@ -210,7 +210,7 @@ where .map(Self::coin_from_proto) .collect::>()?; let msg = SendMessage { - custom_type_prefix: Self::custom_msg_type(&send.type_prefix), + custom_type_prefix: send.type_prefix.to_string().empty_or_some(), from_address: Address::from_str_with_coin(coin, &send.from_address)?, to_address: Address::from_str_with_coin(coin, &send.to_address)?, amount: amounts, @@ -262,7 +262,7 @@ where .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), + custom_type_prefix: delegate.type_prefix.to_string().empty_or_some(), amount, delegator_address: Address::from_str_with_coin(coin, &delegate.delegator_address)?, validator_address: Address::from_str_with_coin(coin, &delegate.validator_address)?, @@ -283,7 +283,7 @@ where let amount = Self::coin_from_proto(amount)?; let msg = UndelegateMessage { - custom_type_prefix: Self::custom_msg_type(&undelegate.type_prefix), + custom_type_prefix: undelegate.type_prefix.to_string().empty_or_some(), amount, delegator_address: Address::from_str_with_coin(coin, &undelegate.delegator_address)?, validator_address: Address::from_str_with_coin(coin, &undelegate.validator_address)?, @@ -298,7 +298,7 @@ where use crate::transaction::message::cosmos_staking_message::WithdrawDelegationRewardMessage; let msg = WithdrawDelegationRewardMessage { - custom_type_prefix: Self::custom_msg_type(&withdraw.type_prefix), + custom_type_prefix: withdraw.type_prefix.to_string().empty_or_some(), delegator_address: Address::from_str_with_coin(coin, &withdraw.delegator_address)?, validator_address: Address::from_str_with_coin(coin, &withdraw.validator_address)?, }; @@ -312,7 +312,7 @@ where use crate::transaction::message::cosmos_staking_message::SetWithdrawAddressMessage; let msg = SetWithdrawAddressMessage { - custom_type_prefix: Self::custom_msg_type(&set.type_prefix), + custom_type_prefix: set.type_prefix.to_string().empty_or_some(), delegator_address: Address::from_str_with_coin(coin, &set.delegator_address)?, withdraw_address: Address::from_str_with_coin(coin, &set.withdraw_address)?, }; @@ -336,7 +336,7 @@ where Address::from_str_with_coin(coin, &redelegate.validator_dst_address)?; let msg = BeginRedelegateMessage { - custom_type_prefix: Self::custom_msg_type(&redelegate.type_prefix), + custom_type_prefix: redelegate.type_prefix.to_string().empty_or_some(), amount, delegator_address: Address::from_str_with_coin(coin, &redelegate.delegator_address)?, validator_src_address, @@ -644,12 +644,4 @@ where }; 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/transaction/message/cosmos_bank_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_bank_message.rs index dddfb065839..85c3a0a4470 100644 --- a/rust/tw_cosmos_sdk/src/transaction/message/cosmos_bank_message.rs +++ b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_bank_message.rs @@ -16,7 +16,7 @@ use tw_proto::to_any; const DEFAULT_JSON_SEND_TYPE: &str = "cosmos-sdk/MsgSend"; /// cosmos-sdk/MsgSend -#[derive(Serialize)] +#[derive(Clone, Serialize)] pub struct SendMessage { #[serde(skip)] pub custom_type_prefix: Option, diff --git a/rust/tw_cosmos_sdk/src/transaction/mod.rs b/rust/tw_cosmos_sdk/src/transaction/mod.rs index 0d3d02812de..c54cace61e2 100644 --- a/rust/tw_cosmos_sdk/src/transaction/mod.rs +++ b/rust/tw_cosmos_sdk/src/transaction/mod.rs @@ -17,8 +17,10 @@ use message::CosmosMessageBox; #[derive(Clone, Copy)] pub enum SignMode { Direct, + Other(i32), } +#[derive(Clone, Serialize)] pub struct Fee
{ pub amounts: Vec, pub gas_limit: u64, diff --git a/rust/tw_evm/src/message/eip712/eip712_message.rs b/rust/tw_evm/src/message/eip712/eip712_message.rs index d31d01608dc..be1dff96a3a 100644 --- a/rust/tw_evm/src/message/eip712/eip712_message.rs +++ b/rust/tw_evm/src/message/eip712/eip712_message.rs @@ -8,12 +8,12 @@ use crate::abi::encode::encode_tokens; use crate::abi::non_empty_array::NonEmptyBytes; use crate::abi::token::Token; use crate::address::Address; -use crate::message::eip712::property::{Property, PropertyType}; +use crate::message::eip712::message_types::CustomTypes; +use crate::message::eip712::property::PropertyType; use crate::message::{EthMessage, MessageSigningError, MessageSigningResult}; use itertools::Itertools; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use serde_json::Value as Json; -use std::collections::HashMap; use std::str::FromStr; use tw_encoding::hex::{self, DecodeHex}; use tw_hash::sha3::keccak256; @@ -27,15 +27,13 @@ const PREFIX: &[u8; 2] = b"\x19\x01"; /// cbindgen:ignore const EIP712_DOMAIN: &str = "EIP712Domain"; -type CustomTypes = HashMap>; - -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct Eip712Message { - types: CustomTypes, - domain: Json, + pub types: CustomTypes, + pub domain: Json, #[serde(rename = "primaryType")] - primary_type: String, - message: Json, + pub primary_type: String, + pub message: Json, } impl Eip712Message { diff --git a/rust/tw_evm/src/message/eip712/message_types.rs b/rust/tw_evm/src/message/eip712/message_types.rs new file mode 100644 index 00000000000..9a4971402fc --- /dev/null +++ b/rust/tw_evm/src/message/eip712/message_types.rs @@ -0,0 +1,55 @@ +// 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::message::eip712::property::{Property, PropertyType}; +use std::collections::hash_map::Entry; +use std::collections::HashMap; + +pub type CustomTypes = HashMap>; + +pub trait DeclareCustomType { + /// A custom type may also depend on other custom types. + fn declare_custom_type(&self, builder: &mut MessageTypesBuilder); +} + +#[derive(Default)] +pub struct MessageTypesBuilder { + types: CustomTypes, +} + +impl MessageTypesBuilder { + pub fn add_custom_type(&mut self, type_name: String) -> Option { + match self.types.entry(type_name) { + Entry::Vacant(entry) => { + let type_properties = entry.insert(Vec::default()); + Some(CustomTypeBuilder { type_properties }) + }, + Entry::Occupied(_) => None, + } + } + + pub fn build(self) -> CustomTypes { + self.types + } +} + +pub struct CustomTypeBuilder<'a> { + type_properties: &'a mut Vec, +} + +impl<'a> CustomTypeBuilder<'a> { + pub fn add_property(&mut self, name: &str, property_type: PropertyType) -> &mut Self { + self.type_properties.push(Property { + name: name.to_string(), + property_type: property_type.to_string(), + }); + self + } + + pub fn sort_by_names(&mut self) { + self.type_properties.sort_by(|x, y| x.name.cmp(&y.name)); + } +} diff --git a/rust/tw_evm/src/message/eip712/mod.rs b/rust/tw_evm/src/message/eip712/mod.rs index 78ee85335ad..74f7d3c885b 100644 --- a/rust/tw_evm/src/message/eip712/mod.rs +++ b/rust/tw_evm/src/message/eip712/mod.rs @@ -5,4 +5,5 @@ // file LICENSE at the root of the source code distribution tree. pub mod eip712_message; +pub mod message_types; pub mod property; diff --git a/rust/tw_evm/src/message/eip712/property.rs b/rust/tw_evm/src/message/eip712/property.rs index d61340873ad..343516ea042 100644 --- a/rust/tw_evm/src/message/eip712/property.rs +++ b/rust/tw_evm/src/message/eip712/property.rs @@ -10,10 +10,11 @@ use crate::abi::param_type::reader::Reader; use crate::abi::uint::UintBits; use crate::abi::{AbiError, AbiErrorKind, AbiResult}; use crate::message::MessageSigningError; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; +use std::fmt; use std::str::FromStr; -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct Property { pub name: String, #[serde(rename = "type")] @@ -88,6 +89,27 @@ impl TypeConstructor for PropertyType { } } +impl fmt::Display for PropertyType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PropertyType::Bool => write!(f, "bool"), + PropertyType::String => write!(f, "string"), + PropertyType::Int => write!(f, "int256"), + PropertyType::Uint => write!(f, "uint256"), + PropertyType::Address => write!(f, "address"), + PropertyType::FixBytes { len } => write!(f, "bytes{len}"), + PropertyType::Bytes => write!(f, "bytes"), + PropertyType::Custom(custom) => write!(f, "{custom}"), + PropertyType::Array(element_type) => { + write!(f, "{element_type}[]") + }, + PropertyType::FixArray { len, element_type } => { + write!(f, "{element_type}[{len}]") + }, + } + } +} + impl FromStr for PropertyType { type Err = MessageSigningError; diff --git a/rust/tw_evm/src/message/signature.rs b/rust/tw_evm/src/message/signature.rs index 847df7f4b4a..74802cd5037 100644 --- a/rust/tw_evm/src/message/signature.rs +++ b/rust/tw_evm/src/message/signature.rs @@ -17,9 +17,10 @@ use tw_number::U256; pub enum SignatureType { Standard, Legacy, - Eip155 { chain_id: u64 }, + Eip155 { chain_id: U256 }, } +#[derive(Clone)] pub struct MessageSignature { r: H256, s: H256, @@ -37,10 +38,8 @@ impl MessageSignature { SignatureType::Legacy => { legacy_replay_protection(sign.v()).map_err(|_| KeyPairError::InvalidSignature)? }, - SignatureType::Eip155 { chain_id } => { - eip155_replay_protection(U256::from(chain_id), sign.v()) - .map_err(|_| KeyPairError::InvalidSignature)? - }, + SignatureType::Eip155 { chain_id } => eip155_replay_protection(chain_id, sign.v()) + .map_err(|_| KeyPairError::InvalidSignature)?, }; Ok(MessageSignature { r: sign.r(), diff --git a/rust/tw_evm/src/modules/message_signer.rs b/rust/tw_evm/src/modules/message_signer.rs index e0f76f9e116..b15d95698ea 100644 --- a/rust/tw_evm/src/modules/message_signer.rs +++ b/rust/tw_evm/src/modules/message_signer.rs @@ -140,7 +140,7 @@ impl EthMessageSigner { }, Proto::MessageType::MessageType_eip155 | Proto::MessageType::MessageType_typed_eip155 => { - let chain_id = maybe_chain_id.unwrap_or_default().chain_id; + let chain_id = U256::from(maybe_chain_id.unwrap_or_default().chain_id); SignatureType::Eip155 { chain_id } }, } diff --git a/rust/tw_misc/src/test_utils/json.rs b/rust/tw_misc/src/test_utils/json.rs index bcab8c3f6d3..6b4969362e4 100644 --- a/rust/tw_misc/src/test_utils/json.rs +++ b/rust/tw_misc/src/test_utils/json.rs @@ -25,6 +25,13 @@ impl<'a> ToJson for Cow<'a, str> { } } +impl ToJson for String { + #[track_caller] + fn to_json(&self) -> Json { + self.as_str().to_json() + } +} + impl<'a> ToJson for &'a str { #[track_caller] fn to_json(&self) -> Json { diff --git a/rust/tw_misc/src/traits.rs b/rust/tw_misc/src/traits.rs index 36fb534853a..cf1ba9a1e80 100644 --- a/rust/tw_misc/src/traits.rs +++ b/rust/tw_misc/src/traits.rs @@ -42,3 +42,17 @@ impl IntoOption for Option { pub trait FromSlice: for<'a> TryFrom<&'a [u8]> {} impl FromSlice for T where for<'a> T: TryFrom<&'a [u8]> {} + +pub trait OptionalEmpty: Sized { + fn empty_or_some(self) -> Option; +} + +impl OptionalEmpty for String { + fn empty_or_some(self) -> Option { + if self.is_empty() { + None + } else { + Some(self) + } + } +} diff --git a/rust/tw_proto/src/lib.rs b/rust/tw_proto/src/lib.rs index 45eb41fac60..94f2db92860 100644 --- a/rust/tw_proto/src/lib.rs +++ b/rust/tw_proto/src/lib.rs @@ -52,6 +52,10 @@ where google::protobuf::Any { type_url, value } } +pub fn type_url() -> String { + format!("/{}", T::PATH) +} + /// There is no way to create an instance of the `NoMessage` enum as it doesn't has variants. pub enum NoMessage {} diff --git a/src/Cosmos/Protobuf/.clang-tidy b/src/Cosmos/Protobuf/.clang-tidy deleted file mode 100644 index 2c22f7387dd..00000000000 --- a/src/Cosmos/Protobuf/.clang-tidy +++ /dev/null @@ -1,6 +0,0 @@ ---- -InheritParentConfig: false -Checks: '-*,misc-definitions-in-headers' -CheckOptions: - - { key: HeaderFileExtensions, value: "x" } -... diff --git a/src/Cosmos/Protobuf/.gitignore b/src/Cosmos/Protobuf/.gitignore deleted file mode 100644 index c96d61208c0..00000000000 --- a/src/Cosmos/Protobuf/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.cc -*.h - diff --git a/src/Greenfield/Constants.h b/src/Greenfield/Constants.h deleted file mode 100644 index f2af62674dd..00000000000 --- a/src/Greenfield/Constants.h +++ /dev/null @@ -1,19 +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 - -#pragma once - -namespace TW::Greenfield { - -static constexpr uint64_t TIMEOUT_HEIGHT = 0; -static constexpr auto* TIMEOUT_HEIGHT_STR = "0"; -static constexpr auto* FEE_GRANTER = ""; -static constexpr auto* MSG_SEND_TYPE = "/cosmos.bank.v1beta1.MsgSend"; -static constexpr auto* MSG_TRANSFER_OUT_TYPE = "/greenfield.bridge.MsgTransferOut"; - -} // namespace TW::Greenfield diff --git a/src/Greenfield/Entry.cpp b/src/Greenfield/Entry.cpp deleted file mode 100644 index 4e1d2941b72..00000000000 --- a/src/Greenfield/Entry.cpp +++ /dev/null @@ -1,54 +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/Address.h" -#include "Signer.h" - -namespace TW::Greenfield { - -bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { - return Ethereum::Address::isValid(address); -} - -std::string Entry::normalizeAddress([[maybe_unused]] TWCoinType coin, const std::string& address) const { - // normalized with EIP55 checksum - return Ethereum::Address(address).string(); -} - -std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { - if (publicKey.type != TWPublicKeyTypeSECP256k1Extended) { - return Ethereum::Address(publicKey.extended()).string(); - } - return Ethereum::Address(publicKey).string(); -} - -Data Entry::addressToData([[maybe_unused]] TWCoinType coin, const std::string& address) const { - const auto addr = Ethereum::Address(address); - return {addr.bytes.begin(), addr.bytes.end()}; -} - -void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signTemplate(dataIn, dataOut); -} - -TW::Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { - return txCompilerTemplate( - txInputData, [](const auto& input, auto& output) { - output = Signer::preImageHashes(input); - }); -} - -void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { - dataOut = txCompilerSingleTemplate( - txInputData, signatures, publicKeys, - [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { - output = Signer::compile(input, publicKey, signature); - }); -} - -} // namespace TW::Greenfield diff --git a/src/Greenfield/Entry.h b/src/Greenfield/Entry.h index 7613878d0ca..c54d843fc99 100644 --- a/src/Greenfield/Entry.h +++ b/src/Greenfield/Entry.h @@ -6,22 +6,13 @@ #pragma once -#include "../CoinEntry.h" +#include "rust/RustCoinEntry.h" namespace TW::Greenfield { /// Entry point for implementation of Greenfield 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 normalizeAddress(TWCoinType coin, const std::string& address) const final; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - Data addressToData(TWCoinType coin, const std::string& address) const final; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - - 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; +struct Entry final : public Rust::RustCoinEntry { }; } // namespace TW::Greenfield diff --git a/src/Greenfield/ProtobufSerialization.cpp b/src/Greenfield/ProtobufSerialization.cpp deleted file mode 100644 index 45fdd59647f..00000000000 --- a/src/Greenfield/ProtobufSerialization.cpp +++ /dev/null @@ -1,147 +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 "Base64.h" -#include "Constants.h" -#include "Cosmos/Protobuf/bank_tx.pb.h" -#include "Cosmos/Protobuf/greenfield_ethsecp256k1.pb.h" -#include "Cosmos/Protobuf/greenfield_tx.pb.h" -#include "Cosmos/Protobuf/tx.pb.h" -#include "PrivateKey.h" - -namespace TW::Greenfield { - -static constexpr auto ProtobufAnyNamespacePrefix = ""; // to override default 'type.googleapis.com' - -using Any = google::protobuf::Any; - -static std::string broadcastMode(Proto::BroadcastMode mode) { - switch (mode) { - case Proto::BroadcastMode::ASYNC: - return "BROADCAST_MODE_ASYNC"; - case Proto::BroadcastMode::SYNC: - default: return "BROADCAST_MODE_SYNC"; - } -} - -static json broadcastJSON(std::string data, Proto::BroadcastMode mode) { - return { - {"tx_bytes", data}, - {"mode", broadcastMode(mode)} - }; -} - -static 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; -} - -static SigningResult convertMessage(const Proto::Message& msg) { - Any any; - - switch (msg.message_oneof_case()) { - case Proto::Message::kSendCoinsMessage: { - 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); - break; - } - case Proto::Message::kBridgeTransferOut: { - const auto& transferOut = msg.bridge_transfer_out(); - - auto msgTransferOut = greenfield::bridge::MsgTransferOut(); - msgTransferOut.set_from(transferOut.from_address()); - msgTransferOut.set_to(transferOut.to_address()); - *msgTransferOut.mutable_amount() = convertCoin(transferOut.amount()); - - any.PackFrom(msgTransferOut, ProtobufAnyNamespacePrefix); - break; - } - default: { - return SigningResult::failure(Common::Proto::SigningError::Error_invalid_params); - } - } - - return SigningResult::success(std::move(any)); -} - -SigningResult ProtobufSerialization::encodeTxProtobuf(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature) { - const auto txBodyResult = encodeTxBody(input); - const auto serializedAuthInfo = encodeAuthInfo(input, publicKey); - - if (txBodyResult.isFailure()) { - return SigningResult::failure(txBodyResult.error()); - } - const auto serializedTxBody = txBodyResult.payload(); - - 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()); - - const auto txRawData = data(txRaw.SerializeAsString()); - auto txJson = broadcastJSON(Base64::encode(txRawData), input.mode()); - - return SigningResult::success(std::move(txJson)); -} - -SigningResult ProtobufSerialization::encodeTxBody(const Proto::SigningInput& input) { - cosmos::tx::v1beta1::TxBody txBody; - - // At this moment, we support only one message. - if (input.messages_size() != 1) { - return SigningResult::failure(Common::Proto::SigningError::Error_invalid_params); - } - const auto msgAny = convertMessage(input.messages(0)); - if (msgAny.isFailure()) { - return SigningResult::failure(msgAny.error()); - } - - *txBody.add_messages() = msgAny.payload(); - txBody.set_memo(input.memo()); - txBody.set_timeout_height(TIMEOUT_HEIGHT); - - return SigningResult::success(data(txBody.SerializeAsString())); -} - -Data ProtobufSerialization::encodeAuthInfo(const Proto::SigningInput& input, const PublicKey& publicKey) { - // AuthInfo - auto authInfo = cosmos::tx::v1beta1::AuthInfo(); - auto* signerInfo = authInfo.add_signer_infos(); - - // At this moment, we support Eip712 signing mode only. - signerInfo->mutable_mode_info()->mutable_single()->set_mode(cosmos::signing::v1beta1::SIGN_MODE_EIP_712); - signerInfo->set_sequence(input.sequence()); - - // `cosmos::crypto::eth::ethsecp256k1` is used only with `SIGN_MODE_EIP_712`. - auto pubKey = cosmos::crypto::eth::ethsecp256k1::PubKey(); - pubKey.set_key(publicKey.bytes.data(), publicKey.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 data(authInfo.SerializeAsString()); -} - -} // namespace TW::Greenfield diff --git a/src/Greenfield/ProtobufSerialization.h b/src/Greenfield/ProtobufSerialization.h deleted file mode 100644 index 63cae96589b..00000000000 --- a/src/Greenfield/ProtobufSerialization.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 "proto/Greenfield.pb.h" -#include "proto/Common.pb.h" -#include "PublicKey.h" -#include "Result.h" - -#include - -namespace TW::Greenfield { - -using json = nlohmann::json; -template -using SigningResult = Result; - -struct ProtobufSerialization { - // Returns a JSON `{ "mode": "$MODE", "tx_bytes": "$TX_BASE64" }`, - // where `TX_BASE64` is a serialized Protobuf packaged encoded into base64. - static SigningResult encodeTxProtobuf(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature); - - // Returns a serialized `cosmos::TxBody` Protobuf package. - static SigningResult encodeTxBody(const Proto::SigningInput& input); - - // Returns a serialized `cosmos::AuthInfo` Protobuf package. - static Data encodeAuthInfo(const Proto::SigningInput& input, const PublicKey& publicKey); -}; - -} // namespace TW::Greenfield diff --git a/src/Greenfield/Signer.cpp b/src/Greenfield/Signer.cpp deleted file mode 100644 index 987fdfe1fd9..00000000000 --- a/src/Greenfield/Signer.cpp +++ /dev/null @@ -1,84 +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 "ProtobufSerialization.h" -#include "PublicKey.h" -#include "SignerEip712.h" - -namespace TW::Greenfield { - -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) { - Proto::SigningOutput output; - - const auto signatureResult = SignerEip712::sign(input); - if (signatureResult.isFailure()) { - output.set_error(signatureResult.error()); - output.set_error_message(Common::Proto::SigningError_Name(signatureResult.error())); - return output; - } - - const auto signature = signatureResult.payload(); - - PrivateKey privateKey(data(input.private_key())); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - - // At this moment, we support Protobuf serialization only. - auto jsonResult = ProtobufSerialization::encodeTxProtobuf(input, publicKey, signature); - if (jsonResult.isFailure()) { - output.set_error(jsonResult.error()); - output.set_error_message(Common::Proto::SigningError_Name(jsonResult.error())); - return output; - } - - output.set_signature(signature.data(), signature.size()); - output.set_serialized(jsonResult.payload().dump()); - return output; -} - -TxCompiler::Proto::PreSigningOutput Signer::preImageHashes(const Proto::SigningInput& input) { - TxCompiler::Proto::PreSigningOutput output; - - // At this moment, we support Eip712 signing mode only. - const auto preImageResult = SignerEip712::preImageHash(input); - if (preImageResult.isFailure()) { - output.set_error(preImageResult.error()); - output.set_error_message(Common::Proto::SigningError_Name(preImageResult.error())); - return output; - } - - const auto preImage = preImageResult.payload(); - auto preImageData = data(preImage.typedData.dump()); - - output.set_data_hash(preImage.typedDataHash.data(), preImage.typedDataHash.size()); - output.set_data(preImageData.data(), preImageData.size()); - return output; -} - -Proto::SigningOutput Signer::compile(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature) { - Proto::SigningOutput output; - - Data sign = signature; - if (input.signing_mode() == Proto::SigningMode::Eip712) { - // Prepare the signature to be compiled. - SignerEip712::prepareSignature(sign); - } - - // At this moment, we support Protobuf serialization only. - auto jsonResult = ProtobufSerialization::encodeTxProtobuf(input, publicKey, sign); - if (jsonResult.isFailure()) { - output.set_error(jsonResult.error()); - output.set_error_message(Common::Proto::SigningError_Name(jsonResult.error())); - return output; - } - - output.set_signature(sign.data(), sign.size()); - output.set_serialized(jsonResult.payload().dump()); - return output; -} - -} // namespace TW::Greenfield diff --git a/src/Greenfield/Signer.h b/src/Greenfield/Signer.h deleted file mode 100644 index 982705e4550..00000000000 --- a/src/Greenfield/Signer.h +++ /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. - -#pragma once - -#include "Data.h" -#include "PrivateKey.h" -#include "proto/Greenfield.pb.h" -#include "proto/TransactionCompiler.pb.h" - -namespace TW::Greenfield { - -/// Helper class that performs Greenfield transaction signing. -class Signer { -public: - /// Hide default constructor - Signer() = delete; - - /// Signs a Proto::SigningInput transaction - static Proto::SigningOutput sign(const Proto::SigningInput& input); - - /// Collect pre-image hashes to be signed - static TxCompiler::Proto::PreSigningOutput preImageHashes(const Proto::SigningInput& input); - - static Proto::SigningOutput compile(const Proto::SigningInput& input, const PublicKey& publicKey, const Data& signature); -}; - -} // namespace TW::Greenfield diff --git a/src/Greenfield/SignerEip712.cpp b/src/Greenfield/SignerEip712.cpp deleted file mode 100644 index ce803cda6f6..00000000000 --- a/src/Greenfield/SignerEip712.cpp +++ /dev/null @@ -1,266 +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 "SignerEip712.h" - -#include "Constants.h" -#include "Ethereum/MessageSigner.h" -#include "HexCoding.h" - -#include - -namespace TW::Greenfield { - -namespace internal::types { - -using TypesMap = std::map>; - -json namedParam(const char *name, const char *type) { - return json { - {"name", name}, - {"type", type} - }; -} - -// https://github.com/bnb-chain/greenfield-cosmos-sdk/blob/b48770f5e210b28536f92734b6228913666d4da1/x/auth/tx/eip712.go#L119-L160 -json makeEip712Types(const TypesMap& msgTypes) { - auto types = json { - {"EIP712Domain", json::array({ - namedParam("chainId", "uint256"), - namedParam("name", "string"), - namedParam("salt", "string"), - namedParam("verifyingContract", "string"), - namedParam("version", "string") - })}, - {"Coin", json::array({ - namedParam("amount", "uint256"), - namedParam("denom", "string") - })}, - {"Fee", json::array({ - namedParam("amount", "Coin[]"), - namedParam("gas_limit", "uint256"), - namedParam("granter", "string"), - namedParam("payer", "string") - })}, - }; - - for (const auto& [msgTypeName, msgType] : msgTypes) { - types[msgTypeName] = msgType; - } - return types; -} - -// `TypeMsg1Amount` and `Msg1` type names are chosen automatically at the function: -// https://github.com/bnb-chain/greenfield-cosmos-sdk/blob/master/x/auth/tx/eip712.go#L90 -// Please note that all parameters repeat the same scheme as `cosmos.bank.v1beta1.MsgSend`. -// -// Use `https://dcellar.io/` with MetaMask to get proper names of types and -json msgSendTypes() { - // `MsgSend` specific types. - TypesMap msgTypes = { - // `TypeMsg1Amount` type represents `cosmos.bank.v1beta1.MsgSend.amount`. - {"TypeMsg1Amount", json::array({ - namedParam("amount", "string"), - namedParam("denom", "string"), - })}, - {"Msg1", json::array({ - namedParam("amount", "TypeMsg1Amount[]"), - namedParam("from_address", "string"), - namedParam("to_address", "string"), - namedParam("type", "string") - })}, - {"Tx", json::array({ - namedParam("account_number", "uint256"), - namedParam("chain_id", "uint256"), - namedParam("fee", "Fee"), - namedParam("memo", "string"), - namedParam("msg1", "Msg1"), - namedParam("sequence", "uint256"), - namedParam("timeout_height", "uint256") - })} - }; - - return makeEip712Types(msgTypes); -} - -// `TypeMsg1Amount` and `Msg1` type names are chosen automatically at the function: -// https://github.com/bnb-chain/greenfield-cosmos-sdk/blob/master/x/auth/tx/eip712.go#L90 -// Please note that all parameters repeat the same scheme as `greenfield.bridge.MsgTransferOut`. -// -// Use `https://dcellar.io/` with MetaMask to get proper names of types and -json msgTransferOutTypes() { - // `MsgSend` specific types. - TypesMap msgTypes = { - // `TypeMsg1Amount` type represents `cosmos.bank.v1beta1.MsgSend.amount`. - {"TypeMsg1Amount", json::array({ - namedParam("amount", "string"), - namedParam("denom", "string"), - })}, - {"Msg1", json::array({ - namedParam("amount", "TypeMsg1Amount"), - namedParam("from", "string"), - namedParam("to", "string"), - namedParam("type", "string") - })}, - {"Tx", json::array({ - namedParam("account_number", "uint256"), - namedParam("chain_id", "uint256"), - namedParam("fee", "Fee"), - namedParam("memo", "string"), - namedParam("msg1", "Msg1"), - namedParam("sequence", "uint256"), - namedParam("timeout_height", "uint256") - })} - }; - - return makeEip712Types(msgTypes); -} - -} // namespace internal::types - -json feeToJsonData(const Proto::SigningInput& input, const std::string& feePayer) { - auto feeAmounts = json::array(); - for (const auto& fAmount : input.fee().amounts()) { - feeAmounts.push_back(json{ - {"amount", fAmount.amount()}, - {"denom", fAmount.denom()} - }); - } - - return json{ - {"amount", feeAmounts}, - {"gas_limit", std::to_string(input.fee().gas())}, - {"granter", FEE_GRANTER}, - {"payer", feePayer}, - }; -} - -// Returns a JSON data of the `EIP712Domain` type. -// https://github.com/bnb-chain/greenfield-cosmos-sdk/blob/b48770f5e210b28536f92734b6228913666d4da1/x/auth/tx/eip712.go#L35-L40 -json domainDataJson(const std::string& chainId) { - return json{ - {"name", "Greenfield Tx"}, - {"version", "1.0.0"}, - {"chainId", chainId}, - {"verifyingContract", "greenfield"}, - {"salt", "0"} - }; -} - -// Returns a JSON data of the `EIP712Domain` type using `MsgSend` transaction. -json SignerEip712::wrapMsgSendToTypedData(const Proto::SigningInput& input, const Proto::Message_Send& msgSendProto) { - auto sendAmounts = json::array(); - for (const auto& sAmount : msgSendProto.amounts()) { - sendAmounts.push_back(json{ - {"amount", sAmount.amount()}, - {"denom", sAmount.denom()}, - }); - } - - std::string typePrefix = MSG_SEND_TYPE; - if (!msgSendProto.type_prefix().empty()) { - typePrefix = msgSendProto.type_prefix(); - } - - return json{ - {"types", internal::types::msgSendTypes()}, - {"primaryType", "Tx"}, - {"domain", domainDataJson(input.eth_chain_id())}, - {"message", json{ - {"account_number", std::to_string(input.account_number())}, - {"chain_id", input.eth_chain_id()}, - {"fee", feeToJsonData(input, msgSendProto.from_address())}, - {"memo", input.memo()}, - {"msg1", json{ - {"amount", sendAmounts}, - {"from_address", msgSendProto.from_address()}, - {"to_address", msgSendProto.to_address()}, - {"type", MSG_SEND_TYPE} - }}, - {"sequence", std::to_string(input.sequence())}, - {"timeout_height", TIMEOUT_HEIGHT_STR} - }} - }; -} - -// Returns a JSON data of the `EIP712Domain` type using `MsgSend` transaction. -json SignerEip712::wrapMsgTransferOutToTypedData(const Proto::SigningInput& input, const Proto::Message_BridgeTransferOut& msgTransferOut) { - std::string typePrefix = MSG_TRANSFER_OUT_TYPE; - if (!msgTransferOut.type_prefix().empty()) { - typePrefix = msgTransferOut.type_prefix(); - } - - return json{ - {"types", internal::types::msgTransferOutTypes()}, - {"primaryType", "Tx"}, - {"domain", domainDataJson(input.eth_chain_id())}, - {"message", json{ - {"account_number", std::to_string(input.account_number())}, - {"chain_id", input.eth_chain_id()}, - {"fee", feeToJsonData(input, msgTransferOut.from_address())}, - {"memo", input.memo()}, - {"msg1", json{ - {"amount", json{ - {"amount", msgTransferOut.amount().amount()}, - {"denom", msgTransferOut.amount().denom()} - }}, - {"from", msgTransferOut.from_address()}, - {"to", msgTransferOut.to_address()}, - {"type", typePrefix} - }}, - {"sequence", std::to_string(input.sequence())}, - {"timeout_height", TIMEOUT_HEIGHT_STR} - }} - }; -} - -SigningResult SignerEip712::wrapTxToTypedData(const Proto::SigningInput& input) { - if (input.messages_size() != 1) { - return SigningResult::failure(Common::Proto::SigningError::Error_invalid_params); - } - - switch(input.messages(0).message_oneof_case()) { - case Proto::Message::kBridgeTransferOut: { - const auto &msgTransferOut = input.messages(0).bridge_transfer_out(); - return SigningResult::success(wrapMsgTransferOutToTypedData(input, msgTransferOut)); - } - case Proto::Message::kSendCoinsMessage: - default: { - const auto& msgSendProto = input.messages(0).send_coins_message(); - return SigningResult::success(wrapMsgSendToTypedData(input, msgSendProto)); - } - } -} - -SigningResult SignerEip712::preImageHash(const Proto::SigningInput& input) { - const auto txTypedDataResult = wrapTxToTypedData(input); - if (txTypedDataResult.isFailure()) { - return SigningResult::failure(txTypedDataResult.error()); - } - - const auto txTypedData = txTypedDataResult.payload(); - const auto txTypedDataHash = Ethereum::MessageSigner::typedDataPreImageHash(txTypedData.dump()); - return SigningResult::success({.typedData = txTypedData, .typedDataHash = txTypedDataHash}); -} - -SigningResult SignerEip712::sign(const Proto::SigningInput& input) { - const PrivateKey privateKey(data(input.private_key())); - const auto txTypedDataResult = wrapTxToTypedData(input); - if (txTypedDataResult.isFailure()) { - return SigningResult::failure(txTypedDataResult.error()); - } - const auto txTypedData = txTypedDataResult.payload().dump(); - const auto chainId = std::stoull(input.eth_chain_id()); - - const auto signatureStr = Ethereum::MessageSigner::signTypedData(privateKey, txTypedData, Ethereum::MessageType::Legacy, chainId); - return SigningResult::success(parse_hex(signatureStr)); -} - -void SignerEip712::prepareSignature(Data& signature) { - Ethereum::MessageSigner::prepareSignature(signature, Ethereum::MessageType::Legacy); -} - -} // namespace TW::Greenfield diff --git a/src/Greenfield/SignerEip712.h b/src/Greenfield/SignerEip712.h deleted file mode 100644 index f36444b3aa2..00000000000 --- a/src/Greenfield/SignerEip712.h +++ /dev/null @@ -1,49 +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/Greenfield.pb.h" -#include "Result.h" - -#include - -namespace TW::Greenfield { - -using json = nlohmann::json; -template -using SigningResult = Result; - -struct Eip712PreImage { - json typedData; - Data typedDataHash; -}; - -struct SignerEip712 { - ~SignerEip712() = delete; - - /// Signs a Proto::SigningInput transaction as EIP712. - /// Returns an rsv signature. - static SigningResult sign(const Proto::SigningInput& input); - - /// Returns a pre-image hash that needs to be signed. - static SigningResult preImageHash(const Proto::SigningInput& input); - - /// Packs the Tx input in a EIP712 object. - static SigningResult wrapTxToTypedData(const Proto::SigningInput& input); - - /// Packs the `MsgSend` Tx input in a EIP712 object. - static json wrapMsgSendToTypedData(const Proto::SigningInput& input, const Proto::Message_Send& msgSendProto); - - /// Packs the `MsgTransferOut` Tx input in a EIP712 object. - static json wrapMsgTransferOutToTypedData(const Proto::SigningInput& input, const Proto::Message_BridgeTransferOut& msgTransferOut); - - /// Prepares the given `signature` to make it Ethereum compatible. - static void prepareSignature(Data& signature); -}; - -} // namespace TW::Greenfield diff --git a/tests/chains/Cosmos/SignerTests.cpp b/tests/chains/Cosmos/SignerTests.cpp index 20ec6a027a7..54b5fbb9344 100644 --- a/tests/chains/Cosmos/SignerTests.cpp +++ b/tests/chains/Cosmos/SignerTests.cpp @@ -11,8 +11,6 @@ #include "Cosmos/Address.h" #include "TrustWalletCore/TWAnySigner.h" #include "TestUtilities.h" -#include "Cosmos/Protobuf/bank_tx.pb.h" -#include "Cosmos/Protobuf/coin.pb.h" #include #include @@ -291,21 +289,11 @@ TEST(CosmosSigner, SignDirect1) { } TEST(CosmosSigner, SignDirect_0a90010a) { + // MsgSend: + // from: cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6 + // to: cosmos1qypqxpq9qcrsszg2pvxq6rs0zqg3yyc5lzv7xu + // amount: 1234567 ucosm const auto bodyBytes = parse_hex("0a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d120731323334353637"); - { // verify contents of body - auto msgSend = cosmos::bank::v1beta1::MsgSend(); - msgSend.set_from_address("cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6"); - msgSend.set_to_address("cosmos1qypqxpq9qcrsszg2pvxq6rs0zqg3yyc5lzv7xu"); - auto amount = msgSend.add_amount(); - amount->set_denom("ucosm"); - amount->set_amount("1234567"); - const auto msgSendSer = msgSend.SerializeAsString(); - const auto bodyBytes1 = data(msgSendSer); - ASSERT_EQ(hex(bodyBytes1), "0a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d120731323334353637"); - const auto prefix = "/cosmos.bank.v1beta1.MsgSend"; - const auto bodyBytes2 = parse_hex("0a90010a1c" + hex(data(prefix)) + "1270" + hex(bodyBytes1)); - ASSERT_EQ(hex(bodyBytes2), hex(bodyBytes)); - } auto input = Proto::SigningInput(); input.set_signing_mode(Proto::Protobuf); diff --git a/tests/chains/Greenfield/SignerTests.cpp b/tests/chains/Greenfield/SignerTests.cpp deleted file mode 100644 index 0cf63636e34..00000000000 --- a/tests/chains/Greenfield/SignerTests.cpp +++ /dev/null @@ -1,541 +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 "Greenfield/Signer.h" -#include "Greenfield/SignerEip712.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include "PublicKey.h" - -#include - -namespace TW::Greenfield::tests { - -TEST(GreenfieldSigner, SignerEip712Send) { - Proto::SigningInput input; - input.set_signing_mode(Proto::Eip712); - input.set_account_number(15560); - input.set_cosmos_chain_id("greenfield_5600-1"); - input.set_eth_chain_id("5600"); - input.set_sequence(2); - - auto& msg = *input.add_messages(); - auto& msgSend = *msg.mutable_send_coins_message(); - msgSend.set_from_address("0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1"); - msgSend.set_to_address("0x280b27f3676db1C4475EE10F75D510Eb527fd155"); - auto amountOfTx = msgSend.add_amounts(); - amountOfTx->set_denom("BNB"); - amountOfTx->set_amount("1000000000000000"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("BNB"); - amountOfFee->set_amount("2000000000000000"); - - auto typedData = SignerEip712::wrapTxToTypedData(input).payload(); - auto expectedJson = json::parse(R"( -{ - "types": { - "Coin": [ - { - "name": "amount", - "type": "uint256" - }, - { - "name": "denom", - "type": "string" - } - ], - "EIP712Domain": [ - { - "name": "chainId", - "type": "uint256" - }, - { - "name": "name", - "type": "string" - }, - { - "name": "salt", - "type": "string" - }, - { - "name": "verifyingContract", - "type": "string" - }, - { - "name": "version", - "type": "string" - } - ], - "Fee": [ - { - "name": "amount", - "type": "Coin[]" - }, - { - "name": "gas_limit", - "type": "uint256" - }, - { - "name": "granter", - "type": "string" - }, - { - "name": "payer", - "type": "string" - } - ], - "Msg1": [ - { - "name": "amount", - "type": "TypeMsg1Amount[]" - }, - { - "name": "from_address", - "type": "string" - }, - { - "name": "to_address", - "type": "string" - }, - { - "name": "type", - "type": "string" - } - ], - "Tx": [ - { - "name": "account_number", - "type": "uint256" - }, - { - "name": "chain_id", - "type": "uint256" - }, - { - "name": "fee", - "type": "Fee" - }, - { - "name": "memo", - "type": "string" - }, - { - "name": "msg1", - "type": "Msg1" - }, - { - "name": "sequence", - "type": "uint256" - }, - { - "name": "timeout_height", - "type": "uint256" - } - ], - "TypeMsg1Amount": [ - { - "name": "amount", - "type": "string" - }, - { - "name": "denom", - "type": "string" - } - ] - }, - "primaryType": "Tx", - "domain": { - "name": "Greenfield Tx", - "version": "1.0.0", - "chainId": "5600", - "verifyingContract": "greenfield", - "salt": "0" - }, - "message": { - "account_number": "15560", - "chain_id": "5600", - "fee": { - "amount": [ - { - "amount": "2000000000000000", - "denom": "BNB" - } - ], - "gas_limit": "200000", - "granter": "", - "payer": "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1" - }, - "memo": "", - "msg1": { - "amount": [ - { - "amount": "1000000000000000", - "denom": "BNB" - } - ], - "from_address": "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1", - "to_address": "0x280b27f3676db1C4475EE10F75D510Eb527fd155", - "type": "/cosmos.bank.v1beta1.MsgSend" - }, - "sequence": "2", - "timeout_height": "0" - } -} - )"); - EXPECT_EQ(typedData, expectedJson); - - auto expectedPreHash = "b8c62654582ca96b37ca94966199682bf70ed934e740d2f874ff54675a0ac344"; - auto preHash = SignerEip712::preImageHash(input).payload(); - EXPECT_EQ(hex(preHash.typedDataHash), expectedPreHash); - - auto privateKey = parse_hex("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto signature = SignerEip712::sign(input).payload(); - auto expectedSignature = "cb3a4684a991014a387a04a85b59227ebb79567c2025addcb296b4ca856e9f810d3b526f2a0d0fad6ad1b126b3b9516f8b3be020a7cca9c03ce3cf47f4199b6d1b"; - EXPECT_EQ(hex(signature), expectedSignature); -} - -TEST(GreenfieldSigner, SignerEip712TransferOut) { - Proto::SigningInput input; - input.set_signing_mode(Proto::Eip712); - input.set_account_number(15560); - input.set_cosmos_chain_id("greenfield_5600-1"); - input.set_eth_chain_id("5600"); - input.set_sequence(2); - - auto& msg = *input.add_messages(); - auto& msgSend = *msg.mutable_bridge_transfer_out(); - msgSend.set_from_address("0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1"); - msgSend.set_to_address("0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1"); - msgSend.mutable_amount()->set_denom("BNB"); - msgSend.mutable_amount()->set_amount("1000000000000000"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("BNB"); - amountOfFee->set_amount("2000000000000000"); - - auto typedData = SignerEip712::wrapTxToTypedData(input).payload(); - auto expectedJson = json::parse(R"( -{ - "types": { - "Coin": [ - { - "name": "amount", - "type": "uint256" - }, - { - "name": "denom", - "type": "string" - } - ], - "EIP712Domain": [ - { - "name": "chainId", - "type": "uint256" - }, - { - "name": "name", - "type": "string" - }, - { - "name": "salt", - "type": "string" - }, - { - "name": "verifyingContract", - "type": "string" - }, - { - "name": "version", - "type": "string" - } - ], - "Fee": [ - { - "name": "amount", - "type": "Coin[]" - }, - { - "name": "gas_limit", - "type": "uint256" - }, - { - "name": "granter", - "type": "string" - }, - { - "name": "payer", - "type": "string" - } - ], - "Msg1": [ - { - "name": "amount", - "type": "TypeMsg1Amount" - }, - { - "name": "from", - "type": "string" - }, - { - "name": "to", - "type": "string" - }, - { - "name": "type", - "type": "string" - } - ], - "Tx": [ - { - "name": "account_number", - "type": "uint256" - }, - { - "name": "chain_id", - "type": "uint256" - }, - { - "name": "fee", - "type": "Fee" - }, - { - "name": "memo", - "type": "string" - }, - { - "name": "msg1", - "type": "Msg1" - }, - { - "name": "sequence", - "type": "uint256" - }, - { - "name": "timeout_height", - "type": "uint256" - } - ], - "TypeMsg1Amount": [ - { - "name": "amount", - "type": "string" - }, - { - "name": "denom", - "type": "string" - } - ] - }, - "primaryType": "Tx", - "domain": { - "name": "Greenfield Tx", - "version": "1.0.0", - "chainId": "5600", - "verifyingContract": "greenfield", - "salt": "0" - }, - "message": { - "account_number": "15560", - "chain_id": "5600", - "fee": { - "amount": [ - { - "amount": "2000000000000000", - "denom": "BNB" - } - ], - "gas_limit": "200000", - "granter": "", - "payer": "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1" - }, - "memo": "", - "msg1": { - "amount": { - "amount": "1000000000000000", - "denom": "BNB" - }, - "from": "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1", - "to": "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1", - "type": "/greenfield.bridge.MsgTransferOut" - }, - "sequence": "2", - "timeout_height": "0" - } -} - )"); - EXPECT_EQ(typedData, expectedJson); - - auto expectedPreHash = "ea7731461041f5f652ab424bb767c670e484cfe1f4a85179deba8a6596873af4"; - auto preHash = SignerEip712::preImageHash(input).payload(); - EXPECT_EQ(hex(preHash.typedDataHash), expectedPreHash); - - auto privateKey = parse_hex("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto signature = SignerEip712::sign(input).payload(); - auto expectedSignature = "c345fe0deb4fd93da5e808f6bd8aac3fb9de70fea2774e4657c37b02143135e37a02d53e8696edaede4a3e2e624eebd3261f43e02972812c11b356e236c834141c"; - EXPECT_EQ(hex(signature), expectedSignature); -} - -TEST(GreenfieldSigner, SignMsgSend9F895C) { - // Successfully broadcasted https://greenfieldscan.com/tx/0x9f895cf2dd64fb1f428cefcf2a6585a813c3540fc9fe1ef42db1da2cb1df55ab - - Proto::SigningInput input; - input.set_signing_mode(Proto::Eip712); - input.set_account_number(15560); - input.set_cosmos_chain_id("greenfield_5600-1"); - input.set_eth_chain_id("5600"); - input.set_sequence(2); - input.set_mode(Proto::BroadcastMode::SYNC); - - auto& msg = *input.add_messages(); - auto& msgSend = *msg.mutable_send_coins_message(); - msgSend.set_from_address("0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1"); - msgSend.set_to_address("0x280b27f3676db1C4475EE10F75D510Eb527fd155"); - auto amountOfTx = msgSend.add_amounts(); - amountOfTx->set_denom("BNB"); - amountOfTx->set_amount("1000000000000000"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(200000); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("BNB"); - amountOfFee->set_amount("2000000000000000"); - - auto privateKey = parse_hex("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input); - EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); - EXPECT_EQ(hex(output.signature()), "cb3a4684a991014a387a04a85b59227ebb79567c2025addcb296b4ca856e9f810d3b526f2a0d0fad6ad1b126b3b9516f8b3be020a7cca9c03ce3cf47f4199b6d1b"); - EXPECT_EQ(output.serialized(), R"({"mode":"BROADCAST_MODE_SYNC","tx_bytes":"CpQBCpEBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnEKKjB4OWQxZDk3YURGY2QzMjRCYmQ2MDNEMzg3MkJENzhlMDQwOTg1MTBiMRIqMHgyODBiMjdmMzY3NmRiMUM0NDc1RUUxMEY3NUQ1MTBFYjUyN2ZkMTU1GhcKA0JOQhIQMTAwMDAwMDAwMDAwMDAwMBJ5ClgKTQomL2Nvc21vcy5jcnlwdG8uZXRoLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohAnnvNAZNoQ2wRjxwSAYWugIHA+w6RQJt73vr0ggvXW/IEgUKAwjIBRgCEh0KFwoDQk5CEhAyMDAwMDAwMDAwMDAwMDAwEMCaDBpByzpGhKmRAUo4egSoW1kifrt5VnwgJa3cspa0yoVun4ENO1JvKg0PrWrRsSazuVFvizvgIKfMqcA8489H9BmbbRs="})"); -} - -TEST(GreenfieldSigner, SignMsgSendB798AB) { - // Successfully broadcasted https://greenfieldscan.com/tx/B798AB548B74B9B410F9464CA2B29C6AFEC3B4F45050338FC34F9DFC057C4D2A - - Proto::SigningInput input; - input.set_signing_mode(Proto::Eip712); - input.set_account_number(15560); - input.set_cosmos_chain_id("greenfield_5600-1"); - input.set_eth_chain_id("5600"); - input.set_sequence(3); - input.set_mode(Proto::BroadcastMode::SYNC); - - auto& msg = *input.add_messages(); - auto& msgSend = *msg.mutable_send_coins_message(); - msgSend.set_from_address("0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1"); - msgSend.set_to_address("0x280b27f3676db1C4475EE10F75D510Eb527fd155"); - auto amountOfTx = msgSend.add_amounts(); - amountOfTx->set_denom("BNB"); - amountOfTx->set_amount("5000000000000000"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(1200); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("BNB"); - amountOfFee->set_amount("6000000000000"); - - auto privateKey = parse_hex("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input); - EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); - EXPECT_EQ(hex(output.signature()), "37bc208e75cb16417f53bec4e6092f42da95aca22413ed4da9af41b64fd59fec0b35da17e54a31ceeda083e1185ef23df8d9805e8886b9b892fce570587b5a951b"); - EXPECT_EQ(output.serialized(), R"({"mode":"BROADCAST_MODE_SYNC","tx_bytes":"CpQBCpEBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnEKKjB4OWQxZDk3YURGY2QzMjRCYmQ2MDNEMzg3MkJENzhlMDQwOTg1MTBiMRIqMHgyODBiMjdmMzY3NmRiMUM0NDc1RUUxMEY3NUQ1MTBFYjUyN2ZkMTU1GhcKA0JOQhIQNTAwMDAwMDAwMDAwMDAwMBJ1ClgKTQomL2Nvc21vcy5jcnlwdG8uZXRoLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohAnnvNAZNoQ2wRjxwSAYWugIHA+w6RQJt73vr0ggvXW/IEgUKAwjIBRgDEhkKFAoDQk5CEg02MDAwMDAwMDAwMDAwELAJGkE3vCCOdcsWQX9TvsTmCS9C2pWsoiQT7U2pr0G2T9Wf7As12hflSjHO7aCD4Rhe8j342YBeiIa5uJL85XBYe1qVGw=="})"); -} - -TEST(GreenfieldSigner, SignMsgTransferOut) { - // Successfully broadcasted Greenfield: https://greenfieldscan.com/tx/38C29C530A74946CFD22EE07DA642F5EF9399BC9AEA59EC56A9B5BE16DE16CE7 - // BSC (parent transaction): https://testnet.bscscan.com/tx/0x7f73c8a362e14e58cb5e0ec17616afc50eff7aa398db472383a6d017c8a5861a - - Proto::SigningInput input; - input.set_signing_mode(Proto::Eip712); - input.set_account_number(15560); - input.set_cosmos_chain_id("greenfield_5600-1"); - input.set_eth_chain_id("5600"); - input.set_sequence(7); - input.set_mode(Proto::BroadcastMode::SYNC); - - auto& msg = *input.add_messages(); - auto& msgTransferOut = *msg.mutable_bridge_transfer_out(); - msgTransferOut.set_from_address("0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1"); - msgTransferOut.set_to_address("0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1"); - auto amountOfTx = msgTransferOut.mutable_amount(); - amountOfTx->set_denom("BNB"); - amountOfTx->set_amount("5670000000000000"); - - auto& fee = *input.mutable_fee(); - fee.set_gas(1200); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("BNB"); - amountOfFee->set_amount("6000000000000"); - - auto privateKey = parse_hex("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input); - EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); - EXPECT_EQ(hex(output.signature()), "ce030c1ca1bbcd14cbf337b1078fbc09a199b15665e1aadf9f6bba0b6c90489729f0b3481f3a5e6062fa5f90e444c34b67e894cc5bb9f1d213c80ce160421f381c"); - EXPECT_EQ(output.serialized(), R"({"mode":"BROADCAST_MODE_SYNC","tx_bytes":"CpkBCpYBCiEvZ3JlZW5maWVsZC5icmlkZ2UuTXNnVHJhbnNmZXJPdXQScQoqMHg5ZDFkOTdhREZjZDMyNEJiZDYwM0QzODcyQkQ3OGUwNDA5ODUxMGIxEioweDlkMWQ5N2FERmNkMzI0QmJkNjAzRDM4NzJCRDc4ZTA0MDk4NTEwYjEaFwoDQk5CEhA1NjcwMDAwMDAwMDAwMDAwEnUKWApNCiYvY29zbW9zLmNyeXB0by5ldGguZXRoc2VjcDI1NmsxLlB1YktleRIjCiECee80Bk2hDbBGPHBIBha6AgcD7DpFAm3ve+vSCC9db8gSBQoDCMgFGAcSGQoUCgNCTkISDTYwMDAwMDAwMDAwMDAQsAkaQc4DDByhu80Uy/M3sQePvAmhmbFWZeGq359rugtskEiXKfCzSB86XmBi+l+Q5ETDS2folMxbufHSE8gM4WBCHzgc"})"); -} - -TEST(GreenfieldSigner, SignNoMessages) { - Proto::SigningInput input; - input.set_signing_mode(Proto::Eip712); - input.set_account_number(15560); - input.set_cosmos_chain_id("greenfield_5600-1"); - input.set_eth_chain_id("5600"); - input.set_sequence(3); - input.set_mode(Proto::BroadcastMode::SYNC); - - auto& fee = *input.mutable_fee(); - fee.set_gas(1200); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("BNB"); - amountOfFee->set_amount("6000000000000"); - - auto privateKey = parse_hex("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input); - EXPECT_EQ(output.error(), Common::Proto::SigningError::Error_invalid_params); -} - -TEST(GreenfieldSigner, SignMultipleMessages) { - Proto::SigningInput input; - input.set_signing_mode(Proto::Eip712); - input.set_account_number(15560); - input.set_cosmos_chain_id("greenfield_5600-1"); - input.set_eth_chain_id("5600"); - input.set_sequence(3); - input.set_mode(Proto::BroadcastMode::SYNC); - - // Append two empty messages. - input.add_messages(); - input.add_messages(); - - auto& fee = *input.mutable_fee(); - fee.set_gas(1200); - auto amountOfFee = fee.add_amounts(); - amountOfFee->set_denom("BNB"); - amountOfFee->set_amount("6000000000000"); - - auto privateKey = parse_hex("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0"); - input.set_private_key(privateKey.data(), privateKey.size()); - - auto output = Signer::sign(input); - EXPECT_EQ(output.error(), Common::Proto::SigningError::Error_invalid_params); -} - -} // namespace TW::Greenfield::tests diff --git a/tests/chains/Greenfield/TWCoinTypeTests.cpp b/tests/chains/Greenfield/TWCoinTypeTests.cpp index 09299b33942..657ba1fca39 100644 --- a/tests/chains/Greenfield/TWCoinTypeTests.cpp +++ b/tests/chains/Greenfield/TWCoinTypeTests.cpp @@ -31,7 +31,7 @@ TEST(TWGreenfieldCoinType, TWCoinType) { ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainGreenfield); ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0x0); ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0x0); - assertStringsEqual(chainId, "9000"); + assertStringsEqual(chainId, "1017"); assertStringsEqual(txUrl, "https://greenfieldscan.com/tx/0x150eac42070957115fd538b1f348fadd78d710fb641c248120efcf35d1e7e4f3"); assertStringsEqual(accUrl, "https://greenfieldscan.com/account/0xcf0f6b88ed72653b00fdebbffc90b98072cb3285"); } diff --git a/tests/chains/Greenfield/TransactionCompilerTests.cpp b/tests/chains/Greenfield/TransactionCompilerTests.cpp index a9c21fc9c9e..ba5220babe1 100644 --- a/tests/chains/Greenfield/TransactionCompilerTests.cpp +++ b/tests/chains/Greenfield/TransactionCompilerTests.cpp @@ -4,7 +4,6 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Greenfield/Signer.h" #include "HexCoding.h" #include "PrivateKey.h" #include "PublicKey.h" @@ -25,12 +24,17 @@ namespace TW::Greenfield { TEST(GreenfieldCompiler, PreHashCompile) { // Successfully broadcasted https://greenfieldscan.com/tx/0x9f895cf2dd64fb1f428cefcf2a6585a813c3540fc9fe1ef42db1da2cb1df55ab + auto privateKeyData = parse_hex("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0"); + PrivateKey privateKey(privateKeyData); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + Proto::SigningInput input; input.set_signing_mode(Proto::Eip712); input.set_account_number(15560); input.set_cosmos_chain_id("greenfield_5600-1"); input.set_eth_chain_id("5600"); input.set_sequence(2); + input.set_public_key(publicKey.bytes.data(), publicKey.bytes.size()); auto& msg = *input.add_messages(); auto& msgSend = *msg.mutable_send_coins_message(); @@ -58,9 +62,6 @@ TEST(GreenfieldCompiler, PreHashCompile) { // Step 2: Sign "remotely" - auto privateKeyData = parse_hex("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0"); - PrivateKey privateKey(privateKeyData); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); auto signature = privateKey.sign(data(preOutput.data_hash()), TWCurveSECP256k1); EXPECT_EQ(hex(signature), "cb3a4684a991014a387a04a85b59227ebb79567c2025addcb296b4ca856e9f810d3b526f2a0d0fad6ad1b126b3b9516f8b3be020a7cca9c03ce3cf47f4199b6d00"); diff --git a/tools/generate-files b/tools/generate-files index 35616f51fe7..bd0fb1638c0 100755 --- a/tools/generate-files +++ b/tools/generate-files @@ -70,7 +70,6 @@ fi # Generate internal message protocol Protobuf files "$PROTOC" -I=$PREFIX/include -I=src/Tron/Protobuf --cpp_out=src/Tron/Protobuf src/Tron/Protobuf/*.proto "$PROTOC" -I=$PREFIX/include -I=src/Zilliqa/Protobuf --cpp_out=src/Zilliqa/Protobuf src/Zilliqa/Protobuf/*.proto -"$PROTOC" -I=$PREFIX/include -I=src/Cosmos/Protobuf --cpp_out=src/Cosmos/Protobuf src/Cosmos/Protobuf/*.proto "$PROTOC" -I=$PREFIX/include -I=src/Hedera/Protobuf --cpp_out=src/Hedera/Protobuf src/Hedera/Protobuf/*.proto "$PROTOC" -I=$PREFIX/include -I=tests/chains/Cosmos/Protobuf --cpp_out=tests/chains/Cosmos/Protobuf tests/chains/Cosmos/Protobuf/*.proto