From 2a63cdae4818d46136ec837a6069515247887b2a Mon Sep 17 00:00:00 2001 From: satoshiotomakan <127754187+satoshiotomakan@users.noreply.github.com> Date: Wed, 13 Dec 2023 17:22:34 +0100 Subject: [PATCH] [Rust/BNB]: Move BNB Beacon chain to Rust (#3589) * [BNB]: Generate BNB Beacon chain skeleton files in Rust * [BNB]: Implement `BinanceAddress`, add Address tests * [BNB]: Add TBinance address tests * [BNB]: Add Transaction and its encoding, prehashing * Add Amino encoding * [BNB]: Sign a transaction * TODO Add tests, handle all message types * [BNB]: Add a signing test * [BNB]: Add `SendOrder`, `CancelOrder` * [BNB]: Add Token orders * [BNB]: Add `TokenBurnOrder`, `HTLTOrder` * [BNB]: Add `DepositHTLT` * [BNB]: Add `ClaimHTLTOrder`, `RefundHTLTOrder` * [BNB]: Add `TransferOutOrder` * [BNB]: Add `SideChainDelegate`, `SideChainRedelegate`, `SideChainUndelegate` orders * [BNB]: Add `TimeLock`, `TimeRelock`, `TimeUnlock` orders * [BNB]: Implement Transaction Compiler * [BNB]: Increase code coverage * [BNB]: Replace C++ implementation * [BNB]: Move TX preimage implementation to `JsonPreimager` * Remove C++ Signing tests * [BNB]: Extend `CoinEntry` documentation * [CI] Trigger CI * [BNB]: Remove `TransactionCompilerBuildInput` * [BNB]: Avoid duplicating code * [BNB]: Add fuzz tests --- codegen-v2/Cargo.toml | 4 +- .../TrustWalletCore/TWTransactionCompiler.h | 17 - registry.json | 2 + rust/Cargo.lock | 33 + rust/Cargo.toml | 1 + rust/chains/tw_binance/Cargo.toml | 21 + rust/chains/tw_binance/fuzz/.gitignore | 5 + rust/chains/tw_binance/fuzz/Cargo.toml | 30 + .../tw_binance/fuzz/fuzz_targets/sign.rs | 11 + rust/chains/tw_binance/src/address.rs | 91 +++ rust/chains/tw_binance/src/amino.rs | 127 ++++ rust/chains/tw_binance/src/compiler.rs | 87 +++ rust/chains/tw_binance/src/entry.rs | 91 +++ rust/chains/tw_binance/src/lib.rs | 14 + .../tw_binance/src/modules/mod.rs} | 5 +- .../tw_binance/src/modules/preimager.rs | 29 + .../tw_binance/src/modules/serializer.rs | 65 ++ .../tw_binance/src/modules/tx_builder.rs | 501 +++++++++++++ rust/chains/tw_binance/src/signature.rs | 9 + rust/chains/tw_binance/src/signer.rs | 53 ++ .../src/transaction/message/htlt_order.rs | 153 ++++ .../tw_binance/src/transaction/message/mod.rs | 84 +++ .../src/transaction/message/send_order.rs | 69 ++ .../message/side_chain_delegate.rs | 138 ++++ .../transaction/message/time_lock_order.rs | 115 +++ .../src/transaction/message/token_order.rs | 169 +++++ .../src/transaction/message/trade_order.rs | 103 +++ .../transaction/message/tranfer_out_order.rs | 48 ++ rust/chains/tw_binance/src/transaction/mod.rs | 47 ++ rust/coverage.stats | 2 +- rust/tw_any_coin/Cargo.toml | 13 +- rust/tw_any_coin/src/test_utils/mod.rs | 1 + rust/tw_any_coin/src/test_utils/sign_utils.rs | 96 +++ .../tests/chains/binance/binance_address.rs | 65 ++ .../tests/chains/binance/binance_compile.rs | 77 ++ .../tests/chains/binance/binance_sign.rs | 687 +++++++++++++++++ rust/tw_any_coin/tests/chains/binance/mod.rs | 18 + rust/tw_any_coin/tests/chains/mod.rs | 2 + .../tw_any_coin/tests/chains/tbinance/mod.rs | 3 +- .../tests/chains/tbinance/tbinance_address.rs | 49 ++ .../tests/coin_address_derivation_test.rs | 2 + rust/tw_aptos/Cargo.toml | 4 +- rust/tw_aptos/fuzz/.gitignore | 1 + rust/tw_bech32_address/Cargo.toml | 2 +- rust/tw_bech32_address/src/lib.rs | 37 +- rust/tw_bitcoin/Cargo.toml | 4 +- rust/tw_coin_entry/Cargo.toml | 2 +- rust/tw_coin_entry/src/coin_entry.rs | 18 +- rust/tw_coin_entry/src/error.rs | 2 +- rust/tw_coin_entry/src/prefix.rs | 2 + rust/tw_coin_registry/Cargo.toml | 9 +- rust/tw_coin_registry/src/blockchain_type.rs | 1 + rust/tw_coin_registry/src/dispatcher.rs | 3 + rust/tw_cosmos_sdk/Cargo.toml | 4 +- .../src/modules/compiler/tw_compiler.rs | 18 +- rust/tw_cosmos_sdk/src/signature/mod.rs | 10 +- rust/tw_cosmos_sdk/src/signature/secp256k1.rs | 28 +- rust/tw_encoding/Cargo.toml | 2 +- rust/tw_encoding/fuzz/Cargo.toml | 2 +- rust/tw_encoding/src/ffi.rs | 2 + rust/tw_encoding/src/hex.rs | 10 + rust/tw_encoding/src/lib.rs | 1 + rust/tw_evm/Cargo.toml | 4 +- rust/tw_evm/fuzz/Cargo.toml | 2 +- rust/tw_hash/Cargo.toml | 4 +- rust/tw_internet_computer/Cargo.toml | 2 +- rust/tw_keypair/Cargo.toml | 4 +- rust/tw_keypair/src/ecdsa/signature.rs | 23 +- rust/tw_misc/Cargo.toml | 4 +- rust/tw_misc/src/lib.rs | 1 + rust/tw_misc/src/serde.rs | 16 + rust/tw_number/Cargo.toml | 2 +- rust/wallet_core_rs/Cargo.toml | 2 +- samples/go/core/transactionHelper.go | 19 - samples/go/sample/external_signing.go | 39 +- src/Binance/Entry.cpp | 117 +-- src/Binance/Entry.h | 16 +- src/Binance/Serialization.cpp | 198 ----- src/Binance/Serialization.h | 21 - src/Binance/Signer.cpp | 227 ------ src/Binance/Signer.h | 64 -- src/Coin.cpp | 6 - src/Coin.h | 2 - src/CoinEntry.h | 3 - src/Cosmos/Entry.cpp | 23 +- src/Cosmos/Entry.h | 2 +- src/Ethereum/Entry.cpp | 27 +- src/Ethereum/Entry.h | 2 +- src/TransactionCompiler.cpp | 7 - src/TransactionCompiler.h | 3 - src/interface/TWTransactionCompiler.cpp | 17 - src/rust/RustCoinEntry.h | 39 + tests/chains/Binance/SignerTests.cpp | 702 ------------------ .../Binance/TransactionCompilerTests.cpp | 44 +- .../Ethereum/TransactionCompilerTests.cpp | 15 - tests/chains/TBinance/AddressTests.cpp | 42 -- .../interface/TWTransactionCompilerTests.cpp | 46 +- tools/new-blockchain | 11 + tools/new-evmchain | 7 + 99 files changed, 3419 insertions(+), 1643 deletions(-) create mode 100644 rust/chains/tw_binance/Cargo.toml create mode 100644 rust/chains/tw_binance/fuzz/.gitignore create mode 100644 rust/chains/tw_binance/fuzz/Cargo.toml create mode 100644 rust/chains/tw_binance/fuzz/fuzz_targets/sign.rs create mode 100644 rust/chains/tw_binance/src/address.rs create mode 100644 rust/chains/tw_binance/src/amino.rs create mode 100644 rust/chains/tw_binance/src/compiler.rs create mode 100644 rust/chains/tw_binance/src/entry.rs create mode 100644 rust/chains/tw_binance/src/lib.rs rename rust/{tw_coin_registry/src/coin_type.rs => chains/tw_binance/src/modules/mod.rs} (76%) create mode 100644 rust/chains/tw_binance/src/modules/preimager.rs create mode 100644 rust/chains/tw_binance/src/modules/serializer.rs create mode 100644 rust/chains/tw_binance/src/modules/tx_builder.rs create mode 100644 rust/chains/tw_binance/src/signature.rs create mode 100644 rust/chains/tw_binance/src/signer.rs create mode 100644 rust/chains/tw_binance/src/transaction/message/htlt_order.rs create mode 100644 rust/chains/tw_binance/src/transaction/message/mod.rs create mode 100644 rust/chains/tw_binance/src/transaction/message/send_order.rs create mode 100644 rust/chains/tw_binance/src/transaction/message/side_chain_delegate.rs create mode 100644 rust/chains/tw_binance/src/transaction/message/time_lock_order.rs create mode 100644 rust/chains/tw_binance/src/transaction/message/token_order.rs create mode 100644 rust/chains/tw_binance/src/transaction/message/trade_order.rs create mode 100644 rust/chains/tw_binance/src/transaction/message/tranfer_out_order.rs create mode 100644 rust/chains/tw_binance/src/transaction/mod.rs create mode 100644 rust/tw_any_coin/src/test_utils/sign_utils.rs create mode 100644 rust/tw_any_coin/tests/chains/binance/binance_address.rs create mode 100644 rust/tw_any_coin/tests/chains/binance/binance_compile.rs create mode 100644 rust/tw_any_coin/tests/chains/binance/binance_sign.rs create mode 100644 rust/tw_any_coin/tests/chains/binance/mod.rs rename src/Cosmos/Address.cpp => rust/tw_any_coin/tests/chains/tbinance/mod.rs (82%) create mode 100644 rust/tw_any_coin/tests/chains/tbinance/tbinance_address.rs create mode 100644 rust/tw_misc/src/serde.rs delete mode 100644 src/Binance/Serialization.cpp delete mode 100644 src/Binance/Serialization.h delete mode 100644 src/Binance/Signer.cpp delete mode 100644 src/Binance/Signer.h delete mode 100644 tests/chains/Binance/SignerTests.cpp delete mode 100644 tests/chains/TBinance/AddressTests.cpp create mode 100755 tools/new-blockchain create mode 100755 tools/new-evmchain diff --git a/codegen-v2/Cargo.toml b/codegen-v2/Cargo.toml index 52ad244f84a..43c2afea1c6 100644 --- a/codegen-v2/Cargo.toml +++ b/codegen-v2/Cargo.toml @@ -15,8 +15,8 @@ path = "src/main.rs" aho-corasick = "1.1.2" convert_case = "0.6.0" pathdiff = "0.2.1" -serde = { version = "1.0.159", features = ["derive"] } -serde_json = "1.0.95" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" serde_yaml = "0.9.21" toml_edit = "0.21.0" handlebars = "4.3.6" diff --git a/include/TrustWalletCore/TWTransactionCompiler.h b/include/TrustWalletCore/TWTransactionCompiler.h index 5ec06883c40..f9ecf2d6c86 100644 --- a/include/TrustWalletCore/TWTransactionCompiler.h +++ b/include/TrustWalletCore/TWTransactionCompiler.h @@ -18,23 +18,6 @@ TW_EXTERN_C_BEGIN TW_EXPORT_STRUCT struct TWTransactionCompiler; -/// Builds a coin-specific SigningInput (proto object) from a simple transaction. -/// -/// \deprecated `TWTransactionCompilerBuildInput` will be removed soon. -/// \param coin coin type. -/// \param from sender of the transaction. -/// \param to receiver of the transaction. -/// \param amount transaction amount in string -/// \param asset optional asset name, like "BNB" -/// \param memo optional memo -/// \param chainId optional chainId to override default -/// \return serialized data of the SigningInput proto object. -TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWTransactionCompilerBuildInput(enum TWCoinType coinType, TWString* _Nonnull from, - TWString* _Nonnull to, TWString* _Nonnull amount, - TWString* _Nonnull asset, TWString* _Nonnull memo, - TWString* _Nonnull chainId); - /// Obtains pre-signing hashes of a transaction. /// /// We provide a default `PreSigningOutput` in TransactionCompiler.proto. diff --git a/registry.json b/registry.json index 28031ea8d0e..d78c60aae73 100644 --- a/registry.json +++ b/registry.json @@ -2440,6 +2440,7 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1", + "addressHasher": "sha256ripemd", "hrp": "bnb", "chainId": "Binance-Chain-Tigris", "explorer": { @@ -2472,6 +2473,7 @@ ], "curve": "secp256k1", "publicKeyType": "secp256k1", + "addressHasher": "sha256ripemd", "hrp": "tbnb", "explorer": { "url": "https://testnet-explorer.binance.org", diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 24a18ba843f..8954202beeb 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1364,6 +1364,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + [[package]] name = "sha1" version = "0.10.5" @@ -1669,6 +1680,27 @@ dependencies = [ "tw_memory", ] +[[package]] +name = "tw_binance" +version = "0.1.0" +dependencies = [ + "quick-protobuf", + "serde", + "serde_json", + "serde_repr", + "strum_macros", + "tw_bech32_address", + "tw_coin_entry", + "tw_cosmos_sdk", + "tw_encoding", + "tw_evm", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_proto", +] + [[package]] name = "tw_bitcoin" version = "0.1.0" @@ -1712,6 +1744,7 @@ dependencies = [ "strum", "strum_macros", "tw_aptos", + "tw_binance", "tw_bitcoin", "tw_coin_entry", "tw_cosmos", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 87d437eff4c..6b80ffb89e6 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "chains/tw_binance", "chains/tw_cosmos", "chains/tw_native_evmos", "chains/tw_native_injective", diff --git a/rust/chains/tw_binance/Cargo.toml b/rust/chains/tw_binance/Cargo.toml new file mode 100644 index 00000000000..9a1d5003651 --- /dev/null +++ b/rust/chains/tw_binance/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "tw_binance" +version = "0.1.0" +edition = "2021" + +[dependencies] +quick-protobuf = "0.8.1" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +serde_repr = "0.1" +strum_macros = "0.25" +tw_bech32_address = { path = "../../tw_bech32_address" } +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_proto = { path = "../../tw_proto" } diff --git a/rust/chains/tw_binance/fuzz/.gitignore b/rust/chains/tw_binance/fuzz/.gitignore new file mode 100644 index 00000000000..5c404b9583f --- /dev/null +++ b/rust/chains/tw_binance/fuzz/.gitignore @@ -0,0 +1,5 @@ +target +corpus +artifacts +coverage +Cargo.lock diff --git a/rust/chains/tw_binance/fuzz/Cargo.toml b/rust/chains/tw_binance/fuzz/Cargo.toml new file mode 100644 index 00000000000..179f9d77239 --- /dev/null +++ b/rust/chains/tw_binance/fuzz/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "tw_binance-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_binance] +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_binance/fuzz/fuzz_targets/sign.rs b/rust/chains/tw_binance/fuzz/fuzz_targets/sign.rs new file mode 100644 index 00000000000..482fd1f5844 --- /dev/null +++ b/rust/chains/tw_binance/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::Binance::Proto; + +fuzz_target!(|input: Proto::SigningInput<'_>| { + let mut signer = AnySignerHelper::::default(); + let _ = signer.sign(CoinType::Binance, input); +}); diff --git a/rust/chains/tw_binance/src/address.rs b/rust/chains/tw_binance/src/address.rs new file mode 100644 index 00000000000..9bc164e0395 --- /dev/null +++ b/rust/chains/tw_binance/src/address.rs @@ -0,0 +1,91 @@ +// 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_bech32_address::bech32_prefix::Bech32Prefix; +use tw_bech32_address::Bech32Address; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::{AddressError, AddressResult}; +use tw_keypair::tw::PublicKey; +use tw_memory::Data; + +/// The list of known BNB hrps. +const BNB_KNOWN_HRPS: [&str; 2] = [ + BinanceAddress::VALIDATOR_HRP, // BNB Validator HRP. + "bca", +]; + +#[derive(Serialize)] +pub struct BinanceAddress(Bech32Address); + +impl CoinAddress for BinanceAddress { + #[inline] + fn data(&self) -> Data { + self.0.data() + } +} + +impl BinanceAddress { + pub const VALIDATOR_HRP: &'static str = "bva"; + + pub fn new_validator_addr(key_hash: Data) -> AddressResult { + Bech32Address::new(Self::VALIDATOR_HRP.to_string(), key_hash).map(BinanceAddress) + } + + /// Creates a Binance address with the only `prefix` + pub fn from_str_with_coin_and_prefix( + coin: &dyn CoinContext, + address_str: String, + prefix: Option, + ) -> AddressResult + where + Self: Sized, + { + let possible_hrps = match prefix { + Some(Bech32Prefix { hrp }) => vec![hrp], + None => { + let coin_hrp = coin.hrp().ok_or(AddressError::InvalidHrp)?; + let other_hrps = BNB_KNOWN_HRPS + .iter() + .map(|another_hrp| another_hrp.to_string()); + std::iter::once(coin_hrp).chain(other_hrps).collect() + }, + }; + Bech32Address::from_str_checked(possible_hrps, address_str).map(BinanceAddress) + } + + pub fn with_public_key_coin_context( + coin: &dyn CoinContext, + public_key: &PublicKey, + prefix: Option, + ) -> AddressResult { + Bech32Address::with_public_key_coin_context(coin, public_key, prefix).map(BinanceAddress) + } + + pub fn from_key_hash_with_coin( + coin: &dyn CoinContext, + key_hash: Data, + ) -> AddressResult { + Bech32Address::from_key_hash_with_coin(coin, key_hash).map(BinanceAddress) + } +} + +impl FromStr for BinanceAddress { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + Bech32Address::from_str(s).map(BinanceAddress) + } +} + +impl fmt::Display for BinanceAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} diff --git a/rust/chains/tw_binance/src/amino.rs b/rust/chains/tw_binance/src/amino.rs new file mode 100644 index 00000000000..0c891586b9e --- /dev/null +++ b/rust/chains/tw_binance/src/amino.rs @@ -0,0 +1,127 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use quick_protobuf::MessageWrite; +use tw_encoding::{EncodingError, EncodingResult}; +use tw_memory::Data; +use tw_proto::serialize; + +pub struct AminoEncoder { + /// The Amino content starts with a prefix. + content: Data, +} + +impl AminoEncoder { + pub fn new(prefix: &[u8]) -> AminoEncoder { + AminoEncoder { + content: prefix.to_vec(), + } + } + + pub fn extend_content(mut self, content: &[u8]) -> AminoEncoder { + self.content.extend_from_slice(content); + self + } + + pub fn extend_with_msg(mut self, msg: &M) -> EncodingResult { + let msg_data = serialize(msg).map_err(|_| EncodingError::Internal)?; + self.content.extend_from_slice(&msg_data); + Ok(self) + } + + pub fn encode(self) -> Data { + self.content + } + + pub fn encode_size_prefixed(self) -> EncodingResult { + const CONTENT_SIZE_CAPACITY: usize = 10; + + let content_len = self.content.len(); + let capacity = content_len + CONTENT_SIZE_CAPACITY; + + let mut buffer = Vec::with_capacity(capacity); + + Self::write_varint(&mut buffer, content_len as u64)?; + buffer.extend_from_slice(&self.content); + + Ok(buffer) + } + + /// This method takes `&mut Data` instead of `&mut [u8]` because the given `buffer` can be extended (become longer). + fn write_varint(buffer: &mut Data, num: u64) -> EncodingResult<()> { + let mut writer = quick_protobuf::Writer::new(buffer); + writer + .write_varint(num) + .map_err(|_| EncodingError::Internal) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tw_encoding::hex::DecodeHex; + + struct TestInput { + prefix: &'static str, + content: &'static str, + content_size_prefixed: bool, + expected: &'static str, + } + + fn amino_encode_impl(input: TestInput) { + let prefix = input.prefix.decode_hex().unwrap(); + let content = input.content.decode_hex().unwrap(); + + let encoder = AminoEncoder::new(&prefix).extend_content(&content); + + let actual = if input.content_size_prefixed { + encoder + .encode_size_prefixed() + .expect("Error on Amino encoding with content size prefix") + } else { + encoder.encode() + }; + + let expected = input.expected.decode_hex().unwrap(); + assert_eq!(actual, expected); + } + + #[test] + fn test_amino_encode() { + let content_size_prefixed = false; + + amino_encode_impl(TestInput { + prefix: "0b0c0d0e", + content: "0102030405060708", + content_size_prefixed, + expected: "0b0c0d0e0102030405060708", + }); + amino_encode_impl(TestInput { + prefix: "0b0c0d0e", + content: "01020304050607080102030405060708010203040506070801020304050607080102030405060708", + content_size_prefixed, + expected: "0b0c0d0e01020304050607080102030405060708010203040506070801020304050607080102030405060708", + }); + } + + #[test] + fn test_amino_encode_with_content_size_prefix() { + let content_size_prefixed = true; + + amino_encode_impl(TestInput { + prefix: "0b0c0d0e", + content: "0102030405060708", + content_size_prefixed, + expected: "0c0b0c0d0e0102030405060708", + }); + amino_encode_impl(TestInput { + prefix: "0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e", + content: "0102030405060708", + content_size_prefixed, + expected: "dc020b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0b0c0d0e0f101112131415161718191a1b1c1d1e0102030405060708", + }); + } +} diff --git a/rust/chains/tw_binance/src/compiler.rs b/rust/chains/tw_binance/src/compiler.rs new file mode 100644 index 00000000000..1ee1e23dd15 --- /dev/null +++ b/rust/chains/tw_binance/src/compiler.rs @@ -0,0 +1,87 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::modules::preimager::{JsonPreimager, JsonTxPreimage}; +use crate::modules::serializer::BinanceAminoSerializer; +use crate::modules::tx_builder::TxBuilder; +use crate::signature::BinanceSignature; +use crate::transaction::SignerInfo; +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::SigningResult; +use tw_coin_entry::signing_output_error; +use tw_keypair::ecdsa::secp256k1; +use tw_proto::Binance::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct BinanceCompiler; + +impl BinanceCompiler { + #[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_tx = TxBuilder::unsigned_tx_from_proto(coin, &input)?; + let JsonTxPreimage { + tx_hash, + encoded_tx, + } = JsonPreimager::preimage_hash(&unsigned_tx)?; + + Ok(CompilerProto::PreSigningOutput { + data_hash: tx_hash.to_vec().into(), + data: encoded_tx.as_bytes().to_vec().into(), + ..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)) + } + + fn compile_impl( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> SigningResult> { + let SingleSignaturePubkey { + signature, + public_key, + } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; + let signature = BinanceSignature::try_from(signature.as_slice())?; + let public_key = secp256k1::PublicKey::try_from(public_key.as_slice())?; + + let unsigned_tx = TxBuilder::unsigned_tx_from_proto(coin, &input)?; + let signed_tx = unsigned_tx.into_signed(SignerInfo { + public_key, + signature, + }); + + let encoded_tx = BinanceAminoSerializer::serialize_signed_tx(&signed_tx)?; + + Ok(Proto::SigningOutput { + encoded: encoded_tx.into(), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/chains/tw_binance/src/entry.rs b/rust/chains/tw_binance/src/entry.rs new file mode 100644 index 00000000000..bda948c9900 --- /dev/null +++ b/rust/chains/tw_binance/src/entry.rs @@ -0,0 +1,91 @@ +// 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::BinanceAddress; +use crate::compiler::BinanceCompiler; +use crate::signer::BinanceSigner; +use std::str::FromStr; +use tw_bech32_address::bech32_prefix::Bech32Prefix; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::AddressResult; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_keypair::tw::PublicKey; +use tw_proto::Binance::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct BinanceEntry; + +impl CoinEntry for BinanceEntry { + type AddressPrefix = Bech32Prefix; + type Address = BinanceAddress; + 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 { + BinanceAddress::from_str_with_coin_and_prefix(coin, address.to_string(), prefix) + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + BinanceAddress::from_str(address) + } + + #[inline] + fn derive_address( + &self, + coin: &dyn CoinContext, + public_key: PublicKey, + _derivation: Derivation, + prefix: Option, + ) -> AddressResult { + BinanceAddress::with_public_key_coin_context(coin, &public_key, prefix) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + BinanceSigner::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + BinanceCompiler::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + BinanceCompiler::compile(coin, input, signatures, public_keys) + } +} diff --git a/rust/chains/tw_binance/src/lib.rs b/rust/chains/tw_binance/src/lib.rs new file mode 100644 index 00000000000..0bd07a91bbf --- /dev/null +++ b/rust/chains/tw_binance/src/lib.rs @@ -0,0 +1,14 @@ +// 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 amino; +pub mod compiler; +pub mod entry; +pub mod modules; +pub mod signature; +pub mod signer; +pub mod transaction; diff --git a/rust/tw_coin_registry/src/coin_type.rs b/rust/chains/tw_binance/src/modules/mod.rs similarity index 76% rename from rust/tw_coin_registry/src/coin_type.rs rename to rust/chains/tw_binance/src/modules/mod.rs index d03c00f87ca..f27e84a2506 100644 --- a/rust/tw_coin_registry/src/coin_type.rs +++ b/rust/chains/tw_binance/src/modules/mod.rs @@ -4,5 +4,6 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -/// TODO make this enum generated from `registry.json`. -pub type CoinType = u32; +pub mod preimager; +pub mod serializer; +pub mod tx_builder; diff --git a/rust/chains/tw_binance/src/modules/preimager.rs b/rust/chains/tw_binance/src/modules/preimager.rs new file mode 100644 index 00000000000..18e2d47b5b5 --- /dev/null +++ b/rust/chains/tw_binance/src/modules/preimager.rs @@ -0,0 +1,29 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::transaction::UnsignedTransaction; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_hash::{sha2, H256}; + +pub struct JsonTxPreimage { + pub encoded_tx: String, + pub tx_hash: H256, +} + +pub struct JsonPreimager; + +impl JsonPreimager { + pub fn preimage_hash(unsigned: &UnsignedTransaction) -> SigningResult { + let encoded_tx = serde_json::to_string(unsigned) + .map_err(|_| SigningError(SigningErrorType::Error_internal))?; + let tx_hash = sha2::sha256(encoded_tx.as_bytes()); + let tx_hash = H256::try_from(tx_hash.as_slice()).expect("sha256 must return 32 bytes"); + Ok(JsonTxPreimage { + encoded_tx, + tx_hash, + }) + } +} diff --git a/rust/chains/tw_binance/src/modules/serializer.rs b/rust/chains/tw_binance/src/modules/serializer.rs new file mode 100644 index 00000000000..4716ffd9ab9 --- /dev/null +++ b/rust/chains/tw_binance/src/modules/serializer.rs @@ -0,0 +1,65 @@ +// 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::amino::AminoEncoder; +use crate::transaction::SignedTransaction; +use std::borrow::Cow; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_hash::H264; +use tw_memory::Data; +use tw_misc::traits::ToBytesVec; +use tw_proto::serialize; +use tw_proto::Binance::Proto; + +/// cbindgen:ignore +pub const TRANSACTION_AMINO_PREFIX: [u8; 4] = [0xF0, 0x62, 0x5D, 0xEE]; +/// cbindgen:ignore +pub const PUBLIC_KEY_PREFIX: [u8; 4] = [0xEB, 0x5A, 0xE9, 0x87]; + +pub struct BinanceAminoSerializer; + +impl BinanceAminoSerializer { + pub fn serialize_signed_tx(tx: &SignedTransaction) -> SigningResult { + let msgs = tx + .unsigned + .msgs + .iter() + .map(|msg| msg.to_amino_protobuf().map(Cow::from)) + .collect::>>()?; + + let signature = Self::serialize_signature(tx)?; + let tx = Proto::Transaction { + msgs, + signatures: vec![signature.into()], + memo: tx.unsigned.memo.clone().into(), + source: tx.unsigned.source, + data: tx.unsigned.data.clone().unwrap_or_default().into(), + }; + Ok(AminoEncoder::new(&TRANSACTION_AMINO_PREFIX) + .extend_with_msg(&tx)? + .encode_size_prefixed()?) + } + + pub fn serialize_public_key(public_key: H264) -> Data { + let public_key_len = public_key.len() as u8; + AminoEncoder::new(&PUBLIC_KEY_PREFIX) + // Push the length of the public key. + .extend_content(&[public_key_len]) + .extend_content(public_key.as_slice()) + .encode() + } + + pub fn serialize_signature(signed: &SignedTransaction) -> SigningResult { + let sign_msg = Proto::Signature { + pub_key: Self::serialize_public_key(signed.signer.public_key.compressed()).into(), + signature: signed.signer.signature.to_vec().into(), + account_number: signed.unsigned.account_number, + sequence: signed.unsigned.sequence, + }; + // There is no need to use Amino encoding here as the prefix is empty. + serialize(&sign_msg).map_err(|_| SigningError(SigningErrorType::Error_internal)) + } +} diff --git a/rust/chains/tw_binance/src/modules/tx_builder.rs b/rust/chains/tw_binance/src/modules/tx_builder.rs new file mode 100644 index 00000000000..a355df60dc4 --- /dev/null +++ b/rust/chains/tw_binance/src/modules/tx_builder.rs @@ -0,0 +1,501 @@ +// 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::BinanceAddress; +use crate::transaction::message::Token; +use crate::transaction::message::{BinanceMessage, BinanceMessageBox}; +use crate::transaction::UnsignedTransaction; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_evm::address::Address as EthereumAddress; +use tw_hash::H160; +use tw_proto::Binance::Proto; + +pub struct TxBuilder; + +impl TxBuilder { + pub fn unsigned_tx_from_proto( + coin: &dyn CoinContext, + input: &Proto::SigningInput<'_>, + ) -> SigningResult { + let msg = Self::msg_from_proto(coin, input)?; + + Ok(UnsignedTransaction { + account_number: input.account_number, + chain_id: input.chain_id.to_string(), + data: None, + memo: input.memo.to_string(), + msgs: vec![msg], + sequence: input.sequence, + source: input.source, + }) + } + + pub fn msg_from_proto( + coin: &dyn CoinContext, + input: &Proto::SigningInput<'_>, + ) -> SigningResult { + use Proto::mod_SigningInput::OneOforder_oneof as OrderEnum; + + match input.order_oneof { + OrderEnum::trade_order(ref new_order) => Self::trade_order_from_proto(coin, new_order), + OrderEnum::cancel_trade_order(ref cancel_order) => { + Self::cancel_order_from_proto(coin, cancel_order) + }, + OrderEnum::send_order(ref send_order) => Self::send_order_from_proto(coin, send_order), + OrderEnum::freeze_order(ref freeze_order) => { + Self::freeze_order_from_proto(coin, freeze_order) + }, + OrderEnum::unfreeze_order(ref unfreeze_order) => { + Self::unfreeze_order_from_proto(coin, unfreeze_order) + }, + OrderEnum::htlt_order(ref htlt_order) => Self::htlt_order_from_proto(coin, htlt_order), + OrderEnum::depositHTLT_order(ref deposit_htlt) => { + Self::deposit_htlt_order_from_proto(coin, deposit_htlt) + }, + OrderEnum::claimHTLT_order(ref claim_htlt) => { + Self::claim_htlt_order_from_proto(coin, claim_htlt) + }, + OrderEnum::refundHTLT_order(ref refund_htlt) => { + Self::refund_htlt_order_from_proto(coin, refund_htlt) + }, + OrderEnum::issue_order(ref issue_order) => { + Self::issue_order_from_proto(coin, issue_order) + }, + OrderEnum::mint_order(ref mint_order) => Self::mint_order_from_proto(coin, mint_order), + OrderEnum::burn_order(ref burn_order) => Self::burn_order_from_proto(coin, burn_order), + OrderEnum::transfer_out_order(ref transfer_out) => { + Self::transfer_out_order_from_proto(coin, transfer_out) + }, + OrderEnum::side_delegate_order(ref side_delegate) => { + Self::side_delegate_order_from_proto(coin, side_delegate) + }, + OrderEnum::side_redelegate_order(ref side_redelegate) => { + Self::side_redelegate_order_from_proto(coin, side_redelegate) + }, + OrderEnum::side_undelegate_order(ref side_undelegate) => { + Self::side_undelegate_order_from_proto(coin, side_undelegate) + }, + OrderEnum::time_lock_order(ref time_lock) => { + Self::time_lock_order_from_proto(coin, time_lock) + }, + OrderEnum::time_relock_order(ref time_relock) => { + Self::time_relock_order_from_proto(coin, time_relock) + }, + OrderEnum::time_unlock_order(ref time_unlock) => { + Self::time_unlock_order_from_proto(coin, time_unlock) + }, + OrderEnum::None => Err(SigningError(SigningErrorType::Error_invalid_params)), + } + } + + pub fn trade_order_from_proto( + coin: &dyn CoinContext, + new_order: &Proto::TradeOrder<'_>, + ) -> SigningResult { + use crate::transaction::message::trade_order::NewTradeOrder; + use crate::transaction::message::trade_order::OrderType; + + let order_type = OrderType::from_repr(new_order.ordertype) + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + let sender = BinanceAddress::from_key_hash_with_coin(coin, new_order.sender.to_vec())?; + + Ok(NewTradeOrder { + id: new_order.id.to_string(), + order_type, + price: new_order.price, + quantity: new_order.quantity, + sender, + side: new_order.side, + symbol: new_order.symbol.to_string(), + time_in_force: new_order.timeinforce, + } + .into_boxed()) + } + + pub fn cancel_order_from_proto( + coin: &dyn CoinContext, + cancel_order: &Proto::CancelTradeOrder<'_>, + ) -> SigningResult { + use crate::transaction::message::trade_order::CancelTradeOrder; + + let sender = BinanceAddress::from_key_hash_with_coin(coin, cancel_order.sender.to_vec())?; + Ok(CancelTradeOrder { + sender, + symbol: cancel_order.symbol.to_string(), + refid: cancel_order.refid.to_string(), + } + .into_boxed()) + } + + pub fn send_order_from_proto( + coin: &dyn CoinContext, + send_order: &Proto::SendOrder<'_>, + ) -> SigningResult { + use crate::transaction::message::send_order::{InOut, SendOrder}; + + fn in_out_from_proto( + coin: &dyn CoinContext, + address_key_hash: &[u8], + coins: &[Proto::mod_SendOrder::Token], + ) -> SigningResult { + let address = BinanceAddress::from_key_hash_with_coin(coin, address_key_hash.to_vec())?; + let coins = coins.iter().map(TxBuilder::token_from_proto).collect(); + + Ok(InOut { address, coins }) + } + + let inputs = send_order + .inputs + .iter() + .map(|input| in_out_from_proto(coin, &input.address, &input.coins)) + .collect::>>()?; + + let outputs = send_order + .outputs + .iter() + .map(|output| in_out_from_proto(coin, &output.address, &output.coins)) + .collect::>>()?; + + Ok(SendOrder { inputs, outputs }.into_boxed()) + } + + pub fn freeze_order_from_proto( + coin: &dyn CoinContext, + freeze_order: &Proto::TokenFreezeOrder<'_>, + ) -> SigningResult { + use crate::transaction::message::token_order::TokenFreezeOrder; + + let from = BinanceAddress::from_key_hash_with_coin(coin, freeze_order.from.to_vec())?; + Ok(TokenFreezeOrder { + from, + symbol: freeze_order.symbol.to_string(), + amount: freeze_order.amount, + } + .into_boxed()) + } + + pub fn unfreeze_order_from_proto( + coin: &dyn CoinContext, + unfreeze_order: &Proto::TokenUnfreezeOrder<'_>, + ) -> SigningResult { + use crate::transaction::message::token_order::TokenUnfreezeOrder; + + let from = BinanceAddress::from_key_hash_with_coin(coin, unfreeze_order.from.to_vec())?; + Ok(TokenUnfreezeOrder { + from, + symbol: unfreeze_order.symbol.to_string(), + amount: unfreeze_order.amount, + } + .into_boxed()) + } + + pub fn htlt_order_from_proto( + coin: &dyn CoinContext, + htlt_order: &Proto::HTLTOrder<'_>, + ) -> SigningResult { + use crate::transaction::message::htlt_order::HTLTOrder; + + let from = BinanceAddress::from_key_hash_with_coin(coin, htlt_order.from.to_vec())?; + let to = BinanceAddress::from_key_hash_with_coin(coin, htlt_order.to.to_vec())?; + + let amount = htlt_order + .amount + .iter() + .map(Self::token_from_proto) + .collect(); + + Ok(HTLTOrder { + from, + to, + recipient_other_chain: htlt_order.recipient_other_chain.to_string(), + sender_other_chain: htlt_order.sender_other_chain.to_string(), + random_number_hash: htlt_order.random_number_hash.to_vec(), + timestamp: htlt_order.timestamp, + amount, + expected_income: htlt_order.expected_income.to_string(), + height_span: htlt_order.height_span, + cross_chain: htlt_order.cross_chain, + } + .into_boxed()) + } + + pub fn deposit_htlt_order_from_proto( + coin: &dyn CoinContext, + deposit_htlt: &Proto::DepositHTLTOrder<'_>, + ) -> SigningResult { + use crate::transaction::message::htlt_order::DepositHTLTOrder; + + let from = BinanceAddress::from_key_hash_with_coin(coin, deposit_htlt.from.to_vec())?; + + let amount = deposit_htlt + .amount + .iter() + .map(Self::token_from_proto) + .collect(); + + Ok(DepositHTLTOrder { + from, + amount, + swap_id: deposit_htlt.swap_id.to_vec(), + } + .into_boxed()) + } + + pub fn claim_htlt_order_from_proto( + coin: &dyn CoinContext, + claim_htlt: &Proto::ClaimHTLOrder<'_>, + ) -> SigningResult { + use crate::transaction::message::htlt_order::ClaimHTLTOrder; + + let from = BinanceAddress::from_key_hash_with_coin(coin, claim_htlt.from.to_vec())?; + + Ok(ClaimHTLTOrder { + from, + swap_id: claim_htlt.swap_id.to_vec(), + random_number: claim_htlt.random_number.to_vec(), + } + .into_boxed()) + } + + pub fn refund_htlt_order_from_proto( + coin: &dyn CoinContext, + refund_htlt: &Proto::RefundHTLTOrder<'_>, + ) -> SigningResult { + use crate::transaction::message::htlt_order::RefundHTLTOrder; + + let from = BinanceAddress::from_key_hash_with_coin(coin, refund_htlt.from.to_vec())?; + let swap_id = refund_htlt.swap_id.to_vec(); + + Ok(RefundHTLTOrder { from, swap_id }.into_boxed()) + } + + pub fn issue_order_from_proto( + coin: &dyn CoinContext, + issue_order: &Proto::TokenIssueOrder<'_>, + ) -> SigningResult { + use crate::transaction::message::token_order::TokenIssueOrder; + + let from = BinanceAddress::from_key_hash_with_coin(coin, issue_order.from.to_vec())?; + Ok(TokenIssueOrder { + from, + name: issue_order.name.to_string(), + symbol: issue_order.symbol.to_string(), + total_supply: issue_order.total_supply, + mintable: issue_order.mintable, + } + .into_boxed()) + } + + pub fn mint_order_from_proto( + coin: &dyn CoinContext, + mint_order: &Proto::TokenMintOrder<'_>, + ) -> SigningResult { + use crate::transaction::message::token_order::TokenMintOrder; + + let from = BinanceAddress::from_key_hash_with_coin(coin, mint_order.from.to_vec())?; + Ok(TokenMintOrder { + from, + symbol: mint_order.symbol.to_string(), + amount: mint_order.amount, + } + .into_boxed()) + } + + pub fn burn_order_from_proto( + coin: &dyn CoinContext, + burn_order: &Proto::TokenBurnOrder<'_>, + ) -> SigningResult { + use crate::transaction::message::token_order::TokenBurnOrder; + + let from = BinanceAddress::from_key_hash_with_coin(coin, burn_order.from.to_vec())?; + Ok(TokenBurnOrder { + from, + symbol: burn_order.symbol.to_string(), + amount: burn_order.amount, + } + .into_boxed()) + } + + pub fn transfer_out_order_from_proto( + coin: &dyn CoinContext, + transfer_out: &Proto::TransferOut<'_>, + ) -> SigningResult { + use crate::transaction::message::tranfer_out_order::TransferOutOrder; + + let from = BinanceAddress::from_key_hash_with_coin(coin, transfer_out.from.to_vec())?; + + let to_bytes = H160::try_from(transfer_out.to.as_ref()) + .map_err(|_| SigningError(SigningErrorType::Error_invalid_address))?; + let to = EthereumAddress::from_bytes(to_bytes); + + let amount_proto = transfer_out + .amount + .as_ref() + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + + Ok(TransferOutOrder { + from, + to, + amount: Self::token_from_proto(amount_proto), + expire_time: transfer_out.expire_time, + } + .into_boxed()) + } + + pub fn side_delegate_order_from_proto( + coin: &dyn CoinContext, + side_delegate: &Proto::SideChainDelegate<'_>, + ) -> SigningResult { + use crate::transaction::message::side_chain_delegate::SideDelegateOrder; + + let delegator_addr = + BinanceAddress::from_key_hash_with_coin(coin, side_delegate.delegator_addr.to_vec())?; + let validator_addr = + BinanceAddress::new_validator_addr(side_delegate.validator_addr.to_vec())?; + + let delegation = side_delegate + .delegation + .as_ref() + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + + Ok(SideDelegateOrder { + delegator_addr, + validator_addr, + delegation: Self::token_from_proto(delegation), + side_chain_id: side_delegate.chain_id.to_string(), + } + .into_boxed()) + } + + pub fn side_redelegate_order_from_proto( + coin: &dyn CoinContext, + side_redelegate: &Proto::SideChainRedelegate<'_>, + ) -> SigningResult { + use crate::transaction::message::side_chain_delegate::SideRedelegateOrder; + + let delegator_addr = + BinanceAddress::from_key_hash_with_coin(coin, side_redelegate.delegator_addr.to_vec())?; + let validator_src_addr = + BinanceAddress::new_validator_addr(side_redelegate.validator_src_addr.to_vec())?; + let validator_dst_addr = + BinanceAddress::new_validator_addr(side_redelegate.validator_dst_addr.to_vec())?; + + let amount = side_redelegate + .amount + .as_ref() + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + + Ok(SideRedelegateOrder { + delegator_addr, + validator_src_addr, + validator_dst_addr, + amount: Self::token_from_proto(amount), + side_chain_id: side_redelegate.chain_id.to_string(), + } + .into_boxed()) + } + + pub fn side_undelegate_order_from_proto( + coin: &dyn CoinContext, + side_undelegate: &Proto::SideChainUndelegate<'_>, + ) -> SigningResult { + use crate::transaction::message::side_chain_delegate::SideUndelegateOrder; + + let delegator_addr = + BinanceAddress::from_key_hash_with_coin(coin, side_undelegate.delegator_addr.to_vec())?; + let validator_addr = + BinanceAddress::new_validator_addr(side_undelegate.validator_addr.to_vec())?; + + let amount = side_undelegate + .amount + .as_ref() + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + + Ok(SideUndelegateOrder { + delegator_addr, + validator_addr, + amount: Self::token_from_proto(amount), + side_chain_id: side_undelegate.chain_id.to_string(), + } + .into_boxed()) + } + + pub fn time_lock_order_from_proto( + coin: &dyn CoinContext, + time_lock: &Proto::TimeLockOrder<'_>, + ) -> SigningResult { + use crate::transaction::message::time_lock_order::TimeLockOrder; + + let from = BinanceAddress::from_key_hash_with_coin(coin, time_lock.from_address.to_vec())?; + let amount = time_lock + .amount + .iter() + .map(Self::token_from_proto) + .collect(); + + Ok(TimeLockOrder { + from, + description: time_lock.description.to_string(), + amount, + lock_time: time_lock.lock_time, + } + .into_boxed()) + } + + pub fn time_relock_order_from_proto( + coin: &dyn CoinContext, + time_relock: &Proto::TimeRelockOrder<'_>, + ) -> SigningResult { + use crate::transaction::message::time_lock_order::TimeRelockOrder; + + let from = + BinanceAddress::from_key_hash_with_coin(coin, time_relock.from_address.to_vec())?; + + let amount = if time_relock.amount.is_empty() { + None + } else { + Some( + time_relock + .amount + .iter() + .map(Self::token_from_proto) + .collect(), + ) + }; + + Ok(TimeRelockOrder { + from, + time_lock_id: time_relock.id, + description: time_relock.description.to_string(), + amount, + lock_time: time_relock.lock_time, + } + .into_boxed()) + } + + pub fn time_unlock_order_from_proto( + coin: &dyn CoinContext, + time_unlock: &Proto::TimeUnlockOrder<'_>, + ) -> SigningResult { + use crate::transaction::message::time_lock_order::TimeUnlockOrder; + + let from = + BinanceAddress::from_key_hash_with_coin(coin, time_unlock.from_address.to_vec())?; + Ok(TimeUnlockOrder { + from, + time_lock_id: time_unlock.id, + } + .into_boxed()) + } + + fn token_from_proto(token: &Proto::mod_SendOrder::Token) -> Token { + Token { + denom: token.denom.to_string(), + amount: token.amount, + } + } +} diff --git a/rust/chains/tw_binance/src/signature.rs b/rust/chains/tw_binance/src/signature.rs new file mode 100644 index 00000000000..5e58a2967a1 --- /dev/null +++ b/rust/chains/tw_binance/src/signature.rs @@ -0,0 +1,9 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_keypair::ecdsa::secp256k1; + +pub type BinanceSignature = secp256k1::VerifySignature; diff --git a/rust/chains/tw_binance/src/signer.rs b/rust/chains/tw_binance/src/signer.rs new file mode 100644 index 00000000000..b8b1c02739e --- /dev/null +++ b/rust/chains/tw_binance/src/signer.rs @@ -0,0 +1,53 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::modules::preimager::{JsonPreimager, JsonTxPreimage}; +use crate::modules::serializer::BinanceAminoSerializer; +use crate::modules::tx_builder::TxBuilder; +use crate::signature::BinanceSignature; +use crate::transaction::SignerInfo; +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_proto::Binance::Proto; + +pub struct BinanceSigner; + +impl BinanceSigner { + 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, + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let unsigned_tx = TxBuilder::unsigned_tx_from_proto(coin, &input)?; + let JsonTxPreimage { tx_hash, .. } = JsonPreimager::preimage_hash(&unsigned_tx)?; + + let key_pair = secp256k1::KeyPair::try_from(input.private_key.as_ref())?; + + let signature = BinanceSignature::from(key_pair.sign(tx_hash)?); + let public_key = key_pair.public().clone(); + + let signed_tx = unsigned_tx.into_signed(SignerInfo { + public_key, + signature, + }); + let encoded_tx = BinanceAminoSerializer::serialize_signed_tx(&signed_tx)?; + + Ok(Proto::SigningOutput { + encoded: encoded_tx.into(), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/chains/tw_binance/src/transaction/message/htlt_order.rs b/rust/chains/tw_binance/src/transaction/message/htlt_order.rs new file mode 100644 index 00000000000..c0969bb305d --- /dev/null +++ b/rust/chains/tw_binance/src/transaction/message/htlt_order.rs @@ -0,0 +1,153 @@ +// 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::BinanceAddress; +use crate::amino::AminoEncoder; +use crate::transaction::message::{message_to_json, BinanceMessage, Token}; +use serde::Serialize; +use serde_json::Value as Json; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::SigningResult; +use tw_encoding::hex::as_hex; +use tw_memory::Data; +use tw_proto::Binance::Proto; + +#[derive(Serialize)] +pub struct HTLTOrder { + pub from: BinanceAddress, + pub to: BinanceAddress, + pub recipient_other_chain: String, + pub sender_other_chain: String, + #[serde(serialize_with = "as_hex")] + pub random_number_hash: Data, + pub timestamp: i64, + pub amount: Vec, + pub expected_income: String, + pub height_span: i64, + pub cross_chain: bool, +} + +impl HTLTOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0xB3, 0x3F, 0x9A, 0x24]; +} + +impl BinanceMessage for HTLTOrder { + fn to_json(&self) -> SigningResult { + message_to_json(self) + } + + fn to_amino_protobuf(&self) -> SigningResult { + let msg = Proto::HTLTOrder { + from: self.from.data().into(), + to: self.to.data().into(), + recipient_other_chain: self.recipient_other_chain.clone().into(), + sender_other_chain: self.sender_other_chain.clone().into(), + random_number_hash: self.random_number_hash.clone().into(), + timestamp: self.timestamp, + amount: self.amount.iter().map(Token::to_proto).collect(), + expected_income: self.expected_income.clone().into(), + height_span: self.height_span, + cross_chain: self.cross_chain, + }; + + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&msg)? + .encode()) + } +} + +#[derive(Serialize)] +pub struct DepositHTLTOrder { + pub from: BinanceAddress, + pub amount: Vec, + #[serde(serialize_with = "as_hex")] + pub swap_id: Data, +} + +impl DepositHTLTOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x63, 0x98, 0x64, 0x96]; +} + +impl BinanceMessage for DepositHTLTOrder { + fn to_json(&self) -> SigningResult { + message_to_json(self) + } + + fn to_amino_protobuf(&self) -> SigningResult { + let msg = Proto::DepositHTLTOrder { + from: self.from.data().into(), + amount: self.amount.iter().map(Token::to_proto).collect(), + swap_id: self.swap_id.clone().into(), + }; + + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&msg)? + .encode()) + } +} + +#[derive(Serialize)] +pub struct ClaimHTLTOrder { + pub from: BinanceAddress, + #[serde(serialize_with = "as_hex")] + pub swap_id: Data, + #[serde(serialize_with = "as_hex")] + pub random_number: Data, +} + +impl ClaimHTLTOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0xC1, 0x66, 0x53, 0x00]; +} + +impl BinanceMessage for ClaimHTLTOrder { + fn to_json(&self) -> SigningResult { + message_to_json(self) + } + + fn to_amino_protobuf(&self) -> SigningResult { + let msg = Proto::ClaimHTLOrder { + from: self.from.data().into(), + swap_id: self.swap_id.clone().into(), + random_number: self.random_number.clone().into(), + }; + + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&msg)? + .encode()) + } +} + +#[derive(Serialize)] +pub struct RefundHTLTOrder { + pub from: BinanceAddress, + #[serde(serialize_with = "as_hex")] + pub swap_id: Data, +} + +impl RefundHTLTOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x34, 0x54, 0xA2, 0x7C]; +} + +impl BinanceMessage for RefundHTLTOrder { + fn to_json(&self) -> SigningResult { + message_to_json(self) + } + + fn to_amino_protobuf(&self) -> SigningResult { + let msg = Proto::RefundHTLTOrder { + from: self.from.data().into(), + swap_id: self.swap_id.clone().into(), + }; + + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&msg)? + .encode()) + } +} diff --git a/rust/chains/tw_binance/src/transaction/message/mod.rs b/rust/chains/tw_binance/src/transaction/message/mod.rs new file mode 100644 index 00000000000..6729a4276e4 --- /dev/null +++ b/rust/chains/tw_binance/src/transaction/message/mod.rs @@ -0,0 +1,84 @@ +// 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::ser::Error as SerError; +use serde::{Serialize, Serializer}; +use serde_json::Value as Json; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_memory::Data; +use tw_proto::Binance::Proto; + +pub mod htlt_order; +pub mod send_order; +pub mod side_chain_delegate; +pub mod time_lock_order; +pub mod token_order; +pub mod trade_order; +pub mod tranfer_out_order; + +pub type BinanceMessageBox = Box; + +pub trait BinanceMessage { + fn into_boxed(self) -> BinanceMessageBox + where + Self: 'static + Sized, + { + Box::new(self) + } + + fn to_json(&self) -> SigningResult; + + fn to_amino_protobuf(&self) -> SigningResult; +} + +impl Serialize for dyn BinanceMessage { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.to_json() + .map_err(|e| SerError::custom(format!("{e:?}")))? + .serialize(serializer) + } +} + +pub fn message_to_json(msg: T) -> SigningResult { + serde_json::to_value(msg).map_err(|_| SigningError(SigningErrorType::Error_internal)) +} + +#[derive(Serialize)] +pub struct Token { + /// Token ID. + pub denom: String, + /// Amount. + pub amount: i64, +} + +impl Token { + pub fn to_proto(&self) -> Proto::mod_SendOrder::Token { + Proto::mod_SendOrder::Token { + denom: self.denom.clone().into(), + amount: self.amount, + } + } + + pub fn serialize_with_string_amount(&self, serializer: S) -> Result + where + S: Serializer, + { + #[derive(Serialize)] + struct TokenWithStringAmount<'a> { + denom: &'a str, + amount: String, + } + + TokenWithStringAmount { + denom: &self.denom, + amount: self.amount.to_string(), + } + .serialize(serializer) + } +} diff --git a/rust/chains/tw_binance/src/transaction/message/send_order.rs b/rust/chains/tw_binance/src/transaction/message/send_order.rs new file mode 100644 index 00000000000..f8e5632a85d --- /dev/null +++ b/rust/chains/tw_binance/src/transaction/message/send_order.rs @@ -0,0 +1,69 @@ +// 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::BinanceAddress; +use crate::amino::AminoEncoder; +use crate::transaction::message::{message_to_json, BinanceMessage, Token}; +use serde::Serialize; +use serde_json::Value as Json; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::SigningResult; +use tw_memory::Data; +use tw_proto::Binance::Proto; + +/// Either an input or output of a `SendOrder`. +#[derive(Serialize)] +pub struct InOut { + /// Source address. + pub address: BinanceAddress, + + /// Input coin amounts. + pub coins: Vec, +} + +impl InOut { + pub fn to_input_proto(&self) -> Proto::mod_SendOrder::Input { + Proto::mod_SendOrder::Input { + address: self.address.data().into(), + coins: self.coins.iter().map(Token::to_proto).collect(), + } + } + + pub fn to_output_proto(&self) -> Proto::mod_SendOrder::Output { + Proto::mod_SendOrder::Output { + address: self.address.data().into(), + coins: self.coins.iter().map(Token::to_proto).collect(), + } + } +} + +#[derive(Serialize)] +pub struct SendOrder { + pub inputs: Vec, + pub outputs: Vec, +} + +impl SendOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x2A, 0x2C, 0x87, 0xFA]; +} + +impl BinanceMessage for SendOrder { + fn to_json(&self) -> SigningResult { + message_to_json(self) + } + + fn to_amino_protobuf(&self) -> SigningResult { + let msg = Proto::SendOrder { + inputs: self.inputs.iter().map(InOut::to_input_proto).collect(), + outputs: self.outputs.iter().map(InOut::to_output_proto).collect(), + }; + + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&msg)? + .encode()) + } +} diff --git a/rust/chains/tw_binance/src/transaction/message/side_chain_delegate.rs b/rust/chains/tw_binance/src/transaction/message/side_chain_delegate.rs new file mode 100644 index 00000000000..d376e530eee --- /dev/null +++ b/rust/chains/tw_binance/src/transaction/message/side_chain_delegate.rs @@ -0,0 +1,138 @@ +// 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::BinanceAddress; +use crate::amino::AminoEncoder; +use crate::transaction::message::{message_to_json, BinanceMessage, Token}; +use serde::Serialize; +use serde_json::Value as Json; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::SigningResult; +use tw_cosmos_sdk::modules::serializer::json_serializer::AnyMsg; +use tw_memory::Data; +use tw_proto::Binance::Proto; + +/// cosmos-sdk/MsgSideChainDelegate +#[derive(Serialize)] +pub struct SideDelegateOrder { + pub delegator_addr: BinanceAddress, + pub validator_addr: BinanceAddress, + #[serde(serialize_with = "Token::serialize_with_string_amount")] + pub delegation: Token, + pub side_chain_id: String, +} + +impl SideDelegateOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0xE3, 0xA0, 0x7F, 0xD2]; + /// cbindgen:ignore + pub const MESSAGE_TYPE: &'static str = "cosmos-sdk/MsgSideChainDelegate"; +} + +impl BinanceMessage for SideDelegateOrder { + fn to_json(&self) -> SigningResult { + let any_msg = AnyMsg { + msg_type: Self::MESSAGE_TYPE.to_string(), + value: message_to_json(self)?, + }; + message_to_json(any_msg) + } + + fn to_amino_protobuf(&self) -> SigningResult { + let msg = Proto::SideChainDelegate { + delegator_addr: self.delegator_addr.data().into(), + validator_addr: self.validator_addr.data().into(), + delegation: Some(self.delegation.to_proto()), + chain_id: self.side_chain_id.clone().into(), + }; + + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&msg)? + .encode()) + } +} + +/// cosmos-sdk/MsgSideChainRedelegate +#[derive(Serialize)] +pub struct SideRedelegateOrder { + pub delegator_addr: BinanceAddress, + pub validator_src_addr: BinanceAddress, + pub validator_dst_addr: BinanceAddress, + #[serde(serialize_with = "Token::serialize_with_string_amount")] + pub amount: Token, + pub side_chain_id: String, +} + +impl SideRedelegateOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0xE3, 0xCE, 0xD3, 0x64]; + /// cbindgen:ignore + pub const MESSAGE_TYPE: &'static str = "cosmos-sdk/MsgSideChainRedelegate"; +} + +impl BinanceMessage for SideRedelegateOrder { + fn to_json(&self) -> SigningResult { + let any_msg = AnyMsg { + msg_type: Self::MESSAGE_TYPE.to_string(), + value: message_to_json(self)?, + }; + message_to_json(any_msg) + } + + fn to_amino_protobuf(&self) -> SigningResult { + let msg = Proto::SideChainRedelegate { + delegator_addr: self.delegator_addr.data().into(), + validator_src_addr: self.validator_src_addr.data().into(), + validator_dst_addr: self.validator_dst_addr.data().into(), + amount: Some(self.amount.to_proto()), + chain_id: self.side_chain_id.clone().into(), + }; + + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&msg)? + .encode()) + } +} + +/// cosmos-sdk/MsgSideChainUndelegate +#[derive(Serialize)] +pub struct SideUndelegateOrder { + pub delegator_addr: BinanceAddress, + pub validator_addr: BinanceAddress, + #[serde(serialize_with = "Token::serialize_with_string_amount")] + pub amount: Token, + pub side_chain_id: String, +} + +impl SideUndelegateOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x51, 0x4F, 0x7E, 0x0E]; + /// cbindgen:ignore + pub const MESSAGE_TYPE: &'static str = "cosmos-sdk/MsgSideChainUndelegate"; +} + +impl BinanceMessage for SideUndelegateOrder { + fn to_json(&self) -> SigningResult { + let any_msg = AnyMsg { + msg_type: Self::MESSAGE_TYPE.to_string(), + value: message_to_json(self)?, + }; + message_to_json(any_msg) + } + + fn to_amino_protobuf(&self) -> SigningResult { + let msg = Proto::SideChainUndelegate { + delegator_addr: self.delegator_addr.data().into(), + validator_addr: self.validator_addr.data().into(), + amount: Some(self.amount.to_proto()), + chain_id: self.side_chain_id.clone().into(), + }; + + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&msg)? + .encode()) + } +} diff --git a/rust/chains/tw_binance/src/transaction/message/time_lock_order.rs b/rust/chains/tw_binance/src/transaction/message/time_lock_order.rs new file mode 100644 index 00000000000..83dbdd6cf24 --- /dev/null +++ b/rust/chains/tw_binance/src/transaction/message/time_lock_order.rs @@ -0,0 +1,115 @@ +// 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::BinanceAddress; +use crate::amino::AminoEncoder; +use crate::transaction::message::{message_to_json, BinanceMessage, Token}; +use serde::Serialize; +use serde_json::Value as Json; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::SigningResult; +use tw_memory::Data; +use tw_proto::Binance::Proto; + +#[derive(Serialize)] +pub struct TimeLockOrder { + pub from: BinanceAddress, + pub description: String, + pub amount: Vec, + pub lock_time: i64, +} + +impl TimeLockOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x07, 0x92, 0x15, 0x31]; +} + +impl BinanceMessage for TimeLockOrder { + fn to_json(&self) -> SigningResult { + message_to_json(self) + } + + fn to_amino_protobuf(&self) -> SigningResult { + let msg = Proto::TimeLockOrder { + from_address: self.from.data().into(), + description: self.description.clone().into(), + amount: self.amount.iter().map(Token::to_proto).collect(), + lock_time: self.lock_time, + }; + + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&msg)? + .encode()) + } +} + +#[derive(Serialize)] +pub struct TimeRelockOrder { + pub from: BinanceAddress, + pub time_lock_id: i64, + pub description: String, + /// If the amount is empty or omitted, set null to avoid signature verification error. + pub amount: Option>, + pub lock_time: i64, +} + +impl TimeRelockOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x50, 0x47, 0x11, 0xDA]; +} + +impl BinanceMessage for TimeRelockOrder { + fn to_json(&self) -> SigningResult { + message_to_json(self) + } + + fn to_amino_protobuf(&self) -> SigningResult { + let amount = match self.amount { + Some(ref tokens) => tokens.iter().map(Token::to_proto).collect(), + None => Vec::default(), + }; + + let msg = Proto::TimeRelockOrder { + from_address: self.from.data().into(), + id: self.time_lock_id, + description: self.description.clone().into(), + amount, + lock_time: self.lock_time, + }; + + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&msg)? + .encode()) + } +} + +#[derive(Serialize)] +pub struct TimeUnlockOrder { + pub from: BinanceAddress, + pub time_lock_id: i64, +} + +impl TimeUnlockOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0xC4, 0x05, 0x0C, 0x6C]; +} + +impl BinanceMessage for TimeUnlockOrder { + fn to_json(&self) -> SigningResult { + message_to_json(self) + } + + fn to_amino_protobuf(&self) -> SigningResult { + let msg = Proto::TimeUnlockOrder { + from_address: self.from.data().into(), + id: self.time_lock_id, + }; + + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&msg)? + .encode()) + } +} diff --git a/rust/chains/tw_binance/src/transaction/message/token_order.rs b/rust/chains/tw_binance/src/transaction/message/token_order.rs new file mode 100644 index 00000000000..16f2594fbcb --- /dev/null +++ b/rust/chains/tw_binance/src/transaction/message/token_order.rs @@ -0,0 +1,169 @@ +// 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::BinanceAddress; +use crate::amino::AminoEncoder; +use crate::transaction::message::{message_to_json, BinanceMessage}; +use serde::Serialize; +use serde_json::Value as Json; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::SigningResult; +use tw_memory::Data; +use tw_proto::Binance::Proto; + +#[derive(Serialize)] +pub struct TokenFreezeOrder { + pub from: BinanceAddress, + pub symbol: String, + pub amount: i64, +} + +impl TokenFreezeOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0xE7, 0x74, 0xB3, 0x2D]; +} + +impl BinanceMessage for TokenFreezeOrder { + fn to_json(&self) -> SigningResult { + message_to_json(self) + } + + fn to_amino_protobuf(&self) -> SigningResult { + let msg = Proto::TokenFreezeOrder { + from: self.from.data().into(), + symbol: self.symbol.clone().into(), + amount: self.amount, + }; + + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&msg)? + .encode()) + } +} + +#[derive(Serialize)] +pub struct TokenUnfreezeOrder { + pub from: BinanceAddress, + pub symbol: String, + pub amount: i64, +} + +impl TokenUnfreezeOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x65, 0x15, 0xFF, 0x0D]; +} + +impl BinanceMessage for TokenUnfreezeOrder { + fn to_json(&self) -> SigningResult { + message_to_json(self) + } + + fn to_amino_protobuf(&self) -> SigningResult { + let msg = Proto::TokenUnfreezeOrder { + from: self.from.data().into(), + symbol: self.symbol.clone().into(), + amount: self.amount, + }; + + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&msg)? + .encode()) + } +} + +#[derive(Serialize)] +pub struct TokenIssueOrder { + pub from: BinanceAddress, + pub name: String, + pub symbol: String, + pub total_supply: i64, + pub mintable: bool, +} + +impl TokenIssueOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x17, 0xEF, 0xAB, 0x80]; +} + +impl BinanceMessage for TokenIssueOrder { + fn to_json(&self) -> SigningResult { + message_to_json(self) + } + + fn to_amino_protobuf(&self) -> SigningResult { + let msg = Proto::TokenIssueOrder { + from: self.from.data().into(), + name: self.name.clone().into(), + symbol: self.symbol.clone().into(), + total_supply: self.total_supply, + mintable: self.mintable, + }; + + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&msg)? + .encode()) + } +} + +#[derive(Serialize)] +pub struct TokenMintOrder { + pub from: BinanceAddress, + pub symbol: String, + pub amount: i64, +} + +impl TokenMintOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x46, 0x7E, 0x08, 0x29]; +} + +impl BinanceMessage for TokenMintOrder { + fn to_json(&self) -> SigningResult { + message_to_json(self) + } + + fn to_amino_protobuf(&self) -> SigningResult { + let msg = Proto::TokenMintOrder { + from: self.from.data().into(), + symbol: self.symbol.clone().into(), + amount: self.amount, + }; + + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&msg)? + .encode()) + } +} + +#[derive(Serialize)] +pub struct TokenBurnOrder { + pub from: BinanceAddress, + pub symbol: String, + pub amount: i64, +} + +impl TokenBurnOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x7E, 0xD2, 0xD2, 0xA0]; +} + +impl BinanceMessage for TokenBurnOrder { + fn to_json(&self) -> SigningResult { + message_to_json(self) + } + + fn to_amino_protobuf(&self) -> SigningResult { + let msg = Proto::TokenBurnOrder { + from: self.from.data().into(), + symbol: self.symbol.clone().into(), + amount: self.amount, + }; + + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&msg)? + .encode()) + } +} diff --git a/rust/chains/tw_binance/src/transaction/message/trade_order.rs b/rust/chains/tw_binance/src/transaction/message/trade_order.rs new file mode 100644 index 00000000000..8e02780e214 --- /dev/null +++ b/rust/chains/tw_binance/src/transaction/message/trade_order.rs @@ -0,0 +1,103 @@ +// 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::BinanceAddress; +use crate::amino::AminoEncoder; +use crate::transaction::message::{message_to_json, BinanceMessage}; +use serde::Serialize; +use serde_json::Value as Json; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::SigningResult; +use tw_memory::Data; +use tw_proto::Binance::Proto; + +#[repr(i64)] +#[derive(Clone, Copy, serde_repr::Serialize_repr, strum_macros::FromRepr)] +pub enum OrderType { + /// https://github.com/bnb-chain/python-sdk/blob/0f6b8a6077f486b26eda3e448f3e84ef35bfff75/binance_chain/constants.py#L62 + Limit = 2, +} + +#[derive(Serialize)] +pub struct NewTradeOrder { + /// Order id, optional. + pub id: String, + /// Order type. + #[serde(rename = "ordertype")] + pub order_type: OrderType, + /// Price of the order, which is the real price multiplied by 1e8 (10^8) and rounded to integer. + pub price: i64, + /// Quantity of the order, which is the real price multiplied by 1e8 (10^8) and rounded to integer. + pub quantity: i64, + /// Originating address. + pub sender: BinanceAddress, + /// 1 for buy and 2 for sell. + pub side: i64, + /// Symbol for trading pair in full name of the tokens. + pub symbol: String, + /// 1 for Good Till Expire(GTE) order and 3 for Immediate Or Cancel (IOC). + #[serde(rename = "timeinforce")] + pub time_in_force: i64, +} + +impl NewTradeOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0xCE, 0x6D, 0xC0, 0x43]; +} + +impl BinanceMessage for NewTradeOrder { + fn to_json(&self) -> SigningResult { + message_to_json(self) + } + + fn to_amino_protobuf(&self) -> SigningResult { + let msg = Proto::TradeOrder { + id: self.id.clone().into(), + ordertype: self.order_type as i64, + price: self.price, + quantity: self.quantity, + sender: self.sender.data().into(), + side: self.side, + symbol: self.symbol.clone().into(), + timeinforce: self.time_in_force, + }; + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&msg)? + .encode()) + } +} + +#[derive(Serialize)] +pub struct CancelTradeOrder { + /// Originating address. + pub sender: BinanceAddress, + /// Symbol for trading pair in full name of the tokens. + pub symbol: String, + /// Order id to cancel. + pub refid: String, +} + +impl CancelTradeOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x16, 0x6E, 0x68, 0x1B]; +} + +impl BinanceMessage for CancelTradeOrder { + fn to_json(&self) -> SigningResult { + message_to_json(self) + } + + fn to_amino_protobuf(&self) -> SigningResult { + let msg = Proto::CancelTradeOrder { + sender: self.sender.data().into(), + symbol: self.symbol.clone().into(), + refid: self.refid.clone().into(), + }; + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&msg)? + .encode()) + } +} diff --git a/rust/chains/tw_binance/src/transaction/message/tranfer_out_order.rs b/rust/chains/tw_binance/src/transaction/message/tranfer_out_order.rs new file mode 100644 index 00000000000..3f838793e11 --- /dev/null +++ b/rust/chains/tw_binance/src/transaction/message/tranfer_out_order.rs @@ -0,0 +1,48 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::BinanceAddress; +use crate::amino::AminoEncoder; +use crate::transaction::message::{message_to_json, BinanceMessage, Token}; +use serde::Serialize; +use serde_json::Value as Json; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::SigningResult; +use tw_evm::address::Address as EthereumAddress; +use tw_memory::Data; +use tw_proto::Binance::Proto; + +#[derive(Serialize)] +pub struct TransferOutOrder { + pub from: BinanceAddress, + pub to: EthereumAddress, + pub amount: Token, + pub expire_time: i64, +} + +impl TransferOutOrder { + /// cbindgen:ignore + pub const PREFIX: [u8; 4] = [0x80, 0x08, 0x19, 0xC0]; +} + +impl BinanceMessage for TransferOutOrder { + fn to_json(&self) -> SigningResult { + message_to_json(self) + } + + fn to_amino_protobuf(&self) -> SigningResult { + let msg = Proto::TransferOut { + from: self.from.data().into(), + to: self.to.data().into(), + amount: Some(self.amount.to_proto()), + expire_time: self.expire_time, + }; + + Ok(AminoEncoder::new(&Self::PREFIX) + .extend_with_msg(&msg)? + .encode()) + } +} diff --git a/rust/chains/tw_binance/src/transaction/mod.rs b/rust/chains/tw_binance/src/transaction/mod.rs new file mode 100644 index 00000000000..74e96f2595b --- /dev/null +++ b/rust/chains/tw_binance/src/transaction/mod.rs @@ -0,0 +1,47 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::signature::BinanceSignature; +use crate::transaction::message::BinanceMessageBox; +use serde::Serialize; +use tw_keypair::ecdsa::secp256k1; +use tw_memory::Data; +use tw_misc::serde::as_string; + +pub mod message; + +#[derive(Serialize)] +pub struct UnsignedTransaction { + #[serde(serialize_with = "as_string")] + pub account_number: i64, + pub chain_id: String, + pub data: Option, + pub memo: String, + pub msgs: Vec, + #[serde(serialize_with = "as_string")] + pub sequence: i64, + #[serde(serialize_with = "as_string")] + pub source: i64, +} + +impl UnsignedTransaction { + pub fn into_signed(self, signer: SignerInfo) -> SignedTransaction { + SignedTransaction { + unsigned: self, + signer, + } + } +} + +pub struct SignerInfo { + pub public_key: secp256k1::PublicKey, + pub signature: BinanceSignature, +} + +pub struct SignedTransaction { + pub unsigned: UnsignedTransaction, + pub signer: SignerInfo, +} diff --git a/rust/coverage.stats b/rust/coverage.stats index 7d7ab43dc7c..f2511044fdd 100644 --- a/rust/coverage.stats +++ b/rust/coverage.stats @@ -1 +1 @@ -92.0 \ No newline at end of file +93.0 \ No newline at end of file diff --git a/rust/tw_any_coin/Cargo.toml b/rust/tw_any_coin/Cargo.toml index e2aeea6d021..d2f85f6fa4d 100644 --- a/rust/tw_any_coin/Cargo.toml +++ b/rust/tw_any_coin/Cargo.toml @@ -11,17 +11,22 @@ tw_hash = { path = "../tw_hash" } tw_keypair = { path = "../tw_keypair" } tw_memory = { path = "../tw_memory" } tw_misc = { path = "../tw_misc" } +tw_proto = { path = "../tw_proto", optional = true } [features] -test-utils = [] +test-utils = [ + "tw_keypair/test-utils", + "tw_memory/test-utils", + "tw_misc/test-utils", + "tw_proto" +] [dev-dependencies] -serde = { version = "1.0.163", features = ["derive"] } -serde_json = { version = "1.0.96" } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" tw_any_coin = { path = "./", features = ["test-utils"] } tw_cosmos_sdk = { path = "../tw_cosmos_sdk", features = ["test-utils"] } tw_keypair = { path = "../tw_keypair", features = ["test-utils"] } tw_memory = { path = "../tw_memory", features = ["test-utils"] } tw_misc = { path = "../tw_misc", features = ["test-utils"] } tw_number = { path = "../tw_number" } -tw_proto = { path = "../tw_proto" } diff --git a/rust/tw_any_coin/src/test_utils/mod.rs b/rust/tw_any_coin/src/test_utils/mod.rs index 7bdd5ac4e99..098e098ea64 100644 --- a/rust/tw_any_coin/src/test_utils/mod.rs +++ b/rust/tw_any_coin/src/test_utils/mod.rs @@ -5,3 +5,4 @@ // file LICENSE at the root of the source code distribution tree. pub mod address_utils; +pub mod sign_utils; diff --git a/rust/tw_any_coin/src/test_utils/sign_utils.rs b/rust/tw_any_coin/src/test_utils/sign_utils.rs new file mode 100644 index 00000000000..ff61b07dcce --- /dev/null +++ b/rust/tw_any_coin/src/test_utils/sign_utils.rs @@ -0,0 +1,96 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ffi::tw_any_signer::tw_any_signer_sign; +use crate::ffi::tw_transaction_compiler::{ + tw_transaction_compiler_compile, tw_transaction_compiler_pre_image_hashes, +}; +use std::marker::PhantomData; +use tw_coin_registry::coin_type::CoinType; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_memory::test_utils::tw_data_vector_helper::TWDataVectorHelper; +use tw_memory::Data; +use tw_proto::{deserialize, serialize, MessageRead, MessageWrite}; + +#[derive(Default)] +pub struct AnySignerHelper<'a, Output: MessageRead<'a>> { + output_data: Data, + _output_type: PhantomData<&'a Output>, +} + +impl<'a, Output: MessageRead<'a>> AnySignerHelper<'a, Output> { + pub fn sign(&'a mut self, coin_type: CoinType, input: Input) -> Output { + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + self.output_data = + TWDataHelper::wrap(unsafe { tw_any_signer_sign(input_data.ptr(), coin_type as u32) }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let output: Output = deserialize(&self.output_data).unwrap(); + output + } +} + +#[derive(Default)] +pub struct PreImageHelper<'a, Output: MessageRead<'a>> { + output_data: Data, + _output_type: PhantomData<&'a Output>, +} + +impl<'a, Output: MessageRead<'a>> PreImageHelper<'a, Output> { + pub fn pre_image_hashes( + &'a mut self, + coin_type: CoinType, + input: &Input, + ) -> Output { + let input_data = TWDataHelper::create(serialize(input).unwrap()); + + self.output_data = TWDataHelper::wrap(unsafe { + tw_transaction_compiler_pre_image_hashes(coin_type as u32, input_data.ptr()) + }) + .to_vec() + .expect("!tw_transaction_compiler_pre_image_hashes returned nullptr"); + + let output: Output = deserialize(&self.output_data).unwrap(); + output + } +} + +#[derive(Default)] +pub struct CompilerHelper<'a, Output: MessageRead<'a>> { + output_data: Data, + _output_type: PhantomData<&'a Output>, +} + +impl<'a, Output: MessageRead<'a>> CompilerHelper<'a, Output> { + pub fn compile( + &'a mut self, + coin_type: CoinType, + input: &Input, + signatures: Vec, + public_keys: Vec, + ) -> Output { + let input_data = TWDataHelper::create(serialize(input).unwrap()); + + let signatures = TWDataVectorHelper::create(signatures); + let public_keys = TWDataVectorHelper::create(public_keys); + + self.output_data = TWDataHelper::wrap(unsafe { + tw_transaction_compiler_compile( + coin_type as u32, + input_data.ptr(), + signatures.ptr(), + public_keys.ptr(), + ) + }) + .to_vec() + .expect("!tw_transaction_compiler_compile returned nullptr"); + + let output: Output = deserialize(&self.output_data).unwrap(); + output + } +} diff --git a/rust/tw_any_coin/tests/chains/binance/binance_address.rs b/rust/tw_any_coin/tests/chains/binance/binance_address.rs new file mode 100644 index 00000000000..d915ef496a8 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/binance/binance_address.rs @@ -0,0 +1,65 @@ +// 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_bech32_is_valid, test_address_get_data, test_address_invalid, + test_address_normalization, test_address_valid, AddressBech32IsValid, +}; +use tw_coin_registry::coin_type::CoinType; + +#[test] +fn test_binance_address_normalization() { + test_address_normalization( + CoinType::Binance, + "bnb1h8xf9htasu9aclra954dnnve8fgcda4ae7qfa8", + "bnb1h8xf9htasu9aclra954dnnve8fgcda4ae7qfa8", + ); +} + +#[test] +fn test_binance_address_is_valid() { + test_address_valid( + CoinType::Binance, + "bnb1h8xf9htasu9aclra954dnnve8fgcda4ae7qfa8", + ); + // Validator address. + test_address_valid( + CoinType::Binance, + "bva10npy5809y303f227g4leqw7vs3s6ep5ul26sq2", + ); +} + +#[test] +fn test_binance_address_invalid() { + // Testnet address. + test_address_invalid( + CoinType::Binance, + "tbnb1x4hxmtdf7pwea9dghq73dufh3qspm8gp5qht9c", + ); + // Unknown `bfa` hrp. + test_address_invalid( + CoinType::Binance, + "bfa10npy5809y303f227g4leqw7vs3s6ep5ul26sq2", + ); +} + +#[test] +fn test_binance_address_get_data() { + test_address_get_data( + CoinType::Binance, + "bnb1h8xf9htasu9aclra954dnnve8fgcda4ae7qfa8", + "b9cc92dd7d870bdc7c7d2d2ad9cd993a5186f6bd", + ); +} + +#[test] +fn test_binance_address_is_valid_bech32() { + test_address_bech32_is_valid(AddressBech32IsValid { + coin: CoinType::Binance, + address: "bva10npy5809y303f227g4leqw7vs3s6ep5ul26sq2", + hrp: "bva", + }); +} diff --git a/rust/tw_any_coin/tests/chains/binance/binance_compile.rs b/rust/tw_any_coin/tests/chains/binance/binance_compile.rs new file mode 100644 index 00000000000..f1104b9657e --- /dev/null +++ b/rust/tw_any_coin/tests/chains/binance/binance_compile.rs @@ -0,0 +1,77 @@ +// 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::binance::make_token; +use tw_any_coin::test_utils::sign_utils::{CompilerHelper, PreImageHelper}; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::DecodeHex; +use tw_encoding::hex::ToHex; +use tw_proto::Binance::Proto; +use tw_proto::Binance::Proto::mod_SigningInput::OneOforder_oneof as OrderType; +use tw_proto::Common::Proto::SigningError; +use tw_proto::TxCompiler::Proto as CompilerProto; + +#[test] +fn test_binance_compile() { + // bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2 + let from_address_key_hash = "40c2979694bbc961023d1d27be6fc4d21a9febe6"; + // bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5 + let to_address_key_hash = "bffe47abfaede50419c577f1074fee6dd1535cd1"; + + let send_order = Proto::SendOrder { + inputs: vec![Proto::mod_SendOrder::Input { + address: from_address_key_hash.decode_hex().unwrap().into(), + coins: vec![make_token("BNB", 1)], + }], + outputs: vec![Proto::mod_SendOrder::Output { + address: to_address_key_hash.decode_hex().unwrap().into(), + coins: vec![make_token("BNB", 1)], + }], + }; + let input = Proto::SigningInput { + // testnet chainId + chain_id: "Binance-Chain-Nile".into(), + order_oneof: OrderType::send_order(send_order), + ..Proto::SigningInput::default() + }; + + // Step 2: Obtain preimage hash + let mut pre_imager = PreImageHelper::::default(); + let preimage_output = pre_imager.pre_image_hashes(CoinType::Binance, &input); + + assert_eq!(preimage_output.error, SigningError::OK); + assert_eq!( + preimage_output.data.to_hex(), + "7b226163636f756e745f6e756d626572223a2230222c22636861696e5f6964223a2242696e616e63652d436861696e2d4e696c65222c2264617461223a6e756c6c2c226d656d6f223a22222c226d736773223a5b7b22696e70757473223a5b7b2261646472657373223a22626e623167727066303935356830796b7a71336172356e6d756d3779366764666c366c78666e34366832222c22636f696e73223a5b7b22616d6f756e74223a312c2264656e6f6d223a22424e42227d5d7d5d2c226f757470757473223a5b7b2261646472657373223a22626e6231686c6c7930326c3661686a7367787739776c6373776e6c776468673478687833387978706435222c22636f696e73223a5b7b22616d6f756e74223a312c2264656e6f6d223a22424e42227d5d7d5d7d5d2c2273657175656e6365223a2230222c22736f75726365223a2230227d" + ); + assert_eq!( + preimage_output.data_hash.to_hex(), + "3f3fece9059e714d303a9a1496ddade8f2c38fa78fc4cc2e505c5dbb0ea678d1" + ); + + // Step 3: Compile transaction info + + // Simulate signature, normally obtained from signature server. + let signature = "1b1181faec30b60a2ddaa2804c253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a504f9e8310679" + .decode_hex() + .unwrap(); + let public_key = "026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502" + .decode_hex() + .unwrap(); + + let mut compiler = CompilerHelper::::default(); + let output = compiler.compile(CoinType::Binance, &input, vec![signature], vec![public_key]); + + assert_eq!(output.error, SigningError::OK); + let expected_tx = concat!( + "b801f0625dee0a462a2c87fa0a1f0a1440c2979694bbc961023d1d27be6fc4d21a9febe612070a03424e421001", + "121f0a14bffe47abfaede50419c577f1074fee6dd1535cd112070a03424e421001126a0a26eb5ae98721026a35", + "920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e50212401b1181faec30b60a2ddaa2804c", + "253cf264c69180ec31814929b5de62088c0c5a45e8a816d1208fc5366bb8b041781a6771248550d04094c3d7a5", + "04f9e8310679", + ); + assert_eq!(output.encoded.to_hex(), expected_tx); +} diff --git a/rust/tw_any_coin/tests/chains/binance/binance_sign.rs b/rust/tw_any_coin/tests/chains/binance/binance_sign.rs new file mode 100644 index 00000000000..564b750971e --- /dev/null +++ b/rust/tw_any_coin/tests/chains/binance/binance_sign.rs @@ -0,0 +1,687 @@ +// 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::binance::make_token; +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::Binance::Proto; +use tw_proto::Binance::Proto::mod_SigningInput::OneOforder_oneof as OrderEnum; + +const ACCOUNT_12_PRIVATE_KEY: &str = + "90335b9d2153ad1a9799a3ccc070bd64b4164e9642ee1dd48053c33f9a3a05e9"; +const ACCOUNT_19_PRIVATE_KEY: &str = + "95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832"; +const ACCOUNT_15_PRIVATE_KEY: &str = + "eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d"; +const ACCOUNT_16_PRIVATE_KEY: &str = + "851fab89c14f4bbec0cc06f5e445ec065efc641068d78b308c67217d9bd5c88a"; + +#[test] +fn test_binance_sign_trade_order() { + // bnb1hgm0p7khfk85zpz5v0j8wnej3a90w709vhkdfu + let sender_key_hash = "ba36f0fad74d8f41045463e4774f328f4af779e5" + .decode_hex() + .unwrap(); + + let new_order = Proto::TradeOrder { + sender: sender_key_hash.into(), + id: "BA36F0FAD74D8F41045463E4774F328F4AF779E5-36".into(), + symbol: "NNB-338_BNB".into(), + ordertype: 2, + side: 1, + price: 136350000, + quantity: 100000000, + timeinforce: 1, + }; + + let input = Proto::SigningInput { + chain_id: "chain-bnb".into(), + account_number: 12, + sequence: 35, + source: 1, + private_key: ACCOUNT_12_PRIVATE_KEY.decode_hex().unwrap().into(), + order_oneof: OrderEnum::trade_order(new_order), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Binance, input); + + assert_eq!( + output.encoded.to_hex(), + "dc01f0625dee0a64ce6dc0430a14ba36f0fad74d8f41045463e4774f328f4af779e5122b424133364630464144373444384634313034353436334534373734463332384634414637373945352d33361a0b4e4e422d3333385f424e422002280130b09282413880c2d72f4001126e0a26eb5ae98721029729a52e4e3c2b4a4e52aa74033eedaf8ba1df5ab6d1f518fd69e67bbd309b0e12409123cb6906bb20aeb753f4a121d4d88ff0e9750ba75b0c4e10d76caee1e7d2481290fa3b9887a6225d6997f5f939ef834ea61d596a314237c48e560da9e17b5a180c20232001" + ); +} + +#[test] +fn test_binance_sign_cancel_trade_order() { + // bnb1hgm0p7khfk85zpz5v0j8wnej3a90w709vhkdfu + let sender_key_hash = "ba36f0fad74d8f41045463e4774f328f4af779e5" + .decode_hex() + .unwrap(); + + let new_order = Proto::CancelTradeOrder { + sender: sender_key_hash.into(), + symbol: "NNB-338_BNB".into(), + refid: "BA36F0FAD74D8F41045463E4774F328F4AF779E5-36".into(), + }; + + let input = Proto::SigningInput { + chain_id: "chain-bnb".into(), + account_number: 12, + sequence: 36, + source: 1, + private_key: ACCOUNT_12_PRIVATE_KEY.decode_hex().unwrap().into(), + order_oneof: OrderEnum::cancel_trade_order(new_order), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Binance, input); + + assert_eq!( + output.encoded.to_hex(), + "cc01f0625dee0a54166e681b0a14ba36f0fad74d8f41045463e4774f328f4af779e5120b4e4e422d3333385f424e421a2b424133364630464144373444384634313034353436334534373734463332384634414637373945352d3336126e0a26eb5ae98721029729a52e4e3c2b4a4e52aa74033eedaf8ba1df5ab6d1f518fd69e67bbd309b0e12403df6603426b991f7040bce22ce0137c12137df01e1d4d425cf3d9104103aec6335ac05c825e08ba26b9f72aa4cc45aa75cacfb6082df86b00692fef9701eb0f5180c20242001" + ); +} + +#[test] +fn test_binance_sign_send_order() { + let amount = 1_001_000_000; + // bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2 + let from_address_key_hash = "40c2979694bbc961023d1d27be6fc4d21a9febe6"; + let to_address_key_hash = "88b37d5e05f3699e2a1406468e5d87cb9dcceb95"; + + let send_order = Proto::SendOrder { + inputs: vec![Proto::mod_SendOrder::Input { + address: from_address_key_hash.decode_hex().unwrap().into(), + coins: vec![make_token("BNB", amount)], + }], + outputs: vec![Proto::mod_SendOrder::Output { + address: to_address_key_hash.decode_hex().unwrap().into(), + coins: vec![make_token("BNB", amount)], + }], + }; + + let input = Proto::SigningInput { + chain_id: "chain-bnb".into(), + account_number: 19, + sequence: 23, + source: 1, + memo: "test".into(), + private_key: ACCOUNT_19_PRIVATE_KEY.decode_hex().unwrap().into(), + order_oneof: OrderEnum::send_order(send_order), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Binance, input); + + let expected_encoded = concat!( + "cc01", + "f0625dee", + "0a4e", + "2a2c87fa", + "0a23", "0a1440c2979694bbc961023d1d27be6fc4d21a9febe6120b0a03424e4210c098a8dd03", + "1223", "0a1488b37d5e05f3699e2a1406468e5d87cb9dcceb95120b0a03424e4210c098a8dd03", + "126e", + "0a26", + "eb5ae987", + "21026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502", + "1240", "c65a13440f18a155bd971ee40b9e0dd58586f5bf344e12ec4c76c439aebca8c7789bab7bfbfb4ce89aadc4a02df225b6b6efc861c13bbeb5f7a3eea2d7ffc80f", + "1813", + "2017", + "1a04", "74657374", + "2001", + ); + assert_eq!(output.encoded.to_hex(), expected_encoded); +} + +#[test] +fn test_binance_sign_token_freeze_order() { + let from_address_key_hash = "08c7c918f6b72c3c0c21b7d08eb6fc66509998e1"; + + let freeze_order = Proto::TokenFreezeOrder { + from: from_address_key_hash.decode_hex().unwrap().into(), + symbol: "NNB-338_BNB".into(), + amount: 1000000, + }; + + let input = Proto::SigningInput { + chain_id: "test-chain".into(), + account_number: 15, + sequence: 1, + private_key: ACCOUNT_15_PRIVATE_KEY.decode_hex().unwrap().into(), + order_oneof: OrderEnum::freeze_order(freeze_order), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Binance, input); + + let expected_encoded = concat!( + "a101f0625dee0a2b", + "e774b32d", + "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b", + "4e4e422d3333385f424e42", + "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f", + "6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162", + "722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001", + ); + assert_eq!(output.encoded.to_hex(), expected_encoded); +} + +#[test] +fn test_binance_sign_token_unfreeze_order() { + let from_address_key_hash = "08c7c918f6b72c3c0c21b7d08eb6fc66509998e1"; + + let unfreeze_order = Proto::TokenUnfreezeOrder { + from: from_address_key_hash.decode_hex().unwrap().into(), + symbol: "NNB-338_BNB".into(), + amount: 1000000, + }; + + let input = Proto::SigningInput { + chain_id: "test-chain".into(), + account_number: 15, + sequence: 1, + private_key: ACCOUNT_15_PRIVATE_KEY.decode_hex().unwrap().into(), + order_oneof: OrderEnum::unfreeze_order(unfreeze_order), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Binance, input); + + let expected_encoded = concat!( + "a101f0625dee0a2b", + "6515ff0d", + "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b", + "4e4e422d3333385f424e42", + "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f", + "6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162", + "722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001", + ); + assert_eq!(output.encoded.to_hex(), expected_encoded); +} + +#[test] +fn test_binance_sign_token_issue_order() { + let from_address_key_hash = "08c7c918f6b72c3c0c21b7d08eb6fc66509998e1"; + + let issue_order = Proto::TokenIssueOrder { + from: from_address_key_hash.decode_hex().unwrap().into(), + name: "NewBinanceToken".into(), + symbol: "NNB-338_BNB".into(), + total_supply: 1000000000, + mintable: true, + }; + + let input = Proto::SigningInput { + chain_id: "test-chain".into(), + account_number: 15, + sequence: 1, + private_key: ACCOUNT_15_PRIVATE_KEY.decode_hex().unwrap().into(), + order_oneof: OrderEnum::issue_order(issue_order), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Binance, input); + + let expected_encoded = concat!( + "b601f0625dee0a40", + "17efab80", + "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120f", + "4e657742696e616e6365546f6b656e", + "1a0b", + "4e4e422d3333385f424e42", + "208094ebdc032801126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc712401fbb993d643f03b3e8e757a502035f58c4c45aaaa6e107a3059ab7c6164283c10f1254e87feee21477c64c87b1a27d8481048533ae2f685b3ac0dc66e4edbc0b180f2001", + ); + assert_eq!(output.encoded.to_hex(), expected_encoded); +} + +#[test] +fn test_binance_sign_token_mint_order() { + let from_address_key_hash = "08c7c918f6b72c3c0c21b7d08eb6fc66509998e1"; + + let mint_order = Proto::TokenMintOrder { + from: from_address_key_hash.decode_hex().unwrap().into(), + symbol: "NNB-338_BNB".into(), + amount: 1000000, + }; + + let input = Proto::SigningInput { + chain_id: "test-chain".into(), + account_number: 15, + sequence: 1, + private_key: ACCOUNT_15_PRIVATE_KEY.decode_hex().unwrap().into(), + order_oneof: OrderEnum::mint_order(mint_order), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Binance, input); + + let expected_encoded = concat!( + "a101f0625dee0a2b", + "467e0829", + "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b", + "4e4e422d3333385f424e42", + "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001", + ); + assert_eq!(output.encoded.to_hex(), expected_encoded); +} + +#[test] +fn test_binance_sign_token_burn_order() { + let from_address_key_hash = "08c7c918f6b72c3c0c21b7d08eb6fc66509998e1"; + + let burn_order = Proto::TokenBurnOrder { + from: from_address_key_hash.decode_hex().unwrap().into(), + symbol: "NNB-338_BNB".into(), + amount: 1000000, + }; + + let input = Proto::SigningInput { + chain_id: "test-chain".into(), + account_number: 15, + sequence: 1, + private_key: ACCOUNT_15_PRIVATE_KEY.decode_hex().unwrap().into(), + order_oneof: OrderEnum::burn_order(burn_order), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Binance, input); + + let expected_encoded = concat!( + "a101f0625dee0a2b", + "7ed2d2a0", + "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b", + "4e4e422d3333385f424e42", + "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001", + ); + assert_eq!(output.encoded.to_hex(), expected_encoded); +} + +#[test] +fn test_binance_sign_htlt_order() { + let from_address_key_hash = "08c7c918f6b72c3c0c21b7d08eb6fc66509998e1"; + let to_address_key_hash = "0153f11d6db7e69c7d51e771c697378018fb6c24"; + let random_number_hash = "e8eae926261ab77d018202434791a335249b470246a7b02e28c3b2fb6ffad8f3"; + + let htlt_order = Proto::HTLTOrder { + from: from_address_key_hash.decode_hex().unwrap().into(), + to: to_address_key_hash.decode_hex().unwrap().into(), + random_number_hash: random_number_hash.decode_hex().unwrap().into(), + timestamp: 1_567_746_273, + amount: vec![make_token("BNB", 100000000)], + expected_income: "100000000:BTC-1DC".into(), + height_span: 400, + cross_chain: false, + ..Proto::HTLTOrder::default() + }; + + let input = Proto::SigningInput { + chain_id: "test-chain".into(), + account_number: 15, + sequence: 0, + private_key: ACCOUNT_15_PRIVATE_KEY.decode_hex().unwrap().into(), + order_oneof: OrderEnum::htlt_order(htlt_order), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Binance, input); + + let expected_encoded = concat!( + "ee01f0625dee0a7ab33f9a240a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e112140153f11d6db7", + "e69c7d51e771c697378018fb6c242a20e8eae926261ab77d018202434791a335249b470246a7b02e28c3", + "b2fb6ffad8f330e1d1c7eb053a0a0a03424e421080c2d72f42113130303030303030303a4254432d3144", + "43489003126c0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f", + "6b6a8fc7124051439de2da19fe9fd22137c903cfc5dc87553bf05dca0bb202c0e07c47f9b51269efa272", + "43eb7b55888f5384a84ac1eac6d325c830d1be0ed042838e2dc0f6a9180f", + ); + assert_eq!(output.encoded.to_hex(), expected_encoded); +} + +#[test] +fn test_binance_sign_deposit_htlt_order() { + let from_address_key_hash = "0153f11d6db7e69c7d51e771c697378018fb6c24"; + let swap_id = "dd8fd4719741844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5"; + + let deposit_htlt = Proto::DepositHTLTOrder { + from: from_address_key_hash.decode_hex().unwrap().into(), + amount: vec![make_token("BTC-1DC", 100000000)], + swap_id: swap_id.decode_hex().unwrap().into(), + }; + + let input = Proto::SigningInput { + chain_id: "test-chain".into(), + account_number: 16, + sequence: 0, + private_key: ACCOUNT_16_PRIVATE_KEY.decode_hex().unwrap().into(), + order_oneof: OrderEnum::depositHTLT_order(deposit_htlt), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Binance, input); + + let expected_encoded = concat!( + "c001f0625dee0a4c639864960a140153f11d6db7e69c7d51e771c697378018fb6c24120e0a074254432d", + "3144431080c2d72f1a20dd8fd4719741844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5", + "126c0a26eb5ae98721038df6960084e20b2d07d50e1422f94105c6241d9f1482a4eb79ce8bfd460f19e4", + "12400ca4144c6818e2836d09b4faf3161781d85f9adfc00badb2eaa0953174610a233b81413dafcf8471", + "6abad48a4ed3aeb9884d90eb8416eec5d5c0c6930ab60bd01810", + ); + assert_eq!(output.encoded.to_hex(), expected_encoded); +} + +#[test] +fn test_binance_sign_claim_htlt_order() { + let from_address_key_hash = "08c7c918f6b72c3c0c21b7d08eb6fc66509998e1"; + let swap_id = "dd8fd4719741844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5"; + let random_number = "bda6933c7757d0ca428aa01fb9d0935a231f87bf2deeb9b409cea3f2d580a2cc"; + + let claim_htlt = Proto::ClaimHTLOrder { + from: from_address_key_hash.decode_hex().unwrap().into(), + swap_id: swap_id.decode_hex().unwrap().into(), + random_number: random_number.decode_hex().unwrap().into(), + }; + + let input = Proto::SigningInput { + chain_id: "test-chain".into(), + account_number: 15, + sequence: 1, + private_key: ACCOUNT_15_PRIVATE_KEY.decode_hex().unwrap().into(), + order_oneof: OrderEnum::claimHTLT_order(claim_htlt), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Binance, input); + + let expected_encoded = concat!( + "d401f0625dee0a5ec16653000a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e11220dd8fd4719741844d35", + "eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e51a20bda6933c7757d0ca428aa01fb9d0935a231f87bf", + "2deeb9b409cea3f2d580a2cc126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561a", + "ac993dbe0f6b6a8fc71240fa30ba50111aa31d8329dacb6d044c1c7d54f1cb782bc9aa2a50c3fabce02a4579d7", + "5b76ca69a9fab11b676d9da66b5af7aa4c9ad3d18e24fffeb16433be39fb180f2001", + ); + assert_eq!(output.encoded.to_hex(), expected_encoded); +} + +#[test] +fn test_binance_sign_refund_htlt_order() { + let from_address_key_hash = "08c7c918f6b72c3c0c21b7d08eb6fc66509998e1"; + let swap_id = "dd8fd4719741844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5"; + + let refund_htlt = Proto::RefundHTLTOrder { + from: from_address_key_hash.decode_hex().unwrap().into(), + swap_id: swap_id.decode_hex().unwrap().into(), + }; + + let input = Proto::SigningInput { + chain_id: "test-chain".into(), + account_number: 15, + sequence: 1, + private_key: ACCOUNT_15_PRIVATE_KEY.decode_hex().unwrap().into(), + order_oneof: OrderEnum::refundHTLT_order(refund_htlt), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Binance, input); + + let expected_encoded = concat!( + "b201f0625dee0a3c3454a27c0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e11220dd8fd4719741", + "844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5126e0a26eb5ae9872103a9a55c040c8e", + "b8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240c9f36142534d16ec8ce656f8eb73", + "70b32206a2d15198b7165acf1e2a18952c9e4570b0f862e1ab7bb868c30781a42c9e3ec0ae08982e8d6c", + "91c55b83c71b7b1e180f2001", + ); + assert_eq!(output.encoded.to_hex(), expected_encoded); +} + +#[test] +fn test_binance_sign_transfer_out_order() { + let from_address_key_hash = "08c7c918f6b72c3c0c21b7d08eb6fc66509998e1"; + let to_address_data = "0x35552c16704d214347f29Fa77f77DA6d75d7C752"; + + let transfer_out = Proto::TransferOut { + from: from_address_key_hash.decode_hex().unwrap().into(), + to: to_address_data.decode_hex().unwrap().into(), + amount: Some(make_token("BNB", 100000000)), + expire_time: 12345678, + }; + + let input = Proto::SigningInput { + chain_id: "test-chain".into(), + account_number: 15, + sequence: 1, + private_key: ACCOUNT_15_PRIVATE_KEY.decode_hex().unwrap().into(), + order_oneof: OrderEnum::transfer_out_order(transfer_out), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Binance, input); + + let expected_encoded = concat!( + "b701f0625dee0a41800819c00a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1121435552c16704d", + "214347f29fa77f77da6d75d7c7521a0a0a03424e421080c2d72f20cec2f105126e0a26eb5ae9872103a9", + "a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc712407eda148e1167b1be12", + "71a788ccf4e3eade1c7e1773e9d2093982d7f802f8f85f35ef550049011728206e4eda1a272f9e96fd95", + "ef3983cad85a29cd14262c22e0180f2001", + ); + assert_eq!(output.encoded.to_hex(), expected_encoded); +} + +#[test] +fn test_binance_sign_side_chain_delegate_order() { + let delegator_key_hash = "08c7c918f6b72c3c0c21b7d08eb6fc66509998e1"; + // bva10npy5809y303f227g4leqw7vs3s6ep5ul26sq2 + let validator_key_hash = "7cc24a1de5245f14a95e457f903bcc8461ac869c"; + + let side_delegate = Proto::SideChainDelegate { + delegator_addr: delegator_key_hash.decode_hex().unwrap().into(), + validator_addr: validator_key_hash.decode_hex().unwrap().into(), + delegation: Some(make_token("BNB", 200000000)), + chain_id: "chapel".into(), + }; + + let input = Proto::SigningInput { + chain_id: "test-chain".into(), + account_number: 15, + sequence: 1, + private_key: ACCOUNT_15_PRIVATE_KEY.decode_hex().unwrap().into(), + order_oneof: OrderEnum::side_delegate_order(side_delegate), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Binance, input); + + let expected_encoded = concat!( + "ba01f0625dee0a44e3a07fd20a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e112147cc24a1de524", + "5f14a95e457f903bcc8461ac869c1a0a0a03424e42108084af5f220663686170656c126e0a26eb5ae987", + "2103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc7124039302c9975fb", + "2a09ac2b6b6fb1d3b9fb5b4c03630d3d7a7da42b1c6736d6127142a3fcdca0b70a3d065da8d4f4df8b5d", + "9d8f46aeb3627a7d7aa901fe186af34c180f2001", + ); + assert_eq!(output.encoded.to_hex(), expected_encoded); +} + +#[test] +fn test_binance_sign_side_chain_redelegate_order() { + let delegator_key_hash = "08c7c918f6b72c3c0c21b7d08eb6fc66509998e1"; + // bva1echrty7p8r23cwx8g3ezwcza9azy4zq7ca0pzw + let validator_src_key_hash = "ce2e3593c138d51c38c7447227605d2f444a881e"; + // bva1p7s26ervsmv3w83k5696glautc9sm5rchz5f5e + let validator_dst_key_hash = "0fa0ad646c86d9171e36a68ba47fbc5e0b0dd078"; + + let side_redelegate = Proto::SideChainRedelegate { + delegator_addr: delegator_key_hash.decode_hex().unwrap().into(), + validator_src_addr: validator_src_key_hash.decode_hex().unwrap().into(), + validator_dst_addr: validator_dst_key_hash.decode_hex().unwrap().into(), + amount: Some(make_token("BNB", 100000000)), + chain_id: "chapel".into(), + }; + + let input = Proto::SigningInput { + chain_id: "test-chain".into(), + account_number: 15, + sequence: 1, + private_key: ACCOUNT_15_PRIVATE_KEY.decode_hex().unwrap().into(), + order_oneof: OrderEnum::side_redelegate_order(side_redelegate), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Binance, input); + + let expected_encoded = concat!( + "d001f0625dee0a5ae3ced3640a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e11214ce2e3593c138", + "d51c38c7447227605d2f444a881e1a140fa0ad646c86d9171e36a68ba47fbc5e0b0dd078220a0a03424e", + "421080c2d72f2a0663686170656c126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08", + "af44ea561aac993dbe0f6b6a8fc71240114c6927423e95ecc831ec763b629b3a40db8feeb330528a941f", + "d74843c0d63b4271b23916770d4901988c1f56b20086e5768a12290ebec265e30a80f8f3d88e180f2001", + ); + assert_eq!(output.encoded.to_hex(), expected_encoded); +} + +#[test] +fn test_binance_sign_side_chain_undelegate_order() { + let delegator_key_hash = "08c7c918f6b72c3c0c21b7d08eb6fc66509998e1"; + // bva1echrty7p8r23cwx8g3ezwcza9azy4zq7ca0pzw + let validator_key_hash = "ce2e3593c138d51c38c7447227605d2f444a881e"; + + let side_undelegate = Proto::SideChainUndelegate { + delegator_addr: delegator_key_hash.decode_hex().unwrap().into(), + validator_addr: validator_key_hash.decode_hex().unwrap().into(), + amount: Some(make_token("BNB", 100000000)), + chain_id: "chapel".into(), + }; + + let input = Proto::SigningInput { + chain_id: "test-chain".into(), + account_number: 15, + sequence: 1, + private_key: ACCOUNT_15_PRIVATE_KEY.decode_hex().unwrap().into(), + order_oneof: OrderEnum::side_undelegate_order(side_undelegate), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Binance, input); + + let expected_encoded = concat!( + "ba01f0625dee0a44514f7e0e0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e11214ce2e3593c138", + "d51c38c7447227605d2f444a881e1a0a0a03424e421080c2d72f220663686170656c126e0a26eb5ae987", + "2103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240a622b7ca7a28", + "75e5eaa675a5ed976b2ec3b8ca055a2b05e7fb471d328bd04df854789437dd06407e41ebb1e5a345604c", + "93663dfb660e223800636c0b94c2e798180f2001", + ); + assert_eq!(output.encoded.to_hex(), expected_encoded); +} + +#[test] +fn test_binance_sign_time_lock_order() { + let from_key_hash = "08c7c918f6b72c3c0c21b7d08eb6fc66509998e1"; + + let time_lock = Proto::TimeLockOrder { + from_address: from_key_hash.decode_hex().unwrap().into(), + description: "Description locked for offer".into(), + amount: vec![make_token("BNB", 1000000)], + lock_time: 1600001371, + }; + + let input = Proto::SigningInput { + chain_id: "test-chain".into(), + account_number: 15, + sequence: 1, + private_key: ACCOUNT_15_PRIVATE_KEY.decode_hex().unwrap().into(), + order_oneof: OrderEnum::time_lock_order(time_lock), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Binance, input); + + let expected_encoded = concat!( + "bf01f0625dee0a49", + "07921531", + "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1121c4465736372697074696f6e206c6f636b656420666f72206f666665721a090a03424e4210c0843d20dbaaf8fa05126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240c270822b9515ba486c6a6b3472d388a5aea872ed960c0b53de0fafdc8682ef473a126f01e7dd2c00f04a0138a601b9540f54b14026846de362f7ab7f9fed948b180f2001", + ); + assert_eq!(output.encoded.to_hex(), expected_encoded); +} + +#[test] +fn test_binance_sign_time_relock_order() { + let from_key_hash = "08c7c918f6b72c3c0c21b7d08eb6fc66509998e1"; + + let time_relock = Proto::TimeRelockOrder { + from_address: from_key_hash.decode_hex().unwrap().into(), + id: 333, + description: "Description locked for offer".into(), + amount: vec![make_token("BNB", 1000000)], + lock_time: 1600001371, + }; + + let input = Proto::SigningInput { + chain_id: "test-chain".into(), + account_number: 15, + sequence: 1, + private_key: ACCOUNT_15_PRIVATE_KEY.decode_hex().unwrap().into(), + order_oneof: OrderEnum::time_relock_order(time_relock), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Binance, input); + + let expected_encoded = concat!( + "c201f0625dee0a4c504711da0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e110cd021a1c446573", + "6372697074696f6e206c6f636b656420666f72206f6666657222090a03424e4210c0843d28dbaaf8fa05", + "126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc7", + "124086ddaa077c8ae551d402fa409cf7e91663982b0542200967c03c0b5876b181353250f689d342f221", + "7624a077b671ce7d09649187e29879f40abbbee9de7ab27c180f2001", + ); + assert_eq!(output.encoded.to_hex(), expected_encoded); +} + +#[test] +fn test_binance_sign_time_unlock_order() { + let from_key_hash = "08c7c918f6b72c3c0c21b7d08eb6fc66509998e1"; + + let time_unlock = Proto::TimeUnlockOrder { + from_address: from_key_hash.decode_hex().unwrap().into(), + id: 333, + }; + + let input = Proto::SigningInput { + chain_id: "test-chain".into(), + account_number: 15, + sequence: 1, + private_key: ACCOUNT_15_PRIVATE_KEY.decode_hex().unwrap().into(), + order_oneof: OrderEnum::time_unlock_order(time_unlock), + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Binance, input); + + let expected_encoded = concat!( + "9301f0625dee0a1dc4050c6c0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e110cd02126e0a26eb", + "5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240da777b", + "fd2032834f59ec9fe69fd6eaa4aca24242dfbc5ec4ef8c435cb9da7eb05ab78e1b8ca9f109657cb77996", + "898f1b59137b3d8f1e00f842e409e18033b347180f2001", + ); + assert_eq!(output.encoded.to_hex(), expected_encoded); +} diff --git a/rust/tw_any_coin/tests/chains/binance/mod.rs b/rust/tw_any_coin/tests/chains/binance/mod.rs new file mode 100644 index 00000000000..5dd8c0fa9fb --- /dev/null +++ b/rust/tw_any_coin/tests/chains/binance/mod.rs @@ -0,0 +1,18 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_proto::Binance::Proto; + +mod binance_address; +mod binance_compile; +mod binance_sign; + +fn make_token(denom: &str, amount: i64) -> Proto::mod_SendOrder::Token { + Proto::mod_SendOrder::Token { + denom: denom.into(), + amount, + } +} diff --git a/rust/tw_any_coin/tests/chains/mod.rs b/rust/tw_any_coin/tests/chains/mod.rs index ddd04b53632..7cbe502a6d2 100644 --- a/rust/tw_any_coin/tests/chains/mod.rs +++ b/rust/tw_any_coin/tests/chains/mod.rs @@ -5,10 +5,12 @@ // file LICENSE at the root of the source code distribution tree. mod aptos; +mod binance; mod bitcoin; mod cosmos; mod ethereum; mod internet_computer; mod native_evmos; mod native_injective; +mod tbinance; mod thorchain; diff --git a/src/Cosmos/Address.cpp b/rust/tw_any_coin/tests/chains/tbinance/mod.rs similarity index 82% rename from src/Cosmos/Address.cpp rename to rust/tw_any_coin/tests/chains/tbinance/mod.rs index 009d5e97fd1..0ccb0a3eebc 100644 --- a/src/Cosmos/Address.cpp +++ b/rust/tw_any_coin/tests/chains/tbinance/mod.rs @@ -1,8 +1,7 @@ -// Copyright © 2017 Pieter Wuille // 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 "Address.h" +mod tbinance_address; diff --git a/rust/tw_any_coin/tests/chains/tbinance/tbinance_address.rs b/rust/tw_any_coin/tests/chains/tbinance/tbinance_address.rs new file mode 100644 index 00000000000..c24b8d9bee6 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/tbinance/tbinance_address.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_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_tbinance_address_normalization() { + test_address_normalization( + CoinType::TBinance, + "tbnb18mtcq20875cr0p7l4upm0u5zx4r9jpj2kfu9f8", + "tbnb18mtcq20875cr0p7l4upm0u5zx4r9jpj2kfu9f8", + ); +} + +#[test] +fn test_tbinance_address_is_valid() { + test_address_valid( + CoinType::TBinance, + "tbnb18mtcq20875cr0p7l4upm0u5zx4r9jpj2kfu9f8", + ); +} + +#[test] +fn test_tbinance_address_invalid() { + test_address_invalid( + CoinType::TBinance, + "tbnb18mtcq20875cr0p7l4upm0u5zx4r9jpj2kfu9aa", + ); + // Mainnet. + test_address_invalid( + CoinType::TBinance, + "bnb1h8xf9htasu9aclra954dnnve8fgcda4ae7qfa8", + ); +} + +#[test] +fn test_tbinance_address_get_data() { + test_address_get_data( + CoinType::TBinance, + "tbnb18mtcq20875cr0p7l4upm0u5zx4r9jpj2kfu9f8", + "3ed78029e7f5303787dfaf03b7f282354659064a", + ); +} diff --git a/rust/tw_any_coin/tests/coin_address_derivation_test.rs b/rust/tw_any_coin/tests/coin_address_derivation_test.rs index ef3eabd910b..260243fa0ea 100644 --- a/rust/tw_any_coin/tests/coin_address_derivation_test.rs +++ b/rust/tw_any_coin/tests/coin_address_derivation_test.rs @@ -144,6 +144,8 @@ fn test_coin_address_derivation() { CoinType::NativeInjective => "inj14s0vgnj0pjnazu4hsqlksdk7slah9vcfyrp6ct", CoinType::NativeCanto => "canto14s0vgnj0pjnazu4hsqlksdk7slah9vcfuuhw7m", CoinType::InternetComputer => "290cc7c359f44c8516fc169c5ed4f0f3ae2e24bf5de0d4c51f5e7545b5474faa", + CoinType::Binance => "bnb1ten42eesehw0ktddcp0fws7d3ycsqez3aqvnpg", + CoinType::TBinance => "tbnb1ten42eesehw0ktddcp0fws7d3ycsqez3n49hpe", // end_of_coin_address_derivation_tests_marker_do_not_modify _ => panic!("{:?} must be covered", coin), }; diff --git a/rust/tw_aptos/Cargo.toml b/rust/tw_aptos/Cargo.toml index 49e4d120138..38b0270b8f1 100644 --- a/rust/tw_aptos/Cargo.toml +++ b/rust/tw_aptos/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -serde_json = "1.0.108" +serde_json = "1.0" tw_coin_entry = { path = "../tw_coin_entry" } tw_encoding = { path = "../tw_encoding" } tw_keypair = { path = "../tw_keypair" } @@ -13,7 +13,7 @@ tw_number = { path = "../tw_number" } tw_hash = { path = "../tw_hash" } tw_memory = { path = "../tw_memory" } move-core-types = { git = "https://github.com/move-language/move", rev = "ea70797099baea64f05194a918cebd69ed02b285", features = ["address32"] } -serde = { version = "1.0.189", features = ["derive"] } +serde = { version = "1.0", features = ["derive"] } serde_bytes = "0.11.12" [dev-dependencies] diff --git a/rust/tw_aptos/fuzz/.gitignore b/rust/tw_aptos/fuzz/.gitignore index 1a45eee7760..5c404b9583f 100644 --- a/rust/tw_aptos/fuzz/.gitignore +++ b/rust/tw_aptos/fuzz/.gitignore @@ -2,3 +2,4 @@ target corpus artifacts coverage +Cargo.lock diff --git a/rust/tw_bech32_address/Cargo.toml b/rust/tw_bech32_address/Cargo.toml index 1ca0cfa493f..8650363cf9e 100644 --- a/rust/tw_bech32_address/Cargo.toml +++ b/rust/tw_bech32_address/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -serde = { version = "1.0.163", features = [ "derive" ] } +serde = { version = "1.0", features = ["derive"] } tw_coin_entry = { path = "../tw_coin_entry" } tw_encoding = { path = "../tw_encoding" } tw_hash = { path = "../tw_hash" } diff --git a/rust/tw_bech32_address/src/lib.rs b/rust/tw_bech32_address/src/lib.rs index 67c361f90a7..d8493362219 100644 --- a/rust/tw_bech32_address/src/lib.rs +++ b/rust/tw_bech32_address/src/lib.rs @@ -92,15 +92,22 @@ impl Bech32Address { Bech32Address::with_public_key_hasher(hrp, &public_key, address_hasher) } - pub fn from_str_checked( - expected_hrp: &str, + pub fn from_str_checked( + possible_hrps: I, address_str: String, - ) -> AddressResult { + ) -> AddressResult + where + I: IntoIterator, + { let bech32::Decoded { hrp, bytes } = bech32::decode(&address_str).map_err(|_| AddressError::InvalidInput)?; + // Try to find at least one hrp matches the actual value. // Copied from the legacy Bech32Address.cpp: // https://github.com/trustwallet/wallet-core/blob/d67078daa580b37063c97be66a625aaee9664882/src/Bech32Address.cpp#L21 - if !hrp.starts_with(expected_hrp) { + if !possible_hrps + .into_iter() + .any(|possible_hrp| hrp.starts_with(&possible_hrp)) + { return Err(AddressError::InvalidHrp); } Ok(Bech32Address { @@ -114,15 +121,20 @@ impl Bech32Address { coin: &dyn CoinContext, address_str: String, prefix: Option, - ) -> AddressResult - where - Self: Sized, - { + ) -> AddressResult { let hrp = match prefix { Some(Bech32Prefix { hrp }) => hrp, None => coin.hrp().ok_or(AddressError::InvalidHrp)?, }; - Self::from_str_checked(&hrp, address_str) + Self::from_str_checked([hrp], address_str) + } + + pub fn from_key_hash_with_coin( + coin: &dyn CoinContext, + key_hash: Data, + ) -> AddressResult { + let hrp = coin.hrp().ok_or(AddressError::InvalidHrp)?; + Bech32Address::new(hrp, key_hash) } pub fn key_hash(&self) -> &[u8] { @@ -202,7 +214,7 @@ mod tests { #[test] fn test_address_from_str_checked_valid() { fn test_impl(addr: &str, hrp: &str) { - Bech32Address::from_str_checked(hrp, addr.to_string()) + Bech32Address::from_str_checked([hrp.to_string()], addr.to_string()) .unwrap_or_else(|e| panic!("ERROR={:?}: hrp={} addr={}", e, hrp, addr)); } @@ -249,7 +261,7 @@ mod tests { #[test] fn test_address_from_str_checked_invalid() { fn test_impl(addr: &str, hrp: &str) { - Bech32Address::from_str_checked(hrp, addr.to_string()) + Bech32Address::from_str_checked([hrp.to_string()], addr.to_string()) .expect_err(&format!("hrp={} addr={}", hrp, addr)); } @@ -293,7 +305,8 @@ mod tests { #[test] fn test_decode() { fn test_impl(addr: &str, hrp: &str, expected_hash: &str) { - let actual = Bech32Address::from_str_checked(hrp, addr.to_string()).unwrap(); + let actual = + Bech32Address::from_str_checked([hrp.to_string()], addr.to_string()).unwrap(); assert_eq!(actual.key_hash.to_hex(), expected_hash); } diff --git a/rust/tw_bitcoin/Cargo.toml b/rust/tw_bitcoin/Cargo.toml index 451491eb345..f82af92fd55 100644 --- a/rust/tw_bitcoin/Cargo.toml +++ b/rust/tw_bitcoin/Cargo.toml @@ -6,8 +6,8 @@ edition = "2021" [dependencies] bitcoin = "0.30.0" secp256k1 = { version = "0.27.0", features = [ "global-context", "rand-std" ] } -serde = { version = "1.0.163", features = [ "derive" ] } -serde_json = "1.0.96" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" tw_coin_entry = { path = "../tw_coin_entry", features = ["test-utils"] } tw_utxo = { path = "../tw_utxo" } tw_encoding = { path = "../tw_encoding" } diff --git a/rust/tw_coin_entry/Cargo.toml b/rust/tw_coin_entry/Cargo.toml index d3df7dd56da..083908cbac4 100644 --- a/rust/tw_coin_entry/Cargo.toml +++ b/rust/tw_coin_entry/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -serde_json = "1.0.95" +serde_json = "1.0" tw_encoding = { path = "../tw_encoding" } tw_hash = { path = "../tw_hash" } tw_keypair = { path = "../tw_keypair" } diff --git a/rust/tw_coin_entry/src/coin_entry.rs b/rust/tw_coin_entry/src/coin_entry.rs index 785021d409b..1ec5e590f48 100644 --- a/rust/tw_coin_entry/src/coin_entry.rs +++ b/rust/tw_coin_entry/src/coin_entry.rs @@ -34,15 +34,31 @@ pub trait CoinAddress: fmt::Display { /// (e.g adding/deleting a method or an associated type): /// https://github.com/trustwallet/wallet-core/blob/master/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs pub trait CoinEntry { + /// Address prefix that is used to derive or validate an address for a different network. + /// It can be a Bech32 HRP prefix, or p2pkh/p2sh Base58 prefix, or even a set of different address prefixes. + /// **Optional**. Use `NoPrefix` if the blockchain does not support any address prefixes. type AddressPrefix: Prefix; + /// Address type that should be parsable from a string and have other required methods. type Address: CoinAddress; + /// Protobuf message that provides the information about a transaction to be generated and signed. type SigningInput<'a>: MessageRead<'a> + MessageWrite; + /// Protobuf message - result of the request to sign or compile a transaction. + /// Contains a serialized transaction or an error if occurred. type SigningOutput: MessageWrite; + /// Protobuf message - result of the request to obtain a transaction preimage hashes. type PreSigningOutput: MessageWrite; - // Optional modules: + /// Not supported at this moment. + /// Use `NoJsonSigner`. type JsonSigner: JsonSigner; + /// Transaction Planner - the module provides transaction planning functionality. + /// Used mostly in Bitcoin and UTXO-based chains. + /// + /// **Optional**. Use `NoPlanBuilder` if the blockchain does not support transaction planning. type PlanBuilder: PlanBuilder; + /// Message Signer - the module provides regular message signing functionality. + /// + /// **Optional**. Use `NoMessageSigner` if the blockchain does not support message signing. type MessageSigner: MessageSigner; /// Tries to parse `Self::Address` from the given `address` string by `coin` type and address `prefix`. diff --git a/rust/tw_coin_entry/src/error.rs b/rust/tw_coin_entry/src/error.rs index 179e8b73b3f..b6bc7b923cc 100644 --- a/rust/tw_coin_entry/src/error.rs +++ b/rust/tw_coin_entry/src/error.rs @@ -27,7 +27,7 @@ macro_rules! signing_output_error { pub type AddressResult = Result; -#[derive(Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum AddressError { UnknownCoinType, MissingPrefix, diff --git a/rust/tw_coin_entry/src/prefix.rs b/rust/tw_coin_entry/src/prefix.rs index 52898e19ee5..b48bfcccc66 100644 --- a/rust/tw_coin_entry/src/prefix.rs +++ b/rust/tw_coin_entry/src/prefix.rs @@ -6,12 +6,14 @@ use crate::error::AddressError; +/// An address prefix. It can contain a bech32 prefix that can be used by `Cosmos` based chains. /// Extend when adding new blockchains. #[derive(Clone)] pub enum AddressPrefix { Hrp(String), } +/// A blockchain's address prefix should be convertable from an `AddressPrefix`. pub trait Prefix: TryFrom {} impl Prefix for T where T: TryFrom {} diff --git a/rust/tw_coin_registry/Cargo.toml b/rust/tw_coin_registry/Cargo.toml index a63ba08b267..bd289c150a1 100644 --- a/rust/tw_coin_registry/Cargo.toml +++ b/rust/tw_coin_registry/Cargo.toml @@ -5,11 +5,12 @@ edition = "2021" [dependencies] lazy_static = "1.4.0" -serde = { version = "1.0.163", features = ["derive"] } -serde_json = "1.0.96" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" strum = "0.25" strum_macros = "0.25" tw_aptos = { path = "../tw_aptos" } +tw_binance = { path = "../chains/tw_binance" } tw_bitcoin = { path = "../tw_bitcoin" } tw_coin_entry = { path = "../tw_coin_entry" } tw_cosmos = { path = "../chains/tw_cosmos" } @@ -27,5 +28,5 @@ tw_thorchain = { path = "../chains/tw_thorchain" } [build-dependencies] itertools = "0.10.5" -serde = { version = "1.0.163", features = ["derive"] } -serde_json = "1.0.96" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/rust/tw_coin_registry/src/blockchain_type.rs b/rust/tw_coin_registry/src/blockchain_type.rs index 357697cd83c..28780562cd8 100644 --- a/rust/tw_coin_registry/src/blockchain_type.rs +++ b/rust/tw_coin_registry/src/blockchain_type.rs @@ -12,6 +12,7 @@ use serde::Deserialize; pub enum BlockchainType { // start_of_blockchain_type - USED TO GENERATE CODE Aptos, + Binance, Bitcoin, Cosmos, Ethereum, diff --git a/rust/tw_coin_registry/src/dispatcher.rs b/rust/tw_coin_registry/src/dispatcher.rs index 61e6f0d5998..d6e8fed9043 100644 --- a/rust/tw_coin_registry/src/dispatcher.rs +++ b/rust/tw_coin_registry/src/dispatcher.rs @@ -10,6 +10,7 @@ use crate::coin_type::CoinType; use crate::error::{RegistryError, RegistryResult}; use crate::registry::get_coin_item; use tw_aptos::entry::AptosEntry; +use tw_binance::entry::BinanceEntry; use tw_bitcoin::entry::BitcoinEntry; use tw_coin_entry::coin_entry_ext::CoinEntryExt; use tw_cosmos::entry::CosmosEntry; @@ -26,6 +27,7 @@ pub type EvmEntryExtStaticRef = &'static dyn EvmEntryExt; // start_of_blockchain_entries - USED TO GENERATE CODE const APTOS: AptosEntry = AptosEntry; +const BINANCE: BinanceEntry = BinanceEntry; const BITCOIN: BitcoinEntry = BitcoinEntry; const COSMOS: CosmosEntry = CosmosEntry; const ETHEREUM: EthereumEntry = EthereumEntry; @@ -40,6 +42,7 @@ pub fn blockchain_dispatcher(blockchain: BlockchainType) -> RegistryResult Ok(&APTOS), + BlockchainType::Binance => Ok(&BINANCE), BlockchainType::Bitcoin => Ok(&BITCOIN), BlockchainType::Cosmos => Ok(&COSMOS), BlockchainType::Ethereum => Ok(ÐEREUM), diff --git a/rust/tw_cosmos_sdk/Cargo.toml b/rust/tw_cosmos_sdk/Cargo.toml index c2b1be4fd94..3e209b4f6df 100644 --- a/rust/tw_cosmos_sdk/Cargo.toml +++ b/rust/tw_cosmos_sdk/Cargo.toml @@ -8,8 +8,8 @@ test-utils = [] [dependencies] quick-protobuf = "0.8.1" -serde = { version = "1.0.163", features = ["derive"] } -serde_json = "1.0.96" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" tw_bech32_address = { path = "../tw_bech32_address" } tw_coin_entry = { path = "../tw_coin_entry" } tw_encoding = { path = "../tw_encoding" } diff --git a/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs b/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs index 897276b8c90..f8211227822 100644 --- a/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs +++ b/rust/tw_cosmos_sdk/src/modules/compiler/tw_compiler.rs @@ -12,7 +12,6 @@ use crate::modules::serializer::json_serializer::JsonSerializer; use crate::modules::serializer::protobuf_serializer::ProtobufSerializer; use crate::modules::tx_builder::TxBuilder; use crate::public_key::CosmosPublicKey; -use crate::signature::CosmosSignature; use std::borrow::Cow; use std::marker::PhantomData; use tw_coin_entry::coin_context::CoinContext; @@ -20,6 +19,7 @@ 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_misc::traits::ToBytesVec; use tw_proto::Cosmos::Proto; use tw_proto::TxCompiler::Proto as CompilerProto; @@ -105,21 +105,21 @@ impl TWTransactionCompiler { signature, public_key, } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; - let signature = Context::Signature::from_bytes(&signature)?; + let signature = Context::Signature::try_from(&signature)?; let public_key = Context::PublicKey::from_bytes(coin, &public_key)?; let signed_tx_raw = match TxBuilder::::try_sign_direct_args(&input) { // If there was a `SignDirect` message in the signing input, generate the `TxRaw` directly. Ok(Some(sign_direct_args)) => ProtobufSerializer::::build_direct_signed_tx( &sign_direct_args, - signature.to_bytes(), + signature.to_vec(), ), // Otherwise, generate the `TxRaw` by using `TxBuilder`. _ => { // Set the public key. It will be used to construct a signer info. input.public_key = Cow::from(public_key.to_bytes()); let unsigned_tx = TxBuilder::::unsigned_tx_from_proto(coin, &input)?; - let signed_tx = unsigned_tx.into_signed(signature.to_bytes()); + let signed_tx = unsigned_tx.into_signed(signature.to_vec()); ProtobufSerializer::build_signed_tx(&signed_tx)? }, @@ -129,12 +129,12 @@ impl TWTransactionCompiler { let broadcast_tx = BroadcastMsg::raw(broadcast_mode, &signed_tx_raw).to_json_string(); let signature_json = - JsonSerializer::::serialize_signature(&public_key, signature.to_bytes()); + JsonSerializer::::serialize_signature(&public_key, signature.to_vec()); let signature_json = serde_json::to_string(&[signature_json]) .map_err(|_| SigningError(SigningErrorType::Error_internal))?; Ok(Proto::SigningOutput { - signature: Cow::from(signature.to_bytes()), + signature: Cow::from(signature.to_vec()), signature_json: Cow::from(signature_json), serialized: Cow::from(broadcast_tx), ..Proto::SigningOutput::default() @@ -151,13 +151,13 @@ impl TWTransactionCompiler { signature, public_key, } = SingleSignaturePubkey::from_sign_pubkey_list(signatures, public_keys)?; - let signature = Context::Signature::from_bytes(&signature)?; + let signature = Context::Signature::try_from(&signature)?; let public_key = Context::PublicKey::from_bytes(coin, &public_key)?; // Set the public key. It will be used to construct a signer info. input.public_key = Cow::from(public_key.to_bytes()); let unsigned_tx = TxBuilder::::unsigned_tx_from_proto(coin, &input)?; - let signed_tx = unsigned_tx.into_signed(signature.to_bytes()); + let signed_tx = unsigned_tx.into_signed(signature.to_vec()); let signed_tx_json = JsonSerializer::build_signed_tx(&signed_tx)?; @@ -168,7 +168,7 @@ impl TWTransactionCompiler { .map_err(|_| SigningError(SigningErrorType::Error_internal))?; Ok(Proto::SigningOutput { - signature: Cow::from(signature.to_bytes()), + signature: Cow::from(signature.to_vec()), signature_json: Cow::from(signature_json), json: Cow::from(broadcast_tx), ..Proto::SigningOutput::default() diff --git a/rust/tw_cosmos_sdk/src/signature/mod.rs b/rust/tw_cosmos_sdk/src/signature/mod.rs index d2c5ba2169e..7184a3c0fd2 100644 --- a/rust/tw_cosmos_sdk/src/signature/mod.rs +++ b/rust/tw_cosmos_sdk/src/signature/mod.rs @@ -4,13 +4,9 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -use tw_keypair::KeyPairResult; -use tw_memory::Data; +use tw_keypair::KeyPairError; +use tw_misc::traits::{FromSlice, ToBytesVec}; pub mod secp256k1; -pub trait CosmosSignature: Sized { - fn from_bytes(signature_bytes: &[u8]) -> KeyPairResult; - - fn to_bytes(&self) -> Data; -} +pub trait CosmosSignature: FromSlice + ToBytesVec {} diff --git a/rust/tw_cosmos_sdk/src/signature/secp256k1.rs b/rust/tw_cosmos_sdk/src/signature/secp256k1.rs index f07ec5957ff..a5e41ec4fb2 100644 --- a/rust/tw_cosmos_sdk/src/signature/secp256k1.rs +++ b/rust/tw_cosmos_sdk/src/signature/secp256k1.rs @@ -5,30 +5,8 @@ // file LICENSE at the root of the source code distribution tree. use crate::signature::CosmosSignature; -use tw_hash::{H512, H520}; -use tw_keypair::{KeyPairError, KeyPairResult}; -use tw_memory::Data; -use tw_misc::traits::ToBytesVec; +use tw_keypair::ecdsa::secp256k1; -pub struct Secp256k1Signature { - signature: H512, -} +pub type Secp256k1Signature = secp256k1::VerifySignature; -impl CosmosSignature for Secp256k1Signature { - fn from_bytes(signature_bytes: &[u8]) -> KeyPairResult { - let signature_slice = if signature_bytes.len() == H520::len() { - // Discard the last `v` recovery byte. - &signature_bytes[0..H512::len()] - } else { - signature_bytes - }; - - let signature = - H512::try_from(signature_slice).map_err(|_| KeyPairError::InvalidSignature)?; - Ok(Secp256k1Signature { signature }) - } - - fn to_bytes(&self) -> Data { - self.signature.to_vec() - } -} +impl CosmosSignature for Secp256k1Signature {} diff --git a/rust/tw_encoding/Cargo.toml b/rust/tw_encoding/Cargo.toml index 88008908278..f36c1580246 100644 --- a/rust/tw_encoding/Cargo.toml +++ b/rust/tw_encoding/Cargo.toml @@ -11,7 +11,7 @@ bs58 = "0.4.0" ciborium = "0.2.1" data-encoding = "2.3.3" hex = "0.4.3" -serde = { version = "1.0.136", features = ["derive"] } +serde = { version = "1.0", features = ["derive"] } tw_memory = { path = "../tw_memory" } [dev-dependencies] diff --git a/rust/tw_encoding/fuzz/Cargo.toml b/rust/tw_encoding/fuzz/Cargo.toml index 81d65cd9818..2bcec734cf5 100644 --- a/rust/tw_encoding/fuzz/Cargo.toml +++ b/rust/tw_encoding/fuzz/Cargo.toml @@ -9,7 +9,7 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = { version = "0.4", features = ["arbitrary-derive"] } -serde = { version = "1.0.136", features = ["derive"] } +serde = { version = "1.0", features = ["derive"] } serde_bytes = "0.11.12" [dependencies.tw_encoding] diff --git a/rust/tw_encoding/src/ffi.rs b/rust/tw_encoding/src/ffi.rs index 83f830793bd..1be7b07bc48 100644 --- a/rust/tw_encoding/src/ffi.rs +++ b/rust/tw_encoding/src/ffi.rs @@ -17,6 +17,7 @@ pub enum CEncodingCode { Ok = 0, InvalidInput = 1, InvalidAlphabet = 2, + Internal = 3, } impl From for CEncodingCode { @@ -24,6 +25,7 @@ impl From for CEncodingCode { match error { EncodingError::InvalidInput => CEncodingCode::InvalidInput, EncodingError::InvalidAlphabet => CEncodingCode::InvalidAlphabet, + EncodingError::Internal => CEncodingCode::Internal, } } } diff --git a/rust/tw_encoding/src/hex.rs b/rust/tw_encoding/src/hex.rs index 38dc8b2e44f..b43ca64d979 100644 --- a/rust/tw_encoding/src/hex.rs +++ b/rust/tw_encoding/src/hex.rs @@ -5,6 +5,7 @@ // file LICENSE at the root of the source code distribution tree. pub use hex::FromHexError; +use serde::{Serialize, Serializer}; use tw_memory::Data; pub type FromHexResult = Result; @@ -65,6 +66,15 @@ pub fn encode>(data: T, prefixed: bool) -> String { encoded } +/// Serializes the `value` as a hex. +pub fn as_hex(value: &T, serializer: S) -> Result +where + T: ToHex, + S: Serializer, +{ + value.to_hex().serialize(serializer) +} + #[cfg(test)] mod tests { use super::*; diff --git a/rust/tw_encoding/src/lib.rs b/rust/tw_encoding/src/lib.rs index 871a6a42887..ed1a7002451 100644 --- a/rust/tw_encoding/src/lib.rs +++ b/rust/tw_encoding/src/lib.rs @@ -19,4 +19,5 @@ pub type EncodingResult = Result; pub enum EncodingError { InvalidInput, InvalidAlphabet, + Internal, } diff --git a/rust/tw_evm/Cargo.toml b/rust/tw_evm/Cargo.toml index 43c3f334c72..857a8f1bd5a 100644 --- a/rust/tw_evm/Cargo.toml +++ b/rust/tw_evm/Cargo.toml @@ -7,8 +7,8 @@ edition = "2021" itertools = "0.10.5" lazy_static = "1.4.0" rlp = "0.5.2" -serde = { version = "1.0.159", features = ["derive"] } -serde_json = "1.0.95" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" tw_coin_entry = { path = "../tw_coin_entry" } tw_encoding = { path = "../tw_encoding" } tw_hash = { path = "../tw_hash" } diff --git a/rust/tw_evm/fuzz/Cargo.toml b/rust/tw_evm/fuzz/Cargo.toml index e1ac2e6c65c..e008ae78164 100644 --- a/rust/tw_evm/fuzz/Cargo.toml +++ b/rust/tw_evm/fuzz/Cargo.toml @@ -9,7 +9,7 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = { version = "0.4", features = ["arbitrary-derive"] } -serde_json = "1.0.96" +serde_json = "1.0" tw_number = { path = "../../tw_number" } tw_proto = { path = "../../tw_proto", features = ["fuzz"] } diff --git a/rust/tw_hash/Cargo.toml b/rust/tw_hash/Cargo.toml index 9d22a32c414..2e1740b15c1 100644 --- a/rust/tw_hash/Cargo.toml +++ b/rust/tw_hash/Cargo.toml @@ -14,7 +14,7 @@ digest = "0.10.6" groestl = "0.10.1" hmac = "0.12.1" ripemd = "0.1.3" -serde = { version = "1.0.159", features = ["derive"], optional = true } +serde = { version = "1.0", features = ["derive"], optional = true } sha1 = "0.10.5" sha2 = "0.10.6" sha3 = "0.10.6" @@ -23,4 +23,4 @@ tw_memory = { path = "../tw_memory" } zeroize = "1.6.0" [dev-dependencies] -serde_json = "1.0.95" +serde_json = "1.0" diff --git a/rust/tw_internet_computer/Cargo.toml b/rust/tw_internet_computer/Cargo.toml index 7fd375e1071..e40261494e7 100644 --- a/rust/tw_internet_computer/Cargo.toml +++ b/rust/tw_internet_computer/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] quick-protobuf = "0.8.1" -serde = { version = "1.0.136", features = ["derive"] } +serde = { version = "1.0", features = ["derive"] } tw_coin_entry = { path = "../tw_coin_entry" } tw_encoding = { path = "../tw_encoding" } tw_hash = { path = "../tw_hash" } diff --git a/rust/tw_keypair/Cargo.toml b/rust/tw_keypair/Cargo.toml index 52b0a350fa8..d29ab7b89b3 100644 --- a/rust/tw_keypair/Cargo.toml +++ b/rust/tw_keypair/Cargo.toml @@ -9,7 +9,7 @@ test-utils = [] [dependencies] arbitrary = { version = "1", features = ["derive"], optional = true } lazy_static = "1.4.0" -serde = { version = "1.0.159", features = ["derive"] } +serde = { version = "1.0", features = ["derive"] } starknet-crypto = "0.5.0" starknet-ff = "0.3.2" tw_encoding = { path = "../tw_encoding" } @@ -31,7 +31,7 @@ digest = "0.9.0" sha2 = "0.9" [dev-dependencies] -serde_json = "1.0.95" +serde_json = "1.0" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] ring = "0.16.20" diff --git a/rust/tw_keypair/src/ecdsa/signature.rs b/rust/tw_keypair/src/ecdsa/signature.rs index 64fe61bfe83..7660bc12a0c 100644 --- a/rust/tw_keypair/src/ecdsa/signature.rs +++ b/rust/tw_keypair/src/ecdsa/signature.rs @@ -8,7 +8,7 @@ use crate::ecdsa::EcdsaCurve; use crate::{KeyPairError, KeyPairResult}; use ecdsa::elliptic_curve::FieldBytes; use std::ops::{Range, RangeInclusive}; -use tw_hash::{H256, H520}; +use tw_hash::{H256, H512, H520}; use tw_misc::traits::ToBytesVec; /// Represents an ECDSA signature. @@ -118,7 +118,26 @@ impl<'a, C: EcdsaCurve> TryFrom<&'a [u8]> for Signature { /// To verify the signature, it's enough to check `r` and `s` parts without the recovery ID. pub struct VerifySignature { - pub signature: ecdsa::Signature, + pub(crate) signature: ecdsa::Signature, +} + +impl VerifySignature { + /// Returns a standard binary signature representation: + /// RS, where R - 32 byte array, S - 32 byte array. + pub fn to_bytes(&self) -> H512 { + let (r, s) = self.signature.split_bytes(); + + let mut dest = H512::default(); + dest[Signature::::R_RANGE].copy_from_slice(r.as_slice()); + dest[Signature::::S_RANGE].copy_from_slice(s.as_slice()); + dest + } +} + +impl ToBytesVec for VerifySignature { + fn to_vec(&self) -> Vec { + self.to_bytes().to_vec() + } } impl<'a, C: EcdsaCurve> TryFrom<&'a [u8]> for VerifySignature { diff --git a/rust/tw_misc/Cargo.toml b/rust/tw_misc/Cargo.toml index 60fe9a1033d..13747bab355 100644 --- a/rust/tw_misc/Cargo.toml +++ b/rust/tw_misc/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" test-utils = ["serde", "serde_json"] [dependencies] -serde = { version = "1.0.163", features = ["derive"], optional = true } -serde_json = { version = "1.0.96", optional = true } +serde = { version = "1.0", features = ["derive"], optional = true } +serde_json = { version = "1.0", optional = true } zeroize = "1.6.0" diff --git a/rust/tw_misc/src/lib.rs b/rust/tw_misc/src/lib.rs index a6017087f76..80fa96cffc9 100644 --- a/rust/tw_misc/src/lib.rs +++ b/rust/tw_misc/src/lib.rs @@ -5,6 +5,7 @@ // file LICENSE at the root of the source code distribution tree. pub mod macros; +pub mod serde; #[cfg(feature = "test-utils")] pub mod test_utils; pub mod traits; diff --git a/rust/tw_misc/src/serde.rs b/rust/tw_misc/src/serde.rs new file mode 100644 index 00000000000..0e1e85d5256 --- /dev/null +++ b/rust/tw_misc/src/serde.rs @@ -0,0 +1,16 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde::{Serialize, Serializer}; + +/// Serializes the `value` as a string. +pub fn as_string(value: &T, serializer: S) -> Result +where + T: ToString, + S: Serializer, +{ + value.to_string().serialize(serializer) +} diff --git a/rust/tw_number/Cargo.toml b/rust/tw_number/Cargo.toml index 3a180315669..d76f8b6ed6d 100644 --- a/rust/tw_number/Cargo.toml +++ b/rust/tw_number/Cargo.toml @@ -12,7 +12,7 @@ helpers = [] arbitrary = { version = "1", features = ["derive"], optional = true } lazy_static = "1.4.0" primitive-types = "0.12.1" -serde = { version = "1.0.159", features = ["derive"], optional = true } +serde = { version = "1.0", features = ["derive"], optional = true } tw_hash = { path = "../tw_hash" } tw_memory = { path = "../tw_memory" } diff --git a/rust/wallet_core_rs/Cargo.toml b/rust/wallet_core_rs/Cargo.toml index 37b8c878d1d..a72042ac6cb 100644 --- a/rust/wallet_core_rs/Cargo.toml +++ b/rust/wallet_core_rs/Cargo.toml @@ -28,7 +28,7 @@ tw_misc = { path = "../tw_misc" } tw_proto = { path = "../tw_proto" } [dev-dependencies] -serde_json = "1.0.96" +serde_json = "1.0" tw_any_coin = { path = "../tw_any_coin", features = ["test-utils"] } tw_memory = { path = "../tw_memory", features = ["test-utils"] } tw_number = { path = "../tw_number", features = ["helpers"] } diff --git a/samples/go/core/transactionHelper.go b/samples/go/core/transactionHelper.go index 6062be8b753..df2a78c64dd 100644 --- a/samples/go/core/transactionHelper.go +++ b/samples/go/core/transactionHelper.go @@ -6,25 +6,6 @@ package core import "C" import "tw/types" -func BuildInput(c CoinType, from, to string, amount string, asset string, memo string, chainId string) []byte { - fromStr := types.TWStringCreateWithGoString(from) - defer C.TWStringDelete(fromStr) - toStr := types.TWStringCreateWithGoString(to) - defer C.TWStringDelete(toStr) - amountStr := types.TWStringCreateWithGoString(amount) - defer C.TWStringDelete(amountStr) - assetStr := types.TWStringCreateWithGoString(asset) - defer C.TWStringDelete(assetStr) - memoStr := types.TWStringCreateWithGoString(memo) - defer C.TWStringDelete(memoStr) - chainIdStr := types.TWStringCreateWithGoString(chainId) - defer C.TWStringDelete(chainIdStr) - - result := C.TWTransactionCompilerBuildInput(C.enum_TWCoinType(c), fromStr, toStr, amountStr, assetStr, memoStr, chainIdStr) - defer C.TWDataDelete(result) - return types.TWDataGoBytes(result) -} - func PreImageHashes(c CoinType, txInputData []byte) []byte { input := types.TWDataCreateWithGoBytes(txInputData) defer C.TWDataDelete(input) diff --git a/samples/go/sample/external_signing.go b/samples/go/sample/external_signing.go index 161de4380db..0cbf729f812 100644 --- a/samples/go/sample/external_signing.go +++ b/samples/go/sample/external_signing.go @@ -25,16 +25,37 @@ func SignExternalBinanceDemo() { coin := core.CoinTypeBinance + // bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2 + fromAddress, _ := hex.DecodeString("40c2979694bbc961023d1d27be6fc4d21a9febe6") + // bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5 + toAddress, _ := hex.DecodeString("bffe47abfaede50419c577f1074fee6dd1535cd1") + + inOutToken := binance.SendOrder_Token { + Denom: "BNB", + Amount: 1, + } + + orderInput := binance.SendOrder_Input { + Address: fromAddress, + Coins: []*binance.SendOrder_Token{&inOutToken}, + } + orderOutput := binance.SendOrder_Output { + Address: toAddress, + Coins: []*binance.SendOrder_Token{&inOutToken}, + } + + input := binance.SigningInput { + ChainId: "Binance-Chain-Nile", + OrderOneof: &binance.SigningInput_SendOrder { + SendOrder: &binance.SendOrder { + Inputs: []*binance.SendOrder_Input{&orderInput}, + Outputs: []*binance.SendOrder_Output{&orderOutput}, + }, + }, + } + fmt.Println("\n==> Step 1: Prepare transaction input (protobuf)") - txInputData := core.BuildInput( - coin, - "bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", // from - "bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5", // to - "1", // amount - "BNB", // asset - "", // memo - "", // chainId - ) + txInputData, _ := proto.Marshal(&input) fmt.Println("txInputData len: ", len(txInputData)) fmt.Println("\n==> Step 2: Obtain preimage hash") diff --git a/src/Binance/Entry.cpp b/src/Binance/Entry.cpp index 75ed2a72e9e..3d65666ba7f 100644 --- a/src/Binance/Entry.cpp +++ b/src/Binance/Entry.cpp @@ -6,119 +6,18 @@ #include "Entry.h" -#include "../proto/TransactionCompiler.pb.h" -#include "Address.h" -#include "Coin.h" -#include "Signer.h" +#include "HexCoding.h" +#include "proto/Binance.pb.h" namespace TW::Binance { -bool Entry::validateAddress(TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { - if (std::holds_alternative(addressPrefix)) { - if (const auto* hrp = std::get(addressPrefix); hrp) { - return Address::isValid(address, hrp); - } - } - return Address::isValid(coin, address); -} - -std::string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, const PrefixVariant& addressPrefix) const { - const std::string hrp = getFromPrefixHrpOrDefault(addressPrefix, coin); - return Address(publicKey, hrp).string(); -} - -Data Entry::addressToData(TWCoinType coin, const std::string& address) const { - const char* hrp = stringForHRP(TW::hrp(coin)); - Address addr(hrp); - if (Address::decode(address, addr)) { - return addr.getKeyHash(); - } - return {}; -} - -void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - auto input = Proto::SigningInput(); - input.ParseFromArray(dataIn.data(), (int)dataIn.size()); - const char* hrp = stringForHRP(TW::hrp(coin)); - auto serializedOut = Signer::sign(input, hrp).SerializeAsString(); - dataOut.insert(dataOut.end(), serializedOut.begin(), serializedOut.end()); -} - std::string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { - const char* hrp = stringForHRP(TW::hrp(coin)); - return Signer::signJSON(json, key, hrp); -} - -Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { - const char* hrp = stringForHRP(TW::hrp(coin)); - - return txCompilerTemplate( - txInputData, [hrp](const auto& input, auto& output) { - Signer signer(input, hrp); - - auto preImageHash = signer.preImageHash(); - auto preImage = signer.signaturePreimage(); - output.set_data_hash(preImageHash.data(), preImageHash.size()); - output.set_data(preImage.data(), preImage.size()); - }); -} - -void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { - const char* hrp = stringForHRP(TW::hrp(coin)); - - dataOut = txCompilerSingleTemplate( - txInputData, signatures, publicKeys, - [hrp](const auto& input, auto& output, const auto& signature, const auto& publicKey) { - output = Signer(input, hrp).compile(signature, publicKey); - }); -} - -Data Entry::buildTransactionInput(TWCoinType coinType, const std::string& from, const std::string& to, const uint256_t& amount, const std::string& asset, const std::string& memo, const std::string& chainId) const { - auto input = Proto::SigningInput(); - input.set_chain_id(chainId); - input.set_account_number(0); - input.set_sequence(0); - input.set_source(0); - input.set_memo(memo); - // do not set private_key! - input.set_private_key(""); - - auto& order = *input.mutable_send_order(); - - const char* hrp = stringForHRP(TW::hrp(coinType)); - - Data fromKeyhash; - Data toKeyhash; - - Address fromAddress(hrp); - if (!Address::decode(from, fromAddress)) { - throw std::invalid_argument("Invalid from address"); - } - fromKeyhash = fromAddress.getKeyHash(); - - Address toAddress(hrp); - if (!Address::decode(to, toAddress)) { - throw std::invalid_argument("Invalid to address"); - } - toKeyhash = toAddress.getKeyHash(); - - { - auto* sendOrderInputs = order.add_inputs(); - sendOrderInputs->set_address(fromKeyhash.data(), fromKeyhash.size()); - auto* inputCoin = sendOrderInputs->add_coins(); - inputCoin->set_denom(asset); - inputCoin->set_amount(static_cast(amount)); - } - { - auto* output = order.add_outputs(); - output->set_address(toKeyhash.data(), toKeyhash.size()); - auto* outputCoin = output->add_coins(); - outputCoin->set_denom(asset); - outputCoin->set_amount(static_cast(amount)); - } - - const auto txInputData = data(input.SerializeAsString()); - return txInputData; + return signJSONHelper( + coin, + json, + key, + [](const Proto::SigningOutput& output) { return hex(output.encoded()); } + ); } } // namespace TW::Binance diff --git a/src/Binance/Entry.h b/src/Binance/Entry.h index f921b085055..579bdba74be 100644 --- a/src/Binance/Entry.h +++ b/src/Binance/Entry.h @@ -6,24 +6,16 @@ #pragma once -#include "../CoinEntry.h" +#include "rust/RustCoinEntry.h" namespace TW::Binance { /// Binance entry dispatcher. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry final : public CoinEntry { +class Entry final : public Rust::RustCoinEntryWithSignJSON { public: - bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - Data addressToData(TWCoinType coin, const std::string& address) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; - bool supportsJSONSigning() const { return true; } - std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const; - - 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; - Data buildTransactionInput(TWCoinType coinType, const std::string& from, const std::string& to, const uint256_t& amount, const std::string& asset, const std::string& memo, const std::string& chainId) const; + bool supportsJSONSigning() const override { return true; } + std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const override; }; } // namespace TW::Binance diff --git a/src/Binance/Serialization.cpp b/src/Binance/Serialization.cpp deleted file mode 100644 index 2ceeaca9df7..00000000000 --- a/src/Binance/Serialization.cpp +++ /dev/null @@ -1,198 +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 "Serialization.h" - -#include "Address.h" -#include "Bech32Address.h" -#include "Ethereum/Address.h" -#include "../HexCoding.h" - -namespace TW::Binance { - -using json = nlohmann::json; - -static inline std::string addressString(const std::string& bytes, const std::string& chainHrp) { - auto data = Data(bytes.begin(), bytes.end()); - return Address(data, chainHrp).string(); -} - -static inline std::string validatorAddress(const std::string& bytes) { - auto data = Data(bytes.begin(), bytes.end()); - return Bech32Address(Address::hrpValidator, data).string(); -} - -json signatureJSON(const Proto::SigningInput& input, const std::string& chainHrp) { - json j; - j["account_number"] = std::to_string(input.account_number()); - j["chain_id"] = input.chain_id(); - j["data"] = nullptr; - j["memo"] = input.memo(); - j["msgs"] = json::array({orderJSON(input, chainHrp)}); - j["sequence"] = std::to_string(input.sequence()); - j["source"] = std::to_string(input.source()); - return j; -} - -json orderJSON(const Proto::SigningInput& input, const std::string& chainHrp) { - json j; - if (input.has_trade_order()) { - j["id"] = input.trade_order().id(); - j["ordertype"] = 2; - j["price"] = input.trade_order().price(); - j["quantity"] = input.trade_order().quantity(); - j["sender"] = addressString(input.trade_order().sender(), chainHrp); - j["side"] = input.trade_order().side(); - j["symbol"] = input.trade_order().symbol(); - j["timeinforce"] = input.trade_order().timeinforce(); - } else if (input.has_cancel_trade_order()) { - j["refid"] = input.cancel_trade_order().refid(); - j["sender"] = addressString(input.cancel_trade_order().sender(), chainHrp); - j["symbol"] = input.cancel_trade_order().symbol(); - } else if (input.has_send_order()) { - j["inputs"] = inputsJSON(input.send_order(), chainHrp); - j["outputs"] = outputsJSON(input.send_order(), chainHrp); - } else if (input.has_freeze_order()) { - j["from"] = addressString(input.freeze_order().from(), chainHrp); - j["symbol"] = input.freeze_order().symbol(); - j["amount"] = input.freeze_order().amount(); - } else if (input.has_unfreeze_order()) { - j["from"] = addressString(input.unfreeze_order().from(), chainHrp); - j["symbol"] = input.unfreeze_order().symbol(); - j["amount"] = input.unfreeze_order().amount(); - } else if (input.has_htlt_order()) { - j["from"] = addressString(input.htlt_order().from(), chainHrp); - j["to"] = addressString(input.htlt_order().to(), chainHrp); - j["recipient_other_chain"] = input.htlt_order().recipient_other_chain(); - j["sender_other_chain"] = input.htlt_order().sender_other_chain(); - j["random_number_hash"] = hex(input.htlt_order().random_number_hash()); - j["timestamp"] = input.htlt_order().timestamp(); - j["amount"] = tokensJSON(input.htlt_order().amount()); - j["expected_income"] = input.htlt_order().expected_income(); - j["height_span"] = input.htlt_order().height_span(); - j["cross_chain"] = input.htlt_order().cross_chain(); - } else if (input.has_deposithtlt_order()) { - j["from"] = addressString(input.deposithtlt_order().from(), chainHrp); - j["swap_id"] = hex(input.deposithtlt_order().swap_id()); - j["amount"] = tokensJSON(input.deposithtlt_order().amount()); - } else if (input.has_claimhtlt_order()) { - j["from"] = addressString(input.claimhtlt_order().from(), chainHrp); - j["swap_id"] = hex(input.claimhtlt_order().swap_id()); - j["random_number"] = hex(input.claimhtlt_order().random_number()); - } else if (input.has_refundhtlt_order()) { - j["from"] = addressString(input.refundhtlt_order().from(), chainHrp); - j["swap_id"] = hex(input.refundhtlt_order().swap_id()); - } else if (input.has_issue_order()) { - j["from"] = addressString(input.issue_order().from(), chainHrp); - j["total_supply"] = input.issue_order().total_supply(); - j["name"] = input.issue_order().name(); - j["symbol"] = input.issue_order().symbol(); - j["mintable"] = input.issue_order().mintable(); - } else if (input.has_mint_order()) { - j["from"] = addressString(input.mint_order().from(), chainHrp); - j["amount"] = input.mint_order().amount(); - j["symbol"] = input.mint_order().symbol(); - } else if (input.has_burn_order()) { - j["from"] = addressString(input.burn_order().from(), chainHrp); - j["amount"] = input.burn_order().amount(); - j["symbol"] = input.burn_order().symbol(); - } else if (input.has_transfer_out_order()) { - auto to = input.transfer_out_order().to(); - auto addr = Ethereum::Address(Data(to.begin(), to.end())); - j["from"] = addressString(input.transfer_out_order().from(), chainHrp); - j["to"] = addr.string(); - j["amount"] = tokenJSON(input.transfer_out_order().amount()); - j["expire_time"] = input.transfer_out_order().expire_time(); - } else if (input.has_side_delegate_order()) { - j["type"] = "cosmos-sdk/MsgSideChainDelegate"; - j["value"] = { - {"delegator_addr", addressString(input.side_delegate_order().delegator_addr(), chainHrp)}, - {"validator_addr",validatorAddress(input.side_delegate_order().validator_addr())}, - {"delegation", tokenJSON(input.side_delegate_order().delegation(), true)}, - {"side_chain_id", input.side_delegate_order().chain_id()}, - }; - } else if (input.has_side_redelegate_order()) { - j["type"] = "cosmos-sdk/MsgSideChainRedelegate"; - j["value"] = { - {"delegator_addr", addressString(input.side_redelegate_order().delegator_addr(), chainHrp)}, - {"validator_src_addr", validatorAddress(input.side_redelegate_order().validator_src_addr())}, - {"validator_dst_addr", validatorAddress(input.side_redelegate_order().validator_dst_addr())}, - {"amount", tokenJSON(input.side_redelegate_order().amount(), true)}, - {"side_chain_id", input.side_redelegate_order().chain_id()}, - }; - } else if (input.has_side_undelegate_order()) { - j["type"] = "cosmos-sdk/MsgSideChainUndelegate"; - j["value"] = { - {"delegator_addr", addressString(input.side_undelegate_order().delegator_addr(), chainHrp)}, - {"validator_addr", validatorAddress(input.side_undelegate_order().validator_addr())}, - {"amount", tokenJSON(input.side_undelegate_order().amount(), true)}, - {"side_chain_id", input.side_undelegate_order().chain_id()}, - }; - } else if (input.has_time_lock_order()) { - j["from"] = addressString(input.time_lock_order().from_address(), chainHrp); - j["description"] = input.time_lock_order().description(); - j["amount"] = tokensJSON(input.time_lock_order().amount()); - j["lock_time"] = input.time_lock_order().lock_time(); - } else if (input.has_time_relock_order()) { - const auto amount = input.time_relock_order().amount(); - j["from"] = addressString(input.time_relock_order().from_address(), chainHrp); - j["time_lock_id"] = input.time_relock_order().id(); - j["description"] = input.time_relock_order().description(); - // if amount is empty or omitted, set null to avoid signature verification error - j["amount"] = nullptr; - if (!amount.empty()) { - j["amount"] = tokensJSON(amount); - } - j["lock_time"] = input.time_relock_order().lock_time(); - } else if (input.has_time_unlock_order()) { - j["from"] = addressString(input.time_unlock_order().from_address(), chainHrp); - j["time_lock_id"] = input.time_unlock_order().id(); - } - - return j; -} - -json inputsJSON(const Proto::SendOrder& order, const std::string& chainHrp) { - json j = json::array(); - for (auto& input : order.inputs()) { - j.push_back({ - {"address", addressString(input.address(), chainHrp)}, - {"coins", tokensJSON(input.coins())} - }); - } - return j; -} - -json outputsJSON(const Proto::SendOrder& order, const std::string& chainHrp) { - json j = json::array(); - for (auto& output : order.outputs()) { - j.push_back({ - {"address", addressString(output.address(), chainHrp)}, - {"coins", tokensJSON(output.coins())} - }); - } - return j; -} - -json tokenJSON(const Proto::SendOrder_Token& token, bool stringAmount) { - json j = {{"denom", token.denom()}}; - if (stringAmount) { - j["amount"] = std::to_string(token.amount()); - } else { - j["amount"] = token.amount(); - } - return j; -} - -json tokensJSON(const google::protobuf::RepeatedPtrField& tokens) { - json j = json::array(); - for (auto& token : tokens) { - j.push_back(tokenJSON(token)); - } - return j; -} - -} // namespace TW::Binance diff --git a/src/Binance/Serialization.h b/src/Binance/Serialization.h deleted file mode 100644 index a49046eb035..00000000000 --- a/src/Binance/Serialization.h +++ /dev/null @@ -1,21 +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 "../proto/Binance.pb.h" -#include - -namespace TW::Binance { - -nlohmann::json signatureJSON(const Proto::SigningInput& input, const std::string& chainHrp); -nlohmann::json orderJSON(const Proto::SigningInput& input, const std::string& chainHrp); -nlohmann::json inputsJSON(const Proto::SendOrder& order, const std::string& chainHrp); -nlohmann::json outputsJSON(const Proto::SendOrder& order, const std::string& chainHrp); -nlohmann::json tokenJSON(const Proto::SendOrder_Token& token, bool stringAmount = false); -nlohmann::json tokensJSON(const ::google::protobuf::RepeatedPtrField& tokens); - -} // namespace TW::Binance diff --git a/src/Binance/Signer.cpp b/src/Binance/Signer.cpp deleted file mode 100644 index 3b0856428d3..00000000000 --- a/src/Binance/Signer.cpp +++ /dev/null @@ -1,227 +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 "Serialization.h" -#include "../HexCoding.h" -#include "../PrivateKey.h" -#include "../Coin.h" - -#include - -#include -#include -#include - -#include - -namespace TW::Binance { - -// Message prefixes -// see https://docs.binance.org/api-reference/transactions.html#amino-types -static const auto sendOrderPrefix = Data{0x2A, 0x2C, 0x87, 0xFA}; -static const auto tradeOrderPrefix = Data{0xCE, 0x6D, 0xC0, 0x43}; -static const auto cancelTradeOrderPrefix = Data{0x16, 0x6E, 0x68, 0x1B}; -static const auto HTLTOrderPrefix = Data{0xB3, 0x3F, 0x9A, 0x24}; -static const auto depositHTLTOrderPrefix = Data{0x63, 0x98, 0x64, 0x96}; -static const auto claimHTLTOrderPrefix = Data{0xC1, 0x66, 0x53, 0x00}; -static const auto refundHTLTOrderPrefix = Data{0x34, 0x54, 0xA2, 0x7C}; -static const auto pubKeyPrefix = Data{0xEB, 0x5A, 0xE9, 0x87}; -static const auto transactionPrefix = Data{0xF0, 0x62, 0x5D, 0xEE}; -static const auto tokenIssueOrderPrefix = Data{0x17, 0xEF, 0xAB, 0x80}; -static const auto tokenMintOrderPrefix = Data{0x46, 0x7E, 0x08, 0x29}; -static const auto tokenBurnOrderPrefix = Data{0x7E, 0xD2, 0xD2, 0xA0}; -static const auto tokenFreezeOrderPrefix = Data{0xE7, 0x74, 0xB3, 0x2D}; -static const auto tokenUnfreezeOrderPrefix = Data{0x65, 0x15, 0xFF, 0x0D}; -static const auto transferOutOrderPrefix = Data{0x80, 0x08, 0x19, 0xC0}; -static const auto sideDelegateOrderPrefix = Data{0xE3, 0xA0, 0x7F, 0xD2}; -static const auto sideRedelegateOrderPrefix = Data{0xE3, 0xCE, 0xD3, 0x64}; -static const auto sideUndelegateOrderPrefix = Data{0x51, 0x4F, 0x7E, 0x0E}; -static const auto timeLockOrderPrefix = Data{0x07, 0x92, 0x15, 0x31}; -static const auto timeRelockOrderPrefix = Data{0x50, 0x47, 0x11, 0xDA}; -static const auto timeUnlockOrderPrefix = Data{0xC4, 0x05, 0x0C, 0x6C}; - -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input, const std::string& chainHrp) noexcept { - auto signer = Signer(input, chainHrp); - auto encoded = signer.build(); - auto output = Proto::SigningOutput(); - output.set_encoded(encoded.data(), encoded.size()); - return output; -} - -std::string Signer::signJSON(const std::string& json, const Data& key, const std::string& chainHrp) { - auto input = Proto::SigningInput(); - google::protobuf::util::JsonStringToMessage(json, &input); - input.set_private_key(key.data(), key.size()); - auto output = Signer::sign(input, chainHrp); - return hex(output.encoded()); -} - -Signer::Signer(Proto::SigningInput input, TWCoinType coin): - input(std::move(input)), - chainHrp(stringForHRP(TW::hrp(coin))) { -} - -Data Signer::build() const { - auto signature = encodeSignature(sign()); - return encodeTransaction(signature); -} - -Data Signer::sign() const { - auto hash = preImageHash(); - auto key = PrivateKey(input.private_key()); - auto signature = key.sign(hash, TWCurveSECP256k1); - return {signature.begin(), signature.end() - 1}; -} - -Data Signer::preImageHash() const { - return Hash::sha256(signaturePreimage()); -} - -Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& publicKey) const { - // validate public key - if (publicKey.type != TWPublicKeyTypeSECP256k1) { - throw std::invalid_argument("Invalid public key"); - } - { - // validate correctness of signature - const auto hash = this->preImageHash(); - if (!publicKey.verify(signature, hash)) { - throw std::invalid_argument("Invalid signature/hash/publickey combination"); - } - } - const auto encoded = encodeTransaction(encodeSignature(signature, publicKey)); - auto output = Proto::SigningOutput(); - output.set_encoded(encoded.data(), encoded.size()); - return output; -} - -std::string Signer::signaturePreimage() const { - auto json = signatureJSON(input, this->chainHrp); - return json.dump(); -} - -Data Signer::encodeTransaction(const Data& signature) const { - auto msg = encodeOrder(); - auto transaction = Binance::Proto::Transaction(); - transaction.add_msgs(msg.data(), msg.size()); - transaction.add_signatures(signature.data(), signature.size()); - transaction.set_memo(input.memo()); - transaction.set_source(input.source()); - - auto data = transaction.SerializeAsString(); - return aminoWrap(data, transactionPrefix, true); -} - -Data Signer::encodeOrder() const { - std::string data; - Data prefix; - if (input.has_trade_order()) { - data = input.trade_order().SerializeAsString(); - prefix = tradeOrderPrefix; - } else if (input.has_cancel_trade_order()) { - data = input.cancel_trade_order().SerializeAsString(); - prefix = cancelTradeOrderPrefix; - } else if (input.has_send_order()) { - data = input.send_order().SerializeAsString(); - prefix = sendOrderPrefix; - } else if (input.has_issue_order()) { - data = input.issue_order().SerializeAsString(); - prefix = tokenIssueOrderPrefix; - } else if (input.has_mint_order()) { - data = input.mint_order().SerializeAsString(); - prefix = tokenMintOrderPrefix; - } else if (input.has_burn_order()) { - data = input.burn_order().SerializeAsString(); - prefix = tokenBurnOrderPrefix; - } else if (input.has_freeze_order()) { - data = input.freeze_order().SerializeAsString(); - prefix = tokenFreezeOrderPrefix; - } else if (input.has_unfreeze_order()) { - data = input.unfreeze_order().SerializeAsString(); - prefix = tokenUnfreezeOrderPrefix; - } else if (input.has_htlt_order()) { - data = input.htlt_order().SerializeAsString(); - prefix = HTLTOrderPrefix; - } else if (input.has_deposithtlt_order()) { - data = input.deposithtlt_order().SerializeAsString(); - prefix = depositHTLTOrderPrefix; - } else if (input.has_claimhtlt_order()) { - data = input.claimhtlt_order().SerializeAsString(); - prefix = claimHTLTOrderPrefix; - } else if (input.has_refundhtlt_order()) { - data = input.refundhtlt_order().SerializeAsString(); - prefix = refundHTLTOrderPrefix; - } else if (input.has_transfer_out_order()) { - data = input.transfer_out_order().SerializeAsString(); - prefix = transferOutOrderPrefix; - } else if (input.has_side_delegate_order()) { - data = input.side_delegate_order().SerializeAsString(); - prefix = sideDelegateOrderPrefix; - } else if (input.has_side_redelegate_order()) { - data = input.side_redelegate_order().SerializeAsString(); - prefix = sideRedelegateOrderPrefix; - } else if (input.has_side_undelegate_order()) { - data = input.side_undelegate_order().SerializeAsString(); - prefix = sideUndelegateOrderPrefix; - } else if (input.has_time_lock_order()) { - data = input.time_lock_order().SerializeAsString(); - prefix = timeLockOrderPrefix; - } else if (input.has_time_relock_order()) { - data = input.time_relock_order().SerializeAsString(); - prefix = timeRelockOrderPrefix; - } else if (input.has_time_unlock_order()) { - data = input.time_unlock_order().SerializeAsString(); - prefix = timeUnlockOrderPrefix; - } else { - return {}; - } - return aminoWrap(data, prefix, false); -} - -Data Signer::encodeSignature(const Data& signature) const { - auto key = PrivateKey(input.private_key()); - auto publicKey = key.getPublicKey(TWPublicKeyTypeSECP256k1); - return encodeSignature(signature, publicKey); -} - -Data Signer::encodeSignature(const Data& signature, const PublicKey& publicKey) const { - auto encodedPublicKey = pubKeyPrefix; - encodedPublicKey.insert(encodedPublicKey.end(), static_cast(publicKey.bytes.size())); - encodedPublicKey.insert(encodedPublicKey.end(), publicKey.bytes.begin(), publicKey.bytes.end()); - - auto object = Binance::Proto::Signature(); - object.set_pub_key(encodedPublicKey.data(), encodedPublicKey.size()); - object.set_signature(signature.data(), signature.size()); - object.set_account_number(input.account_number()); - object.set_sequence(input.sequence()); - - return aminoWrap(object.SerializeAsString(), {}, false); -} - -Data Signer::aminoWrap(const std::string& raw, const Data& typePrefix, bool prefixWithSize) const { - const auto contentsSize = raw.size() + typePrefix.size(); - auto size = contentsSize; - if (prefixWithSize) { - size += 10; - } - - std::string msg; - msg.reserve(size); - { - google::protobuf::io::StringOutputStream output(&msg); - google::protobuf::io::CodedOutputStream cos(&output); - if (prefixWithSize) { - cos.WriteVarint64(contentsSize); - } - cos.WriteRaw(typePrefix.data(), static_cast(typePrefix.size())); - cos.WriteRaw(raw.data(), static_cast(raw.size())); - } - - return {msg.begin(), msg.end()}; -} - -} // namespace TW::Binance diff --git a/src/Binance/Signer.h b/src/Binance/Signer.h deleted file mode 100644 index 88d462f1f6f..00000000000 --- a/src/Binance/Signer.h +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Data.h" -#include "PublicKey.h" -#include "../proto/Binance.pb.h" - -#include -#include -#include - -namespace TW::Binance { - -/// Helper class that performs Binance transaction signing. -class Signer { - public: - /// Signs a Proto::SigningInput transaction - static Proto::SigningOutput sign(const Proto::SigningInput& input, const std::string& chainHrp) noexcept; - /// Signs a json Proto::SigningInput with private key - static std::string signJSON(const std::string& json, const Data& key, const std::string& chainHrp); - public: - Proto::SigningInput input; - std::string chainHrp; - - /// Initializes a transaction signer. - explicit Signer(Proto::SigningInput input, std::string chainHrp): - input(std::move(input)), - chainHrp(std::move(chainHrp)) { - } - - /// Initializes a transaction signer. - explicit Signer(Proto::SigningInput input, TWCoinType coin = TWCoinTypeBinance); - - /// Builds a signed transaction. - /// - /// \returns the signed transaction data or an empty vector if there is an - /// error. - TW::Data build() const; - - /// Signs the transaction. - /// - /// \returns the transaction signature or an empty vector if there is an - /// error. - TW::Data sign() const; - - TW::Data preImageHash() const; - Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) const; - std::string signaturePreimage() const; - - private: - TW::Data encodeTransaction(const TW::Data& signature) const; - TW::Data encodeOrder() const; - TW::Data encodeSignature(const TW::Data& signature) const; - TW::Data encodeSignature(const TW::Data& signature, const PublicKey& publicKey) const; - TW::Data aminoWrap(const std::string& raw, const TW::Data& typePrefix, - bool isPrefixLength) const; -}; - -} // namespace TW::Binance diff --git a/src/Coin.cpp b/src/Coin.cpp index 943324470a7..a9ccd452233 100644 --- a/src/Coin.cpp +++ b/src/Coin.cpp @@ -323,12 +323,6 @@ void TW::anyCoinCompileWithSignatures(TWCoinType coinType, const Data& txInputDa dispatcher->compile(coinType, txInputData, signatures, publicKeys, txOutputOut); } -Data TW::anyCoinBuildTransactionInput(TWCoinType coinType, const std::string& from, const std::string& to, const uint256_t& amount, const std::string& asset, const std::string& memo, const std::string& chainId) { - auto* dispatcher = coinDispatcher(coinType); - assert(dispatcher != nullptr); - return dispatcher->buildTransactionInput(coinType, from, to, amount, asset, memo, chainId); -} - // Coin info accessors extern const CoinInfo getCoinInfo(TWCoinType coin); // in generated CoinInfoData.cpp file diff --git a/src/Coin.h b/src/Coin.h index 3c2bc45ca89..63ac9f07fbe 100644 --- a/src/Coin.h +++ b/src/Coin.h @@ -126,8 +126,6 @@ Data anyCoinPreImageHashes(TWCoinType coinType, const Data& txInputData); void anyCoinCompileWithSignatures(TWCoinType coinType, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& txOutputOut); -Data anyCoinBuildTransactionInput(TWCoinType coinType, const std::string& from, const std::string& to, const uint256_t& amount, const std::string& asset, const std::string& memo, const std::string& chainId); - // Describes a derivation: path + optional format + optional name struct Derivation { TWDerivation name = TWDerivationDefault; diff --git a/src/CoinEntry.h b/src/CoinEntry.h index c8b0b81bbaf..2581fe17e7b 100644 --- a/src/CoinEntry.h +++ b/src/CoinEntry.h @@ -67,9 +67,6 @@ class CoinEntry { virtual Data preImageHashes([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const Data& txInputData) const { return {}; } // Optional method for compiling a transaction with externally-supplied signatures & pubkeys. virtual void compile([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const Data& txInputData, [[maybe_unused]] const std::vector& signatures, [[maybe_unused]] const std::vector& publicKeys, [[maybe_unused]] Data& dataOut) const {} - // Optional helper to prepare a SigningInput from simple parameters. - // Not suitable for UTXO chains. Some parameters, like chain-specific fee/gas paraemters, may need to be set in the SigningInput. - virtual Data buildTransactionInput([[maybe_unused]] TWCoinType coinType, [[maybe_unused]] const std::string& from, [[maybe_unused]] const std::string& to, [[maybe_unused]] const uint256_t& amount, [[maybe_unused]] const std::string& asset, [[maybe_unused]] const std::string& memo, [[maybe_unused]] const std::string& chainId) const { return Data(); } }; // In each coin's Entry.cpp the specific types of the coin are used, this template enforces the Signer implement: diff --git a/src/Cosmos/Entry.cpp b/src/Cosmos/Entry.cpp index 2886108b0d9..2d64e48b4ea 100644 --- a/src/Cosmos/Entry.cpp +++ b/src/Cosmos/Entry.cpp @@ -16,24 +16,13 @@ using namespace std; namespace TW::Cosmos { -// TODO call `signRustJSON` when it's done. string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { - auto input = Proto::SigningInput(); - google::protobuf::util::JsonStringToMessage(json, &input); - input.set_private_key(key.data(), key.size()); - - auto inputData = data(input.SerializeAsString()); - Data dataOut; - sign(coin, inputData, dataOut); - - if (dataOut.empty()) { - return {}; - } - - Proto::SigningOutput output; - output.ParseFromArray(dataOut.data(), static_cast(dataOut.size())); - - return output.json(); + return signJSONHelper( + coin, + json, + key, + [](const Proto::SigningOutput& output) { return output.json(); } + ); } } // namespace TW::Cosmos diff --git a/src/Cosmos/Entry.h b/src/Cosmos/Entry.h index 66b40cb1403..26cbc60fde8 100644 --- a/src/Cosmos/Entry.h +++ b/src/Cosmos/Entry.h @@ -12,7 +12,7 @@ namespace TW::Cosmos { /// Entry point for implementation of Cosmos coin. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry : public Rust::RustCoinEntry { +class Entry : public Rust::RustCoinEntryWithSignJSON { public: ~Entry() override = default; bool supportsJSONSigning() const final { return true; } diff --git a/src/Ethereum/Entry.cpp b/src/Ethereum/Entry.cpp index 78834004477..49d43c73ba7 100644 --- a/src/Ethereum/Entry.cpp +++ b/src/Ethereum/Entry.cpp @@ -10,28 +10,15 @@ #include "HexCoding.h" #include "proto/Ethereum.pb.h" -#include - namespace TW::Ethereum { -// TODO call `signRustJSON` when it's done. -std::string Entry::signJSON([[maybe_unused]] TWCoinType coin, const std::string& json, const Data& key) const { - auto input = Proto::SigningInput(); - google::protobuf::util::JsonStringToMessage(json, &input); - input.set_private_key(key.data(), key.size()); - - auto inputData = data(input.SerializeAsString()); - Data dataOut; - sign(coin, inputData, dataOut); - - if(dataOut.empty()) { - return {}; - } - - Proto::SigningOutput output; - output.ParseFromArray(dataOut.data(), static_cast(dataOut.size())); - - return hex(output.encoded()); +std::string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const { + return signJSONHelper( + coin, + json, + key, + [](const Proto::SigningOutput& output) { return hex(output.encoded()); } + ); } } // namespace TW::Ethereum diff --git a/src/Ethereum/Entry.h b/src/Ethereum/Entry.h index f2a5203a0d2..94b93c57792 100644 --- a/src/Ethereum/Entry.h +++ b/src/Ethereum/Entry.h @@ -12,7 +12,7 @@ namespace TW::Ethereum { /// Entry point for Ethereum and Ethereum-fork coins. /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file -class Entry : public Rust::RustCoinEntry { +class Entry : public Rust::RustCoinEntryWithSignJSON { public: bool supportsJSONSigning() const final { return true; } std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const final; diff --git a/src/TransactionCompiler.cpp b/src/TransactionCompiler.cpp index 721120c1259..5289bffb5b1 100644 --- a/src/TransactionCompiler.cpp +++ b/src/TransactionCompiler.cpp @@ -10,13 +10,6 @@ using namespace TW; - -Data TransactionCompiler::buildInput(TWCoinType coinType, const std::string& from, const std::string& to, const std::string& amount, const std::string& asset, const std::string& memo, const std::string& chainId) { - // parse amount - uint256_t amount256 { amount }; - return anyCoinBuildTransactionInput(coinType, from, to, amount256, asset, memo, chainId); -} - Data TransactionCompiler::preImageHashes(TWCoinType coinType, const Data& txInputData) { return anyCoinPreImageHashes(coinType, txInputData); } diff --git a/src/TransactionCompiler.h b/src/TransactionCompiler.h index 68688af0827..d4963d4c311 100644 --- a/src/TransactionCompiler.h +++ b/src/TransactionCompiler.h @@ -18,9 +18,6 @@ namespace TW { /// Non-core transaction utility methods, like building a transaction using an external signature class TransactionCompiler { public: - /// Build a coin-specific SigningInput protobuf transaction input, from simple transaction parameters - static Data buildInput(TWCoinType coinType, const std::string& from, const std::string& to, const std::string& amount, const std::string& asset, const std::string& memo, const std::string& chainId); - /// Obtain pre-signing hash of a transaction. /// It will return a proto object named `PreSigningOutput` which will include hash. /// We provide a default `PreSigningOutput` in TransactionCompiler.proto. diff --git a/src/interface/TWTransactionCompiler.cpp b/src/interface/TWTransactionCompiler.cpp index 454e8f9aa0a..dffd88fbb5e 100644 --- a/src/interface/TWTransactionCompiler.cpp +++ b/src/interface/TWTransactionCompiler.cpp @@ -14,23 +14,6 @@ using namespace TW; - -TWData *_Nonnull TWTransactionCompilerBuildInput(enum TWCoinType coinType, TWString *_Nonnull from, TWString *_Nonnull to, TWString *_Nonnull amount, TWString *_Nonnull asset, TWString *_Nonnull memo, TWString *_Nonnull chainId) { - Data result; - try { - result = TransactionCompiler::buildInput( - coinType, - std::string(TWStringUTF8Bytes(from)), - std::string(TWStringUTF8Bytes(to)), - std::string(TWStringUTF8Bytes(amount)), - std::string(TWStringUTF8Bytes(asset)), - std::string(TWStringUTF8Bytes(memo)), - std::string(TWStringUTF8Bytes(chainId)) - ); - } catch (...) {} // return empty - return TWDataCreateWithBytes(result.data(), result.size()); -} - static std::vector createFromTWDataVector(const struct TWDataVector* _Nonnull dataVector) { std::vector ret; const auto n = TWDataVectorSize(dataVector); diff --git a/src/rust/RustCoinEntry.h b/src/rust/RustCoinEntry.h index edcf926d9e2..5c481aaa284 100644 --- a/src/rust/RustCoinEntry.h +++ b/src/rust/RustCoinEntry.h @@ -8,8 +8,14 @@ #include "CoinEntry.h" +#include + namespace TW::Rust { +/// The function takes a Protobuf output message `const Output&` and returns `std::string`. +template +using SignJSONOutputHandler = std::function; + class RustCoinEntry : public CoinEntry { public: ~RustCoinEntry() noexcept override = default; @@ -23,4 +29,37 @@ class RustCoinEntry : public CoinEntry { void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; }; +class RustCoinEntryWithSignJSON: public RustCoinEntry { +public: + ~RustCoinEntryWithSignJSON() noexcept override = default; + +protected: + /// Helper function that can be used by [`Entry::signJSON`] to: + /// 1. Deserialize the given `json` as an `Input` object. + /// 2. Put the given `key` as `Input::private_key`. + /// 3. Serialize the input as bytes and call `Entry::sign`. + /// 4. Deserialize the output bytes as an `Output` object. + /// 5. Map the output object to a string. + template + std::string signJSONHelper(TWCoinType coin, const std::string &json, const Data &key, + SignJSONOutputHandler mapOutput) const { + Input input; + google::protobuf::util::JsonStringToMessage(json, &input); + input.set_private_key(key.data(), key.size()); + + auto inputData = data(input.SerializeAsString()); + Data dataOut; + sign(coin, inputData, dataOut); + + if (dataOut.empty()) { + return {}; + } + + Output output; + output.ParseFromArray(dataOut.data(), static_cast(dataOut.size())); + + return mapOutput(output); + } +}; + } // namespace TW::Rust diff --git a/tests/chains/Binance/SignerTests.cpp b/tests/chains/Binance/SignerTests.cpp deleted file mode 100644 index dd9280894e3..00000000000 --- a/tests/chains/Binance/SignerTests.cpp +++ /dev/null @@ -1,702 +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 "Bech32Address.h" -#include "Binance/Address.h" -#include "Binance/Signer.h" -#include "Coin.h" -#include "Ethereum/Address.h" -#include "HDWallet.h" -#include "HexCoding.h" -#include "proto/Binance.pb.h" - -#include - -namespace TW::Binance { - -TEST(BinanceSigner, Sign) { - auto input = Proto::SigningInput(); - input.set_chain_id("chain-bnb"); - input.set_account_number(12); - input.set_sequence(35); - input.set_memo(""); - input.set_source(1); - - auto key = parse_hex("90335b9d2153ad1a9799a3ccc070bd64b4164e9642ee1dd48053c33f9a3a05e9"); - input.set_private_key(key.data(), key.size()); - - auto& order = *input.mutable_trade_order(); - Binance::Address address; - ASSERT_TRUE(Binance::Address::decode("bnb1hgm0p7khfk85zpz5v0j8wnej3a90w709vhkdfu", address)); - auto keyhash = address.getKeyHash(); - order.set_sender(keyhash.data(), keyhash.size()); - order.set_id("BA36F0FAD74D8F41045463E4774F328F4AF779E5-36"); - order.set_symbol("NNB-338_BNB"); - order.set_ordertype(2); - order.set_side(1); - order.set_price(136350000); - order.set_quantity(100000000); - order.set_timeinforce(1); - - auto signer = Binance::Signer(std::move(input)); - auto signature = signer.sign(); - - ASSERT_EQ(hex(signature), - "9123cb6906bb20aeb753f4a121d4d88ff0e9750ba75b0c4e10d76caee1e7d2481290fa3b9887a6225d69" - "97f5f939ef834ea61d596a314237c48e560da9e17b5a"); -} - -TEST(BinanceSigner, Build) { - auto input = Proto::SigningInput(); - input.set_chain_id("chain-bnb"); - input.set_account_number(1); - input.set_sequence(10); - - auto key = parse_hex("90335b9d2153ad1a9799a3ccc070bd64b4164e9642ee1dd48053c33f9a3a05e9"); - input.set_private_key(key.data(), key.size()); - - auto& order = *input.mutable_trade_order(); - auto address = Binance::Address(parse_hex("b6561dcc104130059a7c08f48c64610c1f6f9064")); - auto keyhash = address.getKeyHash(); - order.set_sender(keyhash.data(), keyhash.size()); - order.set_id("B6561DCC104130059A7C08F48C64610C1F6F9064-11"); - order.set_symbol("BTC-5C4_BNB"); - order.set_ordertype(2); - order.set_side(1); - order.set_price(100000000); - order.set_quantity(1200000000); - order.set_timeinforce(1); - - auto signer = Binance::Signer(std::move(input)); - auto result = signer.build(); - - ASSERT_EQ(hex(result), "db01" - "f0625dee" - "0a65" - "ce6dc043" - "0a14""b6561dcc104130059a7c08f48c64610c1f6f9064" - "122b""423635363144434331303431333030353941374330384634384336343631304331463646393036342d3131" - "1a0b""4254432d3543345f424e42" - "2002" - "2801" - "3080c2d72f" - "3880989abc04" - "4001" - "126e" - "0a26" - "eb5ae987" - "21029729a52e4e3c2b4a4e52aa74033eedaf8ba1df5ab6d1f518fd69e67bbd309b0e" - "1240""2a78b6d9a108eb9440221802b626e24d80179395ac984f016db012ef1a0c16d71b4d7053e05366ae3ea2681fc8052398fda20551c965d74c5970bbc66b94b48e" - "1801" - "200a" - ); -} - -TEST(BinanceSigner, BuildSend) { - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("chain-bnb"); - signingInput.set_account_number(19); - signingInput.set_sequence(23); - signingInput.set_memo("test"); - signingInput.set_source(1); - - auto key = parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832"); - signingInput.set_private_key(key.data(), key.size()); - - auto& order = *signingInput.mutable_send_order(); - - auto fromKeyhash = parse_hex("40c2979694bbc961023d1d27be6fc4d21a9febe6"); - auto fromAddress = Binance::Address(fromKeyhash); - - auto toKeyhash = parse_hex("88b37d5e05f3699e2a1406468e5d87cb9dcceb95"); - auto toAddress = Binance::Address(toKeyhash); - - auto input = order.add_inputs(); - input->set_address(fromKeyhash.data(), fromKeyhash.size()); - auto inputCoin = input->add_coins(); - inputCoin->set_denom("BNB"); - inputCoin->set_amount(1'001'000'000); - - auto output = order.add_outputs(); - output->set_address(toKeyhash.data(), toKeyhash.size()); - auto outputCoin = output->add_coins(); - outputCoin->set_denom("BNB"); - outputCoin->set_amount(1'001'000'000); - - auto signer = Binance::Signer(std::move(signingInput)); - auto signature = signer.sign(); - ASSERT_EQ(hex(signature), - "c65a13440f18a155bd971ee40b9e0dd58586f5bf344e12ec4c76c439aebca8c7789bab7bfbfb4ce89aad" - "c4a02df225b6b6efc861c13bbeb5f7a3eea2d7ffc80f"); - - auto result = signer.build(); - - ASSERT_EQ(hex(result), "cc01" - "f0625dee" - "0a4e" - "2a2c87fa" - "0a23""0a1440c2979694bbc961023d1d27be6fc4d21a9febe6120b0a03424e4210c098a8dd03" - "1223""0a1488b37d5e05f3699e2a1406468e5d87cb9dcceb95120b0a03424e4210c098a8dd03" - "126e" - "0a26" - "eb5ae987" - "21026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502" - "1240""c65a13440f18a155bd971ee40b9e0dd58586f5bf344e12ec4c76c439aebca8c7789bab7bfbfb4ce89aadc4a02df225b6b6efc861c13bbeb5f7a3eea2d7ffc80f" - "1813" - "2017" - "1a04""74657374" - "2001" - ); -} - -TEST(BinanceSigner, BuildSend2) { - const auto derivationPath = TW::derivationPath(TWCoinTypeBinance); - - const auto fromWallet = HDWallet("swift slam quote sail high remain mandate sample now stamp title among fiscal captain joy puppy ghost arrow attract ozone situate install gain mean", ""); - const auto fromPrivateKey = fromWallet.getKey(TWCoinTypeBinance, derivationPath); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - - const auto toWallet = HDWallet( "bottom quick strong ranch section decide pepper broken oven demand coin run jacket curious business achieve mule bamboo remain vote kid rigid bench rubber", ""); - const auto toPrivateKey = toWallet.getKey(TWCoinTypeBinance, derivationPath); - const auto toPublicKey = PublicKey(toPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("bnbchain-1000"); - signingInput.set_account_number(0); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto token = Proto::SendOrder::Token(); - token.set_denom("BNB"); - token.set_amount(100000000000000); - - auto input = Proto::SendOrder::Input(); - auto fromKeyHash = Binance::Address(fromPublicKey).getKeyHash(); - input.set_address(fromKeyHash.data(), fromKeyHash.size()); - *input.add_coins() = token; - - auto output = Proto::SendOrder::Output(); - auto toKeyHash = Binance::Address(toPublicKey).getKeyHash(); - output.set_address(toKeyHash.data(), toKeyHash.size()); - *output.add_coins() = token; - - auto sendOrder = Proto::SendOrder(); - *sendOrder.add_inputs() = input; - *sendOrder.add_outputs() = output; - - *signingInput.mutable_send_order() = sendOrder; - - const auto data = Signer(std::move(signingInput)).build(); - ASSERT_EQ(hex(data), - "c601" - "f0625dee" - "0a52" - "2a2c87fa" - "0a25""0a141d0e3086e8e4e0a53c38a90d55bd58b34d57d2fa120d0a03424e42108080e983b1de16" - "1225""0a146b571fc0a9961a7ddf45e49a88a4d83941fcabbe120d0a03424e42108080e983b1de16" - "126c" - "0a26" - "eb5ae987" - "21027e69d96640300433654e016d218a8d7ffed751023d8efe81e55dedbd6754c971" - "1240""8b23eecfa8237a27676725173e58154e6c204bb291b31c3b7b507c8f04e2773909ba70e01b54f4bd0bc76669f5712a5a66b9508acdf3aa5e4fde75fbe57622a1" - "2001" - ); -} - -TEST(BinanceSigner, BuildHTLT) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - auto fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - const auto toPrivateKey = - PrivateKey(parse_hex("851fab89c14f4bbec0cc06f5e445ec065efc641068d78b308c67217d9bd5c88a")); - const auto toPublicKey = PublicKey(toPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - auto toAddr = Binance::Address(toPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(0); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto token = Proto::SendOrder::Token(); - token.set_denom("BNB"); - token.set_amount(100000000); - - auto randomNumberHash = - parse_hex("e8eae926261ab77d018202434791a335249b470246a7b02e28c3b2fb6ffad8f3"); - - auto& htltOrder = *signingInput.mutable_htlt_order(); - htltOrder.set_from(fromAddr.data(), fromAddr.size()); - htltOrder.set_to(toAddr.data(), toAddr.size()); - htltOrder.set_recipient_other_chain(""); - htltOrder.set_sender_other_chain(""); - *htltOrder.add_amount() = token; - htltOrder.set_height_span(400); - htltOrder.set_expected_income("100000000:BTC-1DC"); - htltOrder.set_timestamp(1567746273); - htltOrder.set_random_number_hash(randomNumberHash.data(), randomNumberHash.size()); - htltOrder.set_cross_chain(false); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - ASSERT_EQ(hex(data), - "ee01f0625dee0a7ab33f9a240a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e112140153f11d6db7" - "e69c7d51e771c697378018fb6c242a20e8eae926261ab77d018202434791a335249b470246a7b02e28c3" - "b2fb6ffad8f330e1d1c7eb053a0a0a03424e421080c2d72f42113130303030303030303a4254432d3144" - "43489003126c0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f" - "6b6a8fc7124051439de2da19fe9fd22137c903cfc5dc87553bf05dca0bb202c0e07c47f9b51269efa272" - "43eb7b55888f5384a84ac1eac6d325c830d1be0ed042838e2dc0f6a9180f"); -} - -TEST(BinanceSigner, BuildDepositHTLT) { - const auto fromPrivateKey = - PrivateKey(parse_hex("851fab89c14f4bbec0cc06f5e445ec065efc641068d78b308c67217d9bd5c88a")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - auto fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(16); - signingInput.set_sequence(0); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto token = Proto::SendOrder::Token(); - token.set_denom("BTC-1DC"); - token.set_amount(100000000); - - auto swapID = parse_hex("dd8fd4719741844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5"); - - auto& depositHTLTOrder = *signingInput.mutable_deposithtlt_order(); - depositHTLTOrder.set_from(fromAddr.data(), fromAddr.size()); - depositHTLTOrder.set_swap_id(swapID.data(), swapID.size()); - *depositHTLTOrder.add_amount() = token; - - const auto data = Binance::Signer(std::move(signingInput)).build(); - ASSERT_EQ(hex(data), - "c001f0625dee0a4c639864960a140153f11d6db7e69c7d51e771c697378018fb6c24120e0a074254432d" - "3144431080c2d72f1a20dd8fd4719741844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5" - "126c0a26eb5ae98721038df6960084e20b2d07d50e1422f94105c6241d9f1482a4eb79ce8bfd460f19e4" - "12400ca4144c6818e2836d09b4faf3161781d85f9adfc00badb2eaa0953174610a233b81413dafcf8471" - "6abad48a4ed3aeb9884d90eb8416eec5d5c0c6930ab60bd01810"); -} - -TEST(BinanceSigner, BuildClaimHTLT) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - auto fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto randomNumber = - parse_hex("bda6933c7757d0ca428aa01fb9d0935a231f87bf2deeb9b409cea3f2d580a2cc"); - auto swapID = parse_hex("dd8fd4719741844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5"); - - auto& claimHTLTOrder = *signingInput.mutable_claimhtlt_order(); - claimHTLTOrder.set_from(fromAddr.data(), fromAddr.size()); - claimHTLTOrder.set_swap_id(swapID.data(), swapID.size()); - claimHTLTOrder.set_random_number(randomNumber.data(), randomNumber.size()); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - ASSERT_EQ( - hex(data), - "d401f0625dee0a5ec16653000a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e11220dd8fd4719741844d35" - "eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e51a20bda6933c7757d0ca428aa01fb9d0935a231f87bf" - "2deeb9b409cea3f2d580a2cc126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561a" - "ac993dbe0f6b6a8fc71240fa30ba50111aa31d8329dacb6d044c1c7d54f1cb782bc9aa2a50c3fabce02a4579d7" - "5b76ca69a9fab11b676d9da66b5af7aa4c9ad3d18e24fffeb16433be39fb180f2001"); -} - -TEST(BinanceSigner, BuildRefundHTLT) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - auto fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto swapID = parse_hex("dd8fd4719741844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5"); - - auto& refundHTLTOrder = *signingInput.mutable_refundhtlt_order(); - refundHTLTOrder.set_from(fromAddr.data(), fromAddr.size()); - refundHTLTOrder.set_swap_id(swapID.data(), swapID.size()); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - ASSERT_EQ(hex(data), - "b201f0625dee0a3c3454a27c0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e11220dd8fd4719741" - "844d35eb35ddbeca9531d5493a8e4667689c55e73c77503dd9e5126e0a26eb5ae9872103a9a55c040c8e" - "b8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240c9f36142534d16ec8ce656f8eb73" - "70b32206a2d15198b7165acf1e2a18952c9e4570b0f862e1ab7bb868c30781a42c9e3ec0ae08982e8d6c" - "91c55b83c71b7b1e180f2001"); -} - -TEST(BinanceSigner, BuildIssueOrder) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& issueOrder = *signingInput.mutable_issue_order(); - issueOrder.set_from(fromAddr.data(), fromAddr.size()); - issueOrder.set_name("NewBinanceToken"); - issueOrder.set_symbol("NNB-338_BNB"); - issueOrder.set_total_supply(1000000000); - issueOrder.set_mintable(true); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - ASSERT_EQ(hex(data), - "b601f0625dee0a40" - "17efab80" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120f" - "4e657742696e616e6365546f6b656e" - "1a0b" - "4e4e422d3333385f424e42" - "208094ebdc032801126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc712401fbb993d643f03b3e8e757a502035f58c4c45aaaa6e107a3059ab7c6164283c10f1254e87feee21477c64c87b1a27d8481048533ae2f685b3ac0dc66e4edbc0b180f2001" - ); -} - -TEST(BinanceSigner, BuildMintOrder) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& mintOrder = *signingInput.mutable_mint_order(); - mintOrder.set_from(fromAddr.data(), fromAddr.size()); - mintOrder.set_symbol("NNB-338_BNB"); - mintOrder.set_amount(1000000); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - ASSERT_EQ(hex(data), - "a101f0625dee0a2b" - "467e0829" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" - "4e4e422d3333385f424e42" - "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001" - ); -} - -TEST(BinanceSigner, BuildBurnOrder) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& burnOrder = *signingInput.mutable_burn_order(); - burnOrder.set_from(fromAddr.data(), fromAddr.size()); - burnOrder.set_symbol("NNB-338_BNB"); - burnOrder.set_amount(1000000); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - ASSERT_EQ(hex(data), - "a101f0625dee0a2b" - "7ed2d2a0" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" - "4e4e422d3333385f424e42" - "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001" - ); -} - -TEST(BinanceSigner, BuildFreezeOrder) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& freezeOrder = *signingInput.mutable_freeze_order(); - freezeOrder.set_from(fromAddr.data(), fromAddr.size()); - freezeOrder.set_symbol("NNB-338_BNB"); - freezeOrder.set_amount(1000000); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - ASSERT_EQ(hex(data), - "a101f0625dee0a2b" - "e774b32d" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" - "4e4e422d3333385f424e42" - "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f" - "6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162" - "722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001"); -} - -TEST(BinanceSigner, BuildUnfreezeOrder) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& unfreezeOrder = *signingInput.mutable_unfreeze_order(); - unfreezeOrder.set_from(fromAddr.data(), fromAddr.size()); - unfreezeOrder.set_symbol("NNB-338_BNB"); - unfreezeOrder.set_amount(1000000); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - ASSERT_EQ(hex(data), - "a101f0625dee0a2b" - "6515ff0d" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1120b" - "4e4e422d3333385f424e42" - "18c0843d126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f" - "6b6a8fc71240e3022069d897bf5bf4846d354fcd2c0e85807053be643c8b8c8596306003f7340d43a162" - "722673eb848258b0435b1f49993d0e75d4ae43d03453a3ae57fe6991180f2001"); -} - -TEST(BinanceSigner, BuildTransferOutOrder) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - const auto toAddr = Ethereum::Address("0x35552c16704d214347f29Fa77f77DA6d75d7C752"); - const auto toBytes = Data(toAddr.bytes.begin(), toAddr.bytes.end()); - - auto input = Proto::SigningInput(); - input.set_chain_id("test-chain"); - input.set_account_number(15); - input.set_sequence(1); - input.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& order = *input.mutable_transfer_out_order(); - order.set_from(fromAddr.data(), fromAddr.size()); - order.set_to(toBytes.data(), toBytes.size()); - order.set_expire_time(12345678); - - auto& token = *order.mutable_amount(); - token.set_denom("BNB"); - token.set_amount(100000000); - - const auto data = Binance::Signer(std::move(input)).build(); - ASSERT_EQ(hex(data), - "b701f0625dee0a41800819c00a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1121435552c16704d" - "214347f29fa77f77da6d75d7c7521a0a0a03424e421080c2d72f20cec2f105126e0a26eb5ae9872103a9" - "a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc712407eda148e1167b1be12" - "71a788ccf4e3eade1c7e1773e9d2093982d7f802f8f85f35ef550049011728206e4eda1a272f9e96fd95" - "ef3983cad85a29cd14262c22e0180f2001"); -} - -TEST(BinanceSigner, BuildSideChainDelegate) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - auto validator = Bech32Address(""); - Bech32Address::decode("bva10npy5809y303f227g4leqw7vs3s6ep5ul26sq2", validator, "bva"); - - auto input = Proto::SigningInput(); - input.set_chain_id("test-chain"); - input.set_account_number(15); - input.set_sequence(1); - input.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& order = *input.mutable_side_delegate_order(); - order.set_delegator_addr(fromAddr.data(), fromAddr.size()); - order.set_validator_addr(validator.getKeyHash().data(), validator.getKeyHash().size()); - order.set_chain_id("chapel"); - - auto& token = *order.mutable_delegation(); - token.set_denom("BNB"); - token.set_amount(200000000); - - const auto data = Binance::Signer(std::move(input)).build(); - ASSERT_EQ(hex(data), - "ba01f0625dee0a44e3a07fd20a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e112147cc24a1de524" - "5f14a95e457f903bcc8461ac869c1a0a0a03424e42108084af5f220663686170656c126e0a26eb5ae987" - "2103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc7124039302c9975fb" - "2a09ac2b6b6fb1d3b9fb5b4c03630d3d7a7da42b1c6736d6127142a3fcdca0b70a3d065da8d4f4df8b5d" - "9d8f46aeb3627a7d7aa901fe186af34c180f2001"); -} - -TEST(BinanceSigner, BuildSideChainRedelegate) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - auto validator1 = Bech32Address(""); - auto validator2 = Bech32Address(""); - Bech32Address::decode("bva1echrty7p8r23cwx8g3ezwcza9azy4zq7ca0pzw", validator1, "bva"); - Bech32Address::decode("bva1p7s26ervsmv3w83k5696glautc9sm5rchz5f5e", validator2, "bva"); - - auto input = Proto::SigningInput(); - input.set_chain_id("test-chain"); - input.set_account_number(15); - input.set_sequence(1); - input.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& order = *input.mutable_side_redelegate_order(); - order.set_delegator_addr(fromAddr.data(), fromAddr.size()); - order.set_validator_src_addr(validator1.getKeyHash().data(), validator1.getKeyHash().size()); - order.set_validator_dst_addr(validator2.getKeyHash().data(), validator2.getKeyHash().size()); - order.set_chain_id("chapel"); - - auto& token = *order.mutable_amount(); - token.set_denom("BNB"); - token.set_amount(100000000); - - const auto data = Binance::Signer(std::move(input)).build(); - ASSERT_EQ(hex(data), - "d001f0625dee0a5ae3ced3640a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e11214ce2e3593c138" - "d51c38c7447227605d2f444a881e1a140fa0ad646c86d9171e36a68ba47fbc5e0b0dd078220a0a03424e" - "421080c2d72f2a0663686170656c126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08" - "af44ea561aac993dbe0f6b6a8fc71240114c6927423e95ecc831ec763b629b3a40db8feeb330528a941f" - "d74843c0d63b4271b23916770d4901988c1f56b20086e5768a12290ebec265e30a80f8f3d88e180f2001" - ); -} - -TEST(BinanceSigner, BuildSideChainUndelegate) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - auto validator = Bech32Address(""); - Bech32Address::decode("bva1echrty7p8r23cwx8g3ezwcza9azy4zq7ca0pzw", validator, "bva"); - - auto input = Proto::SigningInput(); - input.set_chain_id("test-chain"); - input.set_account_number(15); - input.set_sequence(1); - input.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& order = *input.mutable_side_undelegate_order(); - order.set_delegator_addr(fromAddr.data(), fromAddr.size()); - order.set_validator_addr(validator.getKeyHash().data(), validator.getKeyHash().size()); - order.set_chain_id("chapel"); - - auto& token = *order.mutable_amount(); - token.set_denom("BNB"); - token.set_amount(100000000); - - const auto data = Binance::Signer(std::move(input)).build(); - ASSERT_EQ(hex(data), - "ba01f0625dee0a44514f7e0e0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e11214ce2e3593c138" - "d51c38c7447227605d2f444a881e1a0a0a03424e421080c2d72f220663686170656c126e0a26eb5ae987" - "2103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240a622b7ca7a28" - "75e5eaa675a5ed976b2ec3b8ca055a2b05e7fb471d328bd04df854789437dd06407e41ebb1e5a345604c" - "93663dfb660e223800636c0b94c2e798180f2001" - ); -} - -TEST(BinanceSigner, BuildTimeLockOrder) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& lockOrder = *signingInput.mutable_time_lock_order(); - lockOrder.set_from_address(fromAddr.data(), fromAddr.size()); - lockOrder.set_description("Description locked for offer"); - auto amount = lockOrder.add_amount(); - amount->set_denom("BNB"); - amount->set_amount(1000000); - lockOrder.set_lock_time(1600001371); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - EXPECT_EQ(hex(data), - "bf01f0625dee0a49" - "07921531" - "0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e1121c4465736372697074696f6e206c6f636b656420666f72206f666665721a090a03424e4210c0843d20dbaaf8fa05126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240c270822b9515ba486c6a6b3472d388a5aea872ed960c0b53de0fafdc8682ef473a126f01e7dd2c00f04a0138a601b9540f54b14026846de362f7ab7f9fed948b180f2001" - ); -} - -TEST(BinanceSigner, BuildTimeRelockOrder) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& relockOrder = *signingInput.mutable_time_relock_order(); - relockOrder.set_from_address(fromAddr.data(), fromAddr.size()); - relockOrder.set_id(333); - relockOrder.set_description("Description locked for offer"); - auto amount = relockOrder.add_amount(); - amount->set_denom("BNB"); - amount->set_amount(1000000); - relockOrder.set_lock_time(1600001371); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - EXPECT_EQ(hex(data), - "c201f0625dee0a4c504711da0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e110cd021a1c446573" - "6372697074696f6e206c6f636b656420666f72206f6666657222090a03424e4210c0843d28dbaaf8fa05" - "126e0a26eb5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc7" - "124086ddaa077c8ae551d402fa409cf7e91663982b0542200967c03c0b5876b181353250f689d342f221" - "7624a077b671ce7d09649187e29879f40abbbee9de7ab27c180f2001"); -} - -TEST(BinanceSigner, BuildTimeUnlockOrder) { - const auto fromPrivateKey = - PrivateKey(parse_hex("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")); - const auto fromPublicKey = PublicKey(fromPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); - const Data fromAddr = Binance::Address(fromPublicKey).getKeyHash(); - - auto signingInput = Proto::SigningInput(); - signingInput.set_chain_id("test-chain"); - signingInput.set_account_number(15); - signingInput.set_sequence(1); - signingInput.set_private_key(fromPrivateKey.bytes.data(), fromPrivateKey.bytes.size()); - - auto& unlockOrder = *signingInput.mutable_time_unlock_order(); - unlockOrder.set_from_address(fromAddr.data(), fromAddr.size()); - unlockOrder.set_id(333); - - const auto data = Binance::Signer(std::move(signingInput)).build(); - EXPECT_EQ(hex(data), - "9301f0625dee0a1dc4050c6c0a1408c7c918f6b72c3c0c21b7d08eb6fc66509998e110cd02126e0a26eb" - "5ae9872103a9a55c040c8eb8120f3d1b32193250841c08af44ea561aac993dbe0f6b6a8fc71240da777b" - "fd2032834f59ec9fe69fd6eaa4aca24242dfbc5ec4ef8c435cb9da7eb05ab78e1b8ca9f109657cb77996" - "898f1b59137b3d8f1e00f842e409e18033b347180f2001"); -} - -} // namespace TW::Binance diff --git a/tests/chains/Binance/TransactionCompilerTests.cpp b/tests/chains/Binance/TransactionCompilerTests.cpp index 8e5580d5b7c..5b5a4e7ef19 100644 --- a/tests/chains/Binance/TransactionCompilerTests.cpp +++ b/tests/chains/Binance/TransactionCompilerTests.cpp @@ -25,27 +25,29 @@ using namespace TW; TEST(BinanceCompiler, CompileWithSignatures) { /// Step 1: Prepare transaction input (protobuf) const auto coin = TWCoinTypeBinance; - const auto txInputData = TransactionCompiler::buildInput( - coin, - "bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", // from - "bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5", // to - "1", // amount - "BNB", // asset - "", // memo - "Binance-Chain-Nile" // testnet chainId - ); + // bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2 + const auto fromAddressData = parse_hex("40c2979694bbc961023d1d27be6fc4d21a9febe6"); + // bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5 + const auto toAddressData = parse_hex("bffe47abfaede50419c577f1074fee6dd1535cd1"); - { - // Check, by parsing - EXPECT_EQ(txInputData.size(), 88ul); - Binance::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(txInputData.data(), (int)txInputData.size())); - EXPECT_EQ(input.chain_id(), "Binance-Chain-Nile"); - EXPECT_TRUE(input.has_send_order()); - ASSERT_EQ(input.send_order().inputs_size(), 1); - EXPECT_EQ(hex(data(input.send_order().inputs(0).address())), - "40c2979694bbc961023d1d27be6fc4d21a9febe6"); - } + Binance::Proto::SigningInput txInput; + + txInput.set_chain_id("Binance-Chain-Nile"); + auto& sendOrder = *txInput.mutable_send_order(); + + auto& input1 = *sendOrder.add_inputs(); + input1.set_address(fromAddressData.data(), fromAddressData.size()); + auto& input1Coin = *input1.add_coins(); + input1Coin.set_amount(1); + input1Coin.set_denom("BNB"); + + auto& output1 = *sendOrder.add_outputs(); + output1.set_address(toAddressData.data(), toAddressData.size()); + auto& output1Coin = *output1.add_coins(); + output1Coin.set_amount(1); + output1Coin.set_denom("BNB"); + + auto txInputData = data(txInput.SerializeAsString()); /// Step 2: Obtain preimage hash const auto preImageHashes = TransactionCompiler::preImageHashes(coin, txInputData); @@ -115,6 +117,6 @@ TEST(BinanceCompiler, CompileWithSignatures) { Binance::Proto::SigningOutput output; ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size())); EXPECT_EQ(output.encoded().size(), 0ul); - EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + EXPECT_EQ(output.error(), Common::Proto::Error_signatures_count); } } diff --git a/tests/chains/Ethereum/TransactionCompilerTests.cpp b/tests/chains/Ethereum/TransactionCompilerTests.cpp index 5a37950eeb6..64c208d5bb5 100644 --- a/tests/chains/Ethereum/TransactionCompilerTests.cpp +++ b/tests/chains/Ethereum/TransactionCompilerTests.cpp @@ -108,18 +108,3 @@ TEST(EthereumCompiler, CompileWithSignatures) { EXPECT_EQ(output.error(), Common::Proto::Error_signatures_count); } } - -TEST(EthereumCompiler, BuildTransactionInputShouldFail) { - const auto coin = TWCoinTypeEthereum; - const auto txInputData0 = - TransactionCompiler::buildInput(coin, - "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", // from - "0x3535353535353535353535353535353535353535", // to - "1000000000000000000", // amount - "ETH", // asset - "Memo", // memo - "05" // chainId - ); - // Ethereum doesn't support `buildInput`. - EXPECT_TRUE(txInputData0.empty()); -} diff --git a/tests/chains/TBinance/AddressTests.cpp b/tests/chains/TBinance/AddressTests.cpp deleted file mode 100644 index 259a0f9505a..00000000000 --- a/tests/chains/TBinance/AddressTests.cpp +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright © 2017-2022 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 "HexCoding.h" -#include "Binance/Address.h" -#include "PublicKey.h" -#include "PrivateKey.h" -#include -#include - -using namespace TW; -using namespace TW::Binance; - -TEST(TBinanceAddress, Valid) { - ASSERT_TRUE(Address::isValid(TWCoinTypeTBinance, "tbnb18mtcq20875cr0p7l4upm0u5zx4r9jpj2kfu9f8")); -} - -TEST(TBinanceAddress, Invalid) { - ASSERT_FALSE(Address::isValid(TWCoinTypeTBinance, "tbnb18mtcq20875cr0p7l4upm0u5zx4r9jpj2kfu9aa")); - ASSERT_FALSE(Address::isValid(TWCoinTypeTBinance, "bnb1h8xf9htasu9aclra954dnnve8fgcda4ae7qfa8")); -} - -TEST(TBinanceAddress, FromPrivateKey) { - auto privateKey = PrivateKey(parse_hex("fc75070f2d9939be82a378ec9a47912cb6df458055f51da022021f42a6bb5ee8")); - auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1), TWCoinTypeTBinance); - ASSERT_EQ(address.string(), "tbnb18mtcq20875cr0p7l4upm0u5zx4r9jpj2kfu9f8"); -} - -TEST(TBinanceAddress, FromPublicKey) { - auto publicKey = PublicKey(parse_hex("02d32c10f9f4b72d0213123d58257e0558e164e5373e719aee73ce5505852c1692"), TWPublicKeyTypeSECP256k1); - auto address = Address(publicKey, TWCoinTypeTBinance); - ASSERT_EQ(address.string(), "tbnb18mtcq20875cr0p7l4upm0u5zx4r9jpj2kfu9f8"); -} - -TEST(TBinanceAddress, FromString) { - Address address(TWCoinTypeTBinance); - Address::decode("tbnb18mtcq20875cr0p7l4upm0u5zx4r9jpj2kfu9f8", address); - ASSERT_EQ(address.string(), "tbnb18mtcq20875cr0p7l4upm0u5zx4r9jpj2kfu9f8"); -} diff --git a/tests/interface/TWTransactionCompilerTests.cpp b/tests/interface/TWTransactionCompilerTests.cpp index a131760e7ce..f2be40920e9 100644 --- a/tests/interface/TWTransactionCompilerTests.cpp +++ b/tests/interface/TWTransactionCompilerTests.cpp @@ -38,22 +38,35 @@ using namespace TW; TEST(TWTransactionCompiler, ExternalSignatureSignBinance) { /// Step 1: Prepare transaction input (protobuf) const auto coin = TWCoinTypeBinance; - const auto txInputData = WRAPD(TWTransactionCompilerBuildInput( - coin, - STRING("bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2").get(), // from - STRING("bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5").get(), // to - STRING("1").get(), // amount - STRING("BNB").get(), // asset - STRING("").get(), // memo - STRING("Binance-Chain-Nile").get() // testnet chainId - )); + // bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2 + const auto fromAddressData = parse_hex("40c2979694bbc961023d1d27be6fc4d21a9febe6"); + // bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5 + const auto toAddressData = parse_hex("bffe47abfaede50419c577f1074fee6dd1535cd1"); + + Binance::Proto::SigningInput txInput; + + txInput.set_chain_id("Binance-Chain-Nile"); + auto& sendOrder = *txInput.mutable_send_order(); + + auto& input1 = *sendOrder.add_inputs(); + input1.set_address(fromAddressData.data(), fromAddressData.size()); + auto& input1Coin = *input1.add_coins(); + input1Coin.set_amount(1); + input1Coin.set_denom("BNB"); + + auto& output1 = *sendOrder.add_outputs(); + output1.set_address(toAddressData.data(), toAddressData.size()); + auto& output1Coin = *output1.add_coins(); + output1Coin.set_amount(1); + output1Coin.set_denom("BNB"); + + auto txInputData = data(txInput.SerializeAsString()); { // Check, by parsing - EXPECT_EQ((int)TWDataSize(txInputData.get()), 88); + EXPECT_EQ((int)txInputData.size(), 88); Binance::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputData.get()), - (int)TWDataSize(txInputData.get()))); + ASSERT_TRUE(input.ParseFromArray(txInputData.data(), (int)txInputData.size())); EXPECT_EQ(input.chain_id(), "Binance-Chain-Nile"); EXPECT_TRUE(input.has_send_order()); ASSERT_EQ(input.send_order().inputs_size(), 1); @@ -62,7 +75,8 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBinance) { } /// Step 2: Obtain preimage hash - const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputData.get())); + auto txInputDataPtr = WRAPD(TWDataCreateWithBytes(txInputData.data(), txInputData.size())); + const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputDataPtr.get())); auto preImageHash = data(TWDataBytes(preImageHashes.get()), TWDataSize(preImageHashes.get())); TxCompiler::Proto::PreSigningOutput preSigningOutput; @@ -86,7 +100,7 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBinance) { /// Step 3: Compile transaction info const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( - coin, txInputData.get(), + coin, txInputDataPtr.get(), WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&publicKeyData)).get())); @@ -108,8 +122,8 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBinance) { { // Double check: check if simple signature process gives the same result. Note that private // keys were not used anywhere up to this point. Binance::Proto::SigningInput input; - ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputData.get()), - (int)TWDataSize(txInputData.get()))); + ASSERT_TRUE(input.ParseFromArray(TWDataBytes(txInputDataPtr.get()), + (int)TWDataSize(txInputDataPtr.get()))); auto key = parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832"); input.set_private_key(key.data(), key.size()); diff --git a/tools/new-blockchain b/tools/new-blockchain new file mode 100755 index 00000000000..8ccdd0e740b --- /dev/null +++ b/tools/new-blockchain @@ -0,0 +1,11 @@ +#!/bin/bash +# +# This script generates new Blockchain skeleton files. + +pushd codegen-v2 +cargo run -- new-blockchain $1 +popd # codegen-v2 + +pushd codegen +codegen/bin/newcoin-mobile-tests $1 +popd # codegen diff --git a/tools/new-evmchain b/tools/new-evmchain new file mode 100755 index 00000000000..3eb2c89410d --- /dev/null +++ b/tools/new-evmchain @@ -0,0 +1,7 @@ +#!/bin/bash +# +# This script generates new EVM chain skeleton files. + +pushd codegen-v2 +cargo run -- new-evmchain $1 +popd # codegen-v2