From d88a2632ba130c9912e98184b7d5792ef764db4d Mon Sep 17 00:00:00 2001 From: 10gic Date: Mon, 9 Sep 2024 23:33:13 +0800 Subject: [PATCH] Support calculating the TX hash of a transaction (#4008) * Support calculating the TX hash of a transaction * Implement the calc_tx_hash for TON/Aptos/Sui * Adjust the code according to the comments * Refactor the calcTxHash for Solana * Format rust code using `cargo fmt` * Change UtxoTransactionUtil to BitcoinTransactionUtil * Add some comments * Adjust the code according to the comments * Add more test cases * Fix issue found by `cargo clippy` --- .../rust/templates/blockchain_crate/entry.rs | 2 + include/TrustWalletCore/TWTransactionUtil.h | 24 ++++++ rust/chains/tw_aptos/src/entry.rs | 7 ++ rust/chains/tw_aptos/src/lib.rs | 1 + rust/chains/tw_aptos/src/modules/mod.rs | 5 ++ .../tw_aptos/src/modules/transaction_util.rs | 35 +++++++++ rust/chains/tw_binance/src/entry.rs | 2 + rust/chains/tw_cosmos/src/entry.rs | 7 ++ rust/chains/tw_ethereum/src/entry.rs | 7 ++ rust/chains/tw_greenfield/src/entry.rs | 2 + rust/chains/tw_internet_computer/src/entry.rs | 3 +- rust/chains/tw_native_evmos/src/entry.rs | 2 + rust/chains/tw_native_injective/src/entry.rs | 2 + rust/chains/tw_ronin/src/entry.rs | 2 + rust/chains/tw_solana/src/entry.rs | 7 ++ rust/chains/tw_solana/src/modules/mod.rs | 1 + .../src/modules/transaction_decoder.rs | 2 +- .../tw_solana/src/modules/transaction_util.rs | 37 ++++++++++ rust/chains/tw_sui/src/entry.rs | 7 ++ rust/chains/tw_sui/src/modules/mod.rs | 1 + .../tw_sui/src/modules/transaction_util.rs | 40 ++++++++++ rust/chains/tw_thorchain/src/entry.rs | 2 + rust/chains/tw_ton/src/entry.rs | 7 ++ rust/chains/tw_ton/src/modules/mod.rs | 1 + .../tw_ton/src/modules/transaction_util.rs | 36 +++++++++ rust/tw_any_coin/src/ffi/mod.rs | 1 + .../src/ffi/tw_transaction_util.rs | 31 ++++++++ rust/tw_any_coin/src/lib.rs | 1 + rust/tw_any_coin/src/test_utils/mod.rs | 1 + .../transaction_calc_tx_hash_utils.rs | 21 ++++++ rust/tw_any_coin/src/transaction_util.rs | 18 +++++ .../chains/aptos/aptos_transaction_util.rs | 13 ++++ rust/tw_any_coin/tests/chains/aptos/mod.rs | 1 + .../bitcoin/bitcoin_transaction_util.rs | 13 ++++ rust/tw_any_coin/tests/chains/bitcoin/mod.rs | 1 + .../chains/cosmos/cosmos_transaction_util.rs | 13 ++++ rust/tw_any_coin/tests/chains/cosmos/mod.rs | 1 + .../ethereum/ethereum_transaction_util.rs | 13 ++++ rust/tw_any_coin/tests/chains/ethereum/mod.rs | 1 + rust/tw_any_coin/tests/chains/solana/mod.rs | 1 + .../chains/solana/solana_transaction_util.rs | 13 ++++ rust/tw_any_coin/tests/chains/sui/mod.rs | 1 + .../tests/chains/sui/sui_transaction_util.rs | 10 +++ rust/tw_any_coin/tests/chains/ton/mod.rs | 1 + .../tests/chains/ton/ton_transaction_util.rs | 13 ++++ rust/tw_bitcoin/src/entry.rs | 7 ++ rust/tw_bitcoin/src/modules/mod.rs | 1 + .../src/modules/transaction_util.rs | 33 +++++++++ rust/tw_coin_entry/src/coin_entry.rs | 10 +++ rust/tw_coin_entry/src/coin_entry_ext.rs | 12 +++ rust/tw_coin_entry/src/modules/mod.rs | 1 + .../src/modules/transaction_util.rs | 20 +++++ rust/tw_cosmos_sdk/src/modules/mod.rs | 1 + .../src/modules/transaction_util.rs | 40 ++++++++++ rust/tw_evm/src/modules/mod.rs | 1 + rust/tw_evm/src/modules/transaction_util.rs | 25 +++++++ src/interface/TWTranscationUtil.cpp | 26 +++++++ tests/interface/TWTransactionUtilTests.cpp | 73 +++++++++++++++++++ 58 files changed, 658 insertions(+), 2 deletions(-) create mode 100644 include/TrustWalletCore/TWTransactionUtil.h create mode 100644 rust/chains/tw_aptos/src/modules/mod.rs create mode 100644 rust/chains/tw_aptos/src/modules/transaction_util.rs create mode 100644 rust/chains/tw_solana/src/modules/transaction_util.rs create mode 100644 rust/chains/tw_sui/src/modules/transaction_util.rs create mode 100644 rust/chains/tw_ton/src/modules/transaction_util.rs create mode 100644 rust/tw_any_coin/src/ffi/tw_transaction_util.rs create mode 100644 rust/tw_any_coin/src/test_utils/transaction_calc_tx_hash_utils.rs create mode 100644 rust/tw_any_coin/src/transaction_util.rs create mode 100644 rust/tw_any_coin/tests/chains/aptos/aptos_transaction_util.rs create mode 100644 rust/tw_any_coin/tests/chains/bitcoin/bitcoin_transaction_util.rs create mode 100644 rust/tw_any_coin/tests/chains/cosmos/cosmos_transaction_util.rs create mode 100644 rust/tw_any_coin/tests/chains/ethereum/ethereum_transaction_util.rs create mode 100644 rust/tw_any_coin/tests/chains/solana/solana_transaction_util.rs create mode 100644 rust/tw_any_coin/tests/chains/sui/sui_transaction_util.rs create mode 100644 rust/tw_any_coin/tests/chains/ton/ton_transaction_util.rs create mode 100644 rust/tw_bitcoin/src/modules/transaction_util.rs create mode 100644 rust/tw_coin_entry/src/modules/transaction_util.rs create mode 100644 rust/tw_cosmos_sdk/src/modules/transaction_util.rs create mode 100644 rust/tw_evm/src/modules/transaction_util.rs create mode 100644 src/interface/TWTranscationUtil.cpp create mode 100644 tests/interface/TWTransactionUtilTests.cpp diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs b/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs index c76b1b25d69..9b1194bbb68 100644 --- a/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs @@ -14,6 +14,7 @@ use tw_coin_entry::modules::json_signer::NoJsonSigner; use tw_coin_entry::modules::message_signer::NoMessageSigner; use tw_coin_entry::modules::plan_builder::NoPlanBuilder; use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::transaction_util::NoTransactionUtil; use tw_coin_entry::modules::wallet_connector::NoWalletConnector; use tw_coin_entry::prefix::NoPrefix; use tw_keypair::tw::PublicKey; @@ -35,6 +36,7 @@ impl CoinEntry for {BLOCKCHAIN}Entry { type MessageSigner = NoMessageSigner; type WalletConnector = NoWalletConnector; type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = NoTransactionUtil; #[inline] fn parse_address( diff --git a/include/TrustWalletCore/TWTransactionUtil.h b/include/TrustWalletCore/TWTransactionUtil.h new file mode 100644 index 00000000000..55b2a811428 --- /dev/null +++ b/include/TrustWalletCore/TWTransactionUtil.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "TWBase.h" +#include "TWCoinType.h" +#include "TWData.h" + +TW_EXTERN_C_BEGIN + +TW_EXPORT_STRUCT +struct TWTransactionUtil; + +/// Calculate the TX hash of a transaction. +/// +/// \param coin coin type. +/// \param encodedTx encoded transaction data. +/// \return The TX hash of a transaction, If the input is invalid or the chain is unsupported, null is returned. +TW_EXPORT_STATIC_METHOD +TWString* _Nullable TWTransactionUtilCalcTxHash(enum TWCoinType coinType, TWString* _Nonnull encodedTx); + +TW_EXTERN_C_END diff --git a/rust/chains/tw_aptos/src/entry.rs b/rust/chains/tw_aptos/src/entry.rs index bee4fb47ddc..f9a46c55049 100644 --- a/rust/chains/tw_aptos/src/entry.rs +++ b/rust/chains/tw_aptos/src/entry.rs @@ -4,6 +4,7 @@ use crate::address::Address; use crate::compiler::Compiler; +use crate::modules::transaction_util::AptosTransactionUtil; use crate::signer::Signer; use std::str::FromStr; use tw_coin_entry::coin_context::CoinContext; @@ -35,6 +36,7 @@ impl CoinEntry for AptosEntry { type MessageSigner = NoMessageSigner; type WalletConnector = NoWalletConnector; type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = AptosTransactionUtil; #[inline] fn parse_address( @@ -102,4 +104,9 @@ impl CoinEntry for AptosEntry { fn message_signer(&self) -> Option { None } + + #[inline] + fn transaction_util(&self) -> Option { + Some(AptosTransactionUtil) + } } diff --git a/rust/chains/tw_aptos/src/lib.rs b/rust/chains/tw_aptos/src/lib.rs index 99f90ae428d..5388e03f4e7 100644 --- a/rust/chains/tw_aptos/src/lib.rs +++ b/rust/chains/tw_aptos/src/lib.rs @@ -12,6 +12,7 @@ pub mod nft; pub mod compiler; pub mod liquid_staking; +pub mod modules; pub mod signer; pub mod transaction; pub mod transaction_builder; diff --git a/rust/chains/tw_aptos/src/modules/mod.rs b/rust/chains/tw_aptos/src/modules/mod.rs new file mode 100644 index 00000000000..c083bb0102e --- /dev/null +++ b/rust/chains/tw_aptos/src/modules/mod.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod transaction_util; diff --git a/rust/chains/tw_aptos/src/modules/transaction_util.rs b/rust/chains/tw_aptos/src/modules/transaction_util.rs new file mode 100644 index 00000000000..6b7034477f8 --- /dev/null +++ b/rust/chains/tw_aptos/src/modules/transaction_util.rs @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::transaction_util::TransactionUtil; +use tw_encoding::hex; +use tw_hash::sha3::sha3_256; + +pub struct AptosTransactionUtil; + +impl TransactionUtil for AptosTransactionUtil { + fn calc_tx_hash(&self, coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + Self::calc_tx_hash_impl(coin, encoded_tx) + } +} + +impl AptosTransactionUtil { + fn calc_tx_hash_impl(_coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + let txn_bytes = hex::decode(encoded_tx).map_err(|_| SigningErrorType::Error_input_parse)?; + + // See: https://github.com/aptos-labs/aptos-ts-sdk/blob/f54cac824a41e41dea09c7a6916858a8604dc901/src/api/transaction.ts#L118 + let prefix = sha3_256("APTOS::Transaction".as_bytes()); + + let mut hash_message = Vec::new(); + hash_message.extend_from_slice(&prefix); + // 0 is the index of the enum `Transaction`, see: https://github.com/aptos-labs/aptos-core/blob/6a130c1cca274a5cfdb4a65b441cd5fe61b6c15b/types/src/transaction/mod.rs#L1939 + hash_message.push(0); + hash_message.extend_from_slice(&txn_bytes); + + let tx_hash = sha3_256(&hash_message); + Ok(hex::encode(tx_hash, true)) + } +} diff --git a/rust/chains/tw_binance/src/entry.rs b/rust/chains/tw_binance/src/entry.rs index cd552881c9d..55930473d53 100644 --- a/rust/chains/tw_binance/src/entry.rs +++ b/rust/chains/tw_binance/src/entry.rs @@ -16,6 +16,7 @@ use tw_coin_entry::modules::json_signer::NoJsonSigner; use tw_coin_entry::modules::message_signer::NoMessageSigner; use tw_coin_entry::modules::plan_builder::NoPlanBuilder; use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::transaction_util::NoTransactionUtil; use tw_keypair::tw::PublicKey; use tw_proto::Binance::Proto; use tw_proto::TxCompiler::Proto as CompilerProto; @@ -35,6 +36,7 @@ impl CoinEntry for BinanceEntry { type MessageSigner = NoMessageSigner; type WalletConnector = BinanceWalletConnector; type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = NoTransactionUtil; #[inline] fn parse_address( diff --git a/rust/chains/tw_cosmos/src/entry.rs b/rust/chains/tw_cosmos/src/entry.rs index 67a15bc2d1e..dc2e9de681b 100644 --- a/rust/chains/tw_cosmos/src/entry.rs +++ b/rust/chains/tw_cosmos/src/entry.rs @@ -16,6 +16,7 @@ use tw_cosmos_sdk::address::{Address, Bech32Prefix}; use tw_cosmos_sdk::context::StandardCosmosContext; use tw_cosmos_sdk::modules::compiler::tw_compiler::TWTransactionCompiler; use tw_cosmos_sdk::modules::signer::tw_signer::TWSigner; +use tw_cosmos_sdk::modules::transaction_util::CosmosTransactionUtil; use tw_keypair::tw; use tw_proto::Cosmos::Proto; use tw_proto::TxCompiler::Proto as CompilerProto; @@ -35,6 +36,7 @@ impl CoinEntry for CosmosEntry { type MessageSigner = NoMessageSigner; type WalletConnector = NoWalletConnector; type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = CosmosTransactionUtil; #[inline] fn parse_address( @@ -95,4 +97,9 @@ impl CoinEntry for CosmosEntry { public_keys, ) } + + #[inline] + fn transaction_util(&self) -> Option { + Some(CosmosTransactionUtil::::default()) + } } diff --git a/rust/chains/tw_ethereum/src/entry.rs b/rust/chains/tw_ethereum/src/entry.rs index 13f48c92701..aad3d6f90b5 100644 --- a/rust/chains/tw_ethereum/src/entry.rs +++ b/rust/chains/tw_ethereum/src/entry.rs @@ -18,6 +18,7 @@ use tw_evm::evm_entry::EvmEntry; use tw_evm::modules::compiler::Compiler; use tw_evm::modules::message_signer::EthMessageSigner; use tw_evm::modules::signer::Signer; +use tw_evm::modules::transaction_util::EvmTransactionUtil; use tw_keypair::tw::PublicKey; use tw_proto::Ethereum::Proto; use tw_proto::TxCompiler::Proto as CompilerProto; @@ -37,6 +38,7 @@ impl CoinEntry for EthereumEntry { type MessageSigner = EthMessageSigner; type WalletConnector = NoWalletConnector; type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = EvmTransactionUtil; #[inline] fn parse_address( @@ -100,6 +102,11 @@ impl CoinEntry for EthereumEntry { fn message_signer(&self) -> Option { Some(EthMessageSigner) } + + #[inline] + fn transaction_util(&self) -> Option { + Some(EvmTransactionUtil) + } } impl EvmEntry for EthereumEntry { diff --git a/rust/chains/tw_greenfield/src/entry.rs b/rust/chains/tw_greenfield/src/entry.rs index 01ee90e50ae..448cbe54f15 100644 --- a/rust/chains/tw_greenfield/src/entry.rs +++ b/rust/chains/tw_greenfield/src/entry.rs @@ -14,6 +14,7 @@ use tw_coin_entry::modules::json_signer::NoJsonSigner; use tw_coin_entry::modules::message_signer::NoMessageSigner; use tw_coin_entry::modules::plan_builder::NoPlanBuilder; use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::transaction_util::NoTransactionUtil; use tw_coin_entry::modules::wallet_connector::NoWalletConnector; use tw_coin_entry::prefix::NoPrefix; use tw_keypair::tw::PublicKey; @@ -35,6 +36,7 @@ impl CoinEntry for GreenfieldEntry { type MessageSigner = NoMessageSigner; type WalletConnector = NoWalletConnector; type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = NoTransactionUtil; #[inline] fn parse_address( diff --git a/rust/chains/tw_internet_computer/src/entry.rs b/rust/chains/tw_internet_computer/src/entry.rs index a11d07ad9cc..3e9be8f75cb 100644 --- a/rust/chains/tw_internet_computer/src/entry.rs +++ b/rust/chains/tw_internet_computer/src/entry.rs @@ -5,6 +5,7 @@ use std::str::FromStr; use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::transaction_util::NoTransactionUtil; use tw_coin_entry::{ coin_context::CoinContext, coin_entry::CoinEntry, @@ -16,7 +17,6 @@ use tw_coin_entry::{ prefix::NoPrefix, signing_output_error, }; - use tw_proto::{ Common::Proto::SigningError as CommonError, InternetComputer::Proto, TxCompiler::Proto as CompilerProto, @@ -39,6 +39,7 @@ impl CoinEntry for InternetComputerEntry { type MessageSigner = NoMessageSigner; type WalletConnector = NoWalletConnector; type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = NoTransactionUtil; #[inline] fn parse_address( diff --git a/rust/chains/tw_native_evmos/src/entry.rs b/rust/chains/tw_native_evmos/src/entry.rs index 86a9e715b73..e3964e6454c 100644 --- a/rust/chains/tw_native_evmos/src/entry.rs +++ b/rust/chains/tw_native_evmos/src/entry.rs @@ -12,6 +12,7 @@ use tw_coin_entry::modules::json_signer::NoJsonSigner; use tw_coin_entry::modules::message_signer::NoMessageSigner; use tw_coin_entry::modules::plan_builder::NoPlanBuilder; use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::transaction_util::NoTransactionUtil; use tw_coin_entry::modules::wallet_connector::NoWalletConnector; use tw_cosmos_sdk::address::{Address, Bech32Prefix}; use tw_cosmos_sdk::modules::compiler::tw_compiler::TWTransactionCompiler; @@ -35,6 +36,7 @@ impl CoinEntry for NativeEvmosEntry { type MessageSigner = NoMessageSigner; type WalletConnector = NoWalletConnector; type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = NoTransactionUtil; #[inline] fn parse_address( diff --git a/rust/chains/tw_native_injective/src/entry.rs b/rust/chains/tw_native_injective/src/entry.rs index 3a16d84836c..bca5b9759dd 100644 --- a/rust/chains/tw_native_injective/src/entry.rs +++ b/rust/chains/tw_native_injective/src/entry.rs @@ -12,6 +12,7 @@ use tw_coin_entry::modules::json_signer::NoJsonSigner; use tw_coin_entry::modules::message_signer::NoMessageSigner; use tw_coin_entry::modules::plan_builder::NoPlanBuilder; use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::transaction_util::NoTransactionUtil; use tw_coin_entry::modules::wallet_connector::NoWalletConnector; use tw_cosmos_sdk::address::{Address, Bech32Prefix}; use tw_cosmos_sdk::modules::compiler::tw_compiler::TWTransactionCompiler; @@ -35,6 +36,7 @@ impl CoinEntry for NativeInjectiveEntry { type MessageSigner = NoMessageSigner; type WalletConnector = NoWalletConnector; type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = NoTransactionUtil; #[inline] fn parse_address( diff --git a/rust/chains/tw_ronin/src/entry.rs b/rust/chains/tw_ronin/src/entry.rs index 1c3a9ab674d..49a21c71c0c 100644 --- a/rust/chains/tw_ronin/src/entry.rs +++ b/rust/chains/tw_ronin/src/entry.rs @@ -12,6 +12,7 @@ use tw_coin_entry::error::prelude::*; use tw_coin_entry::modules::json_signer::NoJsonSigner; use tw_coin_entry::modules::plan_builder::NoPlanBuilder; use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::transaction_util::NoTransactionUtil; use tw_coin_entry::modules::wallet_connector::NoWalletConnector; use tw_coin_entry::prefix::NoPrefix; use tw_evm::evm_entry::EvmEntry; @@ -37,6 +38,7 @@ impl CoinEntry for RoninEntry { type MessageSigner = EthMessageSigner; type WalletConnector = NoWalletConnector; type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = NoTransactionUtil; #[inline] fn parse_address( diff --git a/rust/chains/tw_solana/src/entry.rs b/rust/chains/tw_solana/src/entry.rs index cc30d20c6fa..5fd330739fe 100644 --- a/rust/chains/tw_solana/src/entry.rs +++ b/rust/chains/tw_solana/src/entry.rs @@ -5,6 +5,7 @@ use crate::address::SolanaAddress; use crate::compiler::SolanaCompiler; use crate::modules::transaction_decoder::SolanaTransactionDecoder; +use crate::modules::transaction_util::SolanaTransactionUtil; use crate::modules::wallet_connect::connector::SolanaWalletConnector; use crate::signer::SolanaSigner; use std::str::FromStr; @@ -34,6 +35,7 @@ impl CoinEntry for SolanaEntry { type MessageSigner = NoMessageSigner; type WalletConnector = SolanaWalletConnector; type TransactionDecoder = SolanaTransactionDecoder; + type TransactionUtil = SolanaTransactionUtil; #[inline] fn parse_address( @@ -99,4 +101,9 @@ impl CoinEntry for SolanaEntry { fn transaction_decoder(&self) -> Option { Some(SolanaTransactionDecoder) } + + #[inline] + fn transaction_util(&self) -> Option { + Some(SolanaTransactionUtil) + } } diff --git a/rust/chains/tw_solana/src/modules/mod.rs b/rust/chains/tw_solana/src/modules/mod.rs index 253aec84a3d..074e5cbc8a7 100644 --- a/rust/chains/tw_solana/src/modules/mod.rs +++ b/rust/chains/tw_solana/src/modules/mod.rs @@ -12,6 +12,7 @@ pub mod instruction_builder; pub mod message_builder; pub mod proto_builder; pub mod transaction_decoder; +pub mod transaction_util; pub mod tx_signer; pub mod utils; pub mod wallet_connect; diff --git a/rust/chains/tw_solana/src/modules/transaction_decoder.rs b/rust/chains/tw_solana/src/modules/transaction_decoder.rs index 3a98fb58203..02a441a464a 100644 --- a/rust/chains/tw_solana/src/modules/transaction_decoder.rs +++ b/rust/chains/tw_solana/src/modules/transaction_decoder.rs @@ -22,7 +22,7 @@ impl TransactionDecoder for SolanaTransactionDecoder { } impl SolanaTransactionDecoder { - fn decode_transaction_impl( + pub(crate) fn decode_transaction_impl( _coin: &dyn CoinContext, tx: &[u8], ) -> SigningResult> { diff --git a/rust/chains/tw_solana/src/modules/transaction_util.rs b/rust/chains/tw_solana/src/modules/transaction_util.rs new file mode 100644 index 00000000000..fdde5d6952c --- /dev/null +++ b/rust/chains/tw_solana/src/modules/transaction_util.rs @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::transaction_decoder::SolanaTransactionDecoder; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::transaction_util::TransactionUtil; +use tw_encoding::base64; +use tw_encoding::base64::STANDARD; + +pub struct SolanaTransactionUtil; + +impl TransactionUtil for SolanaTransactionUtil { + fn calc_tx_hash(&self, coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + Self::calc_tx_hash_impl(coin, encoded_tx) + } +} + +impl SolanaTransactionUtil { + fn calc_tx_hash_impl(coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + // Solana signed transactions can be encoded in either base64 or base58. For more information, see: https://solana.com/docs/rpc/http/sendtransaction + // Currently, this function only accepts base64 encoding. + let tx_bytes = base64::decode(encoded_tx, STANDARD)?; + let decoded_tx_output = SolanaTransactionDecoder::decode_transaction_impl(coin, &tx_bytes)?; + + let first_sig = decoded_tx_output + .transaction + .as_ref() + .and_then(|tx| tx.signatures.first()) + .or_tw_err(SigningErrorType::Error_input_parse) + .context("There is no transaction signatures. Looks like it hasn't been signed yet")?; + + // Tx hash is the first signature + Ok(first_sig.signature.to_string()) + } +} diff --git a/rust/chains/tw_sui/src/entry.rs b/rust/chains/tw_sui/src/entry.rs index 72ec4866299..63bd4e92df7 100644 --- a/rust/chains/tw_sui/src/entry.rs +++ b/rust/chains/tw_sui/src/entry.rs @@ -4,6 +4,7 @@ use crate::address::SuiAddress; use crate::compiler::SuiCompiler; +use crate::modules::transaction_util::SuiTransactionUtil; use crate::signer::SuiSigner; use std::str::FromStr; use tw_coin_entry::coin_context::CoinContext; @@ -35,6 +36,7 @@ impl CoinEntry for SuiEntry { type MessageSigner = NoMessageSigner; type WalletConnector = NoWalletConnector; type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = SuiTransactionUtil; #[inline] fn parse_address( @@ -93,4 +95,9 @@ impl CoinEntry for SuiEntry { ) -> Self::SigningOutput { SuiCompiler::compile(coin, input, signatures, public_keys) } + + #[inline] + fn transaction_util(&self) -> Option { + Some(SuiTransactionUtil) + } } diff --git a/rust/chains/tw_sui/src/modules/mod.rs b/rust/chains/tw_sui/src/modules/mod.rs index 7398efd0e9b..32f928c8ec3 100644 --- a/rust/chains/tw_sui/src/modules/mod.rs +++ b/rust/chains/tw_sui/src/modules/mod.rs @@ -2,5 +2,6 @@ // // Copyright © 2017 Trust Wallet. +pub mod transaction_util; pub mod tx_builder; pub mod tx_signer; diff --git a/rust/chains/tw_sui/src/modules/transaction_util.rs b/rust/chains/tw_sui/src/modules/transaction_util.rs new file mode 100644 index 00000000000..f2ad4ff585f --- /dev/null +++ b/rust/chains/tw_sui/src/modules/transaction_util.rs @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::transaction_util::TransactionUtil; +use tw_encoding::base58::{self, Alphabet}; +use tw_encoding::base64::{self, STANDARD}; +use tw_hash::blake2::blake2_b; +use tw_hash::H256; + +pub struct SuiTransactionUtil; + +impl TransactionUtil for SuiTransactionUtil { + fn calc_tx_hash(&self, coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + Self::calc_tx_hash_impl(coin, encoded_tx) + } +} + +// See: https://github.com/mofalabs/sui/blob/74908b3ad8b82e5e401d5017fed4fa7dc2361569/lib/builder/hash.dart#L7 +fn hash_typed_data(type_tag: &str, data: &[u8]) -> Result, tw_hash::Error> { + let type_tag_bytes: Vec = format!("{}::", type_tag).into_bytes(); + + let mut data_with_tag = Vec::with_capacity(type_tag_bytes.len() + data.len()); + data_with_tag.extend_from_slice(&type_tag_bytes); + data_with_tag.extend_from_slice(data); + + blake2_b(&data_with_tag, H256::LEN) +} + +impl SuiTransactionUtil { + fn calc_tx_hash_impl(_coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + let tx = base64::decode(encoded_tx, STANDARD)?; + let tx_hash = hash_typed_data("TransactionData", &tx) + .map_err(|_| SigningErrorType::Error_input_parse)?; + + Ok(base58::encode(&tx_hash, Alphabet::Bitcoin)) + } +} diff --git a/rust/chains/tw_thorchain/src/entry.rs b/rust/chains/tw_thorchain/src/entry.rs index 970701b0d14..9f93134601f 100644 --- a/rust/chains/tw_thorchain/src/entry.rs +++ b/rust/chains/tw_thorchain/src/entry.rs @@ -13,6 +13,7 @@ use tw_coin_entry::modules::json_signer::NoJsonSigner; use tw_coin_entry::modules::message_signer::NoMessageSigner; use tw_coin_entry::modules::plan_builder::NoPlanBuilder; use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::transaction_util::NoTransactionUtil; use tw_coin_entry::modules::wallet_connector::NoWalletConnector; use tw_cosmos_sdk::address::{Address, Bech32Prefix}; use tw_keypair::tw; @@ -34,6 +35,7 @@ impl CoinEntry for ThorchainEntry { type MessageSigner = NoMessageSigner; type WalletConnector = NoWalletConnector; type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = NoTransactionUtil; #[inline] fn parse_address( diff --git a/rust/chains/tw_ton/src/entry.rs b/rust/chains/tw_ton/src/entry.rs index 8c57ee5907f..a2bd5aadc2d 100644 --- a/rust/chains/tw_ton/src/entry.rs +++ b/rust/chains/tw_ton/src/entry.rs @@ -4,6 +4,7 @@ use crate::address::TonAddress; use crate::compiler::TheOpenNetworkCompiler; +use crate::modules::transaction_util::TonTransactionUtil; use crate::signer::TheOpenNetworkSigner; use crate::wallet::{wallet_v4, VersionedTonWallet}; use std::str::FromStr; @@ -36,6 +37,7 @@ impl CoinEntry for TheOpenNetworkEntry { type MessageSigner = NoMessageSigner; type WalletConnector = NoWalletConnector; type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = TonTransactionUtil; #[inline] fn parse_address( @@ -100,4 +102,9 @@ impl CoinEntry for TheOpenNetworkEntry { ) -> Self::SigningOutput { TheOpenNetworkCompiler::compile(coin, input, signatures, public_keys) } + + #[inline] + fn transaction_util(&self) -> Option { + Some(TonTransactionUtil) + } } diff --git a/rust/chains/tw_ton/src/modules/mod.rs b/rust/chains/tw_ton/src/modules/mod.rs index d27e79c19d1..95d32dc4847 100644 --- a/rust/chains/tw_ton/src/modules/mod.rs +++ b/rust/chains/tw_ton/src/modules/mod.rs @@ -4,4 +4,5 @@ pub mod address_converter; pub mod personal_message_signer; +pub mod transaction_util; pub mod wallet_provider; diff --git a/rust/chains/tw_ton/src/modules/transaction_util.rs b/rust/chains/tw_ton/src/modules/transaction_util.rs new file mode 100644 index 00000000000..47200e82187 --- /dev/null +++ b/rust/chains/tw_ton/src/modules/transaction_util.rs @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::transaction_util::TransactionUtil; +use tw_ton_sdk::boc::BagOfCells; + +pub struct TonTransactionUtil; + +impl TransactionUtil for TonTransactionUtil { + fn calc_tx_hash(&self, coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + Self::calc_tx_hash_impl(coin, encoded_tx) + } +} + +impl TonTransactionUtil { + // In the TON blockchain, there are both message hashes and transaction hashes. + // Strictly speaking, this function returns the message hash, not the transaction hash, + // because we often use the TON message hash to track transaction status. + // The transaction hash is unknown until the transaction is included in a block. + fn calc_tx_hash_impl(_coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + let boc = BagOfCells::parse_base64(encoded_tx) + .map_err(|_| SigningErrorType::Error_input_parse)?; + let root_cell_hash = boc + .roots + .first() + .ok_or(SigningErrorType::Error_input_parse)? + .cell_hash(); + + // The message hash in TON can be encoded in base64, base64url, or hex. + // Here, we return the message hash in hex encoding. + Ok(root_cell_hash.to_string()) + } +} diff --git a/rust/tw_any_coin/src/ffi/mod.rs b/rust/tw_any_coin/src/ffi/mod.rs index 8d422f37de4..698f616468f 100644 --- a/rust/tw_any_coin/src/ffi/mod.rs +++ b/rust/tw_any_coin/src/ffi/mod.rs @@ -7,4 +7,5 @@ pub mod tw_any_signer; pub mod tw_message_signer; pub mod tw_transaction_compiler; pub mod tw_transaction_decoder; +pub mod tw_transaction_util; pub mod tw_wallet_connect_request; diff --git a/rust/tw_any_coin/src/ffi/tw_transaction_util.rs b/rust/tw_any_coin/src/ffi/tw_transaction_util.rs new file mode 100644 index 00000000000..7dd7acbf382 --- /dev/null +++ b/rust/tw_any_coin/src/ffi/tw_transaction_util.rs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#![allow(clippy::missing_safety_doc)] + +use crate::transaction_util::TransactionUtil; +use tw_coin_registry::coin_type::CoinType; +use tw_memory::ffi::tw_string::TWString; +use tw_memory::ffi::RawPtrTrait; +use tw_misc::try_or_else; + +/// Calculate the TX hash of a transaction. +/// +/// \param coin coin type. +/// \param encoded_tx encoded transaction data. +/// \return The TX hash of a transaction, If the input is invalid or the chain is unsupported, null is returned. +#[no_mangle] +pub unsafe extern "C" fn tw_transaction_util_calc_tx_hash( + coin: u32, + encoded_tx: *const TWString, +) -> *mut TWString { + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); + + let encoded_tx = try_or_else!(TWString::from_ptr_as_ref(encoded_tx), std::ptr::null_mut); + let encoded_tx = try_or_else!(encoded_tx.as_str(), std::ptr::null_mut); + + TransactionUtil::calc_tx_hash(coin, encoded_tx) + .map(|output| TWString::from(output).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} diff --git a/rust/tw_any_coin/src/lib.rs b/rust/tw_any_coin/src/lib.rs index 412873c0095..be2b4c4909e 100644 --- a/rust/tw_any_coin/src/lib.rs +++ b/rust/tw_any_coin/src/lib.rs @@ -12,3 +12,4 @@ pub mod wallet_connect_request; #[cfg(feature = "test-utils")] pub mod test_utils; +pub mod transaction_util; diff --git a/rust/tw_any_coin/src/test_utils/mod.rs b/rust/tw_any_coin/src/test_utils/mod.rs index fa4b9e5d3b2..19ddedaf441 100644 --- a/rust/tw_any_coin/src/test_utils/mod.rs +++ b/rust/tw_any_coin/src/test_utils/mod.rs @@ -5,5 +5,6 @@ pub mod address_utils; pub mod plan_utils; pub mod sign_utils; +pub mod transaction_calc_tx_hash_utils; pub mod transaction_decode_utils; pub mod wallet_connect_utils; diff --git a/rust/tw_any_coin/src/test_utils/transaction_calc_tx_hash_utils.rs b/rust/tw_any_coin/src/test_utils/transaction_calc_tx_hash_utils.rs new file mode 100644 index 00000000000..269104a37e1 --- /dev/null +++ b/rust/tw_any_coin/src/test_utils/transaction_calc_tx_hash_utils.rs @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::ffi::tw_transaction_util::tw_transaction_util_calc_tx_hash; +use tw_coin_registry::coin_type::CoinType; +use tw_memory::test_utils::tw_string_helper::TWStringHelper; + +pub struct TransactionUtilHelper; + +impl TransactionUtilHelper { + pub fn calc_tx_hash(coin_type: CoinType, tx: &str) -> String { + let tx_data = TWStringHelper::create(tx); + + TWStringHelper::wrap(unsafe { + tw_transaction_util_calc_tx_hash(coin_type as u32, tx_data.ptr()) + }) + .to_string() + .expect("!tw_transaction_util_calc_tx_hash returned nullptr") + } +} diff --git a/rust/tw_any_coin/src/transaction_util.rs b/rust/tw_any_coin/src/transaction_util.rs new file mode 100644 index 00000000000..09032126f70 --- /dev/null +++ b/rust/tw_any_coin/src/transaction_util.rs @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::error::prelude::*; +use tw_coin_registry::coin_type::CoinType; +use tw_coin_registry::dispatcher::coin_dispatcher; + +pub struct TransactionUtil; + +impl TransactionUtil { + /// Calculate the TX hash of a transaction. + #[inline] + pub fn calc_tx_hash(coin: CoinType, encoded_tx: &str) -> SigningResult { + let (ctx, entry) = coin_dispatcher(coin)?; + entry.calc_tx_hash(&ctx, encoded_tx) + } +} diff --git a/rust/tw_any_coin/tests/chains/aptos/aptos_transaction_util.rs b/rust/tw_any_coin/tests/chains/aptos/aptos_transaction_util.rs new file mode 100644 index 00000000000..8145d24935d --- /dev/null +++ b/rust/tw_any_coin/tests/chains/aptos/aptos_transaction_util.rs @@ -0,0 +1,13 @@ +use tw_any_coin::test_utils::transaction_calc_tx_hash_utils::TransactionUtilHelper; +use tw_coin_registry::coin_type::CoinType; + +#[test] +fn test_aptos_transaction_util_calc_tx_hash() { + let encoded_tx = "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c405707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"; + let tx_hash = TransactionUtilHelper::calc_tx_hash(CoinType::Aptos, encoded_tx); + + assert_eq!( + tx_hash, + "0xb4d62afd3862116e060dd6ad9848ccb50c2bc177799819f1d29c059ae2042467" + ); +} diff --git a/rust/tw_any_coin/tests/chains/aptos/mod.rs b/rust/tw_any_coin/tests/chains/aptos/mod.rs index 4aee9d56923..3b7b55c4d36 100644 --- a/rust/tw_any_coin/tests/chains/aptos/mod.rs +++ b/rust/tw_any_coin/tests/chains/aptos/mod.rs @@ -5,6 +5,7 @@ mod aptos_address; mod aptos_compile; mod aptos_sign; +mod aptos_transaction_util; mod test_cases; const APTOS_COIN_TYPE: u32 = 637; diff --git a/rust/tw_any_coin/tests/chains/bitcoin/bitcoin_transaction_util.rs b/rust/tw_any_coin/tests/chains/bitcoin/bitcoin_transaction_util.rs new file mode 100644 index 00000000000..a42d55cf322 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/bitcoin/bitcoin_transaction_util.rs @@ -0,0 +1,13 @@ +use tw_any_coin::test_utils::transaction_calc_tx_hash_utils::TransactionUtilHelper; +use tw_coin_registry::coin_type::CoinType; + +#[test] +fn test_bitcoin_transaction_util_calc_tx_hash() { + let encoded_tx = "02000000000101089098890d2653567b9e8df2d1fbe5c3c8bf1910ca7184e301db0ad3b495c88e0100000000ffffffff02581b000000000000225120e8b706a97732e705e22ae7710703e7f589ed13c636324461afa443016134cc051040000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d02483045022100a44aa28446a9a886b378a4a65e32ad9a3108870bd725dc6105160bed4f317097022069e9de36422e4ce2e42b39884aa5f626f8f94194d1013007d5a1ea9220a06dce0121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000"; + let tx_hash = TransactionUtilHelper::calc_tx_hash(CoinType::Bitcoin, encoded_tx); + + assert_eq!( + tx_hash, + "797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1" + ); +} diff --git a/rust/tw_any_coin/tests/chains/bitcoin/mod.rs b/rust/tw_any_coin/tests/chains/bitcoin/mod.rs index 7ccbcb26932..25954022bf4 100644 --- a/rust/tw_any_coin/tests/chains/bitcoin/mod.rs +++ b/rust/tw_any_coin/tests/chains/bitcoin/mod.rs @@ -6,3 +6,4 @@ mod bitcoin_address; mod bitcoin_compile; mod bitcoin_plan; mod bitcoin_sign; +mod bitcoin_transaction_util; diff --git a/rust/tw_any_coin/tests/chains/cosmos/cosmos_transaction_util.rs b/rust/tw_any_coin/tests/chains/cosmos/cosmos_transaction_util.rs new file mode 100644 index 00000000000..d986376243e --- /dev/null +++ b/rust/tw_any_coin/tests/chains/cosmos/cosmos_transaction_util.rs @@ -0,0 +1,13 @@ +use tw_any_coin::test_utils::transaction_calc_tx_hash_utils::TransactionUtilHelper; +use tw_coin_registry::coin_type::CoinType; + +#[test] +fn test_cosmos_transaction_util_calc_tx_hash() { + let encoded_tx = "CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseBItY29zbW9zMThzMGhkbnNsbGdjY2x3ZXU5YXltdzRuZ2t0cjJrMHJreWdkemRwGg8KBXVhdG9tEgY0MDAwMDASZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJEgQKAggBEhMKDQoFdWF0b20SBDEwMDAQwJoMGkCvvVE6d29P30cO9/lnXyGunWMPxNY12NuqDcCnFkNM0H4CUQdl1Gc9+ogIJbro5nyzZzlv9rl2/GsZox/JXoCX"; + let tx_hash = TransactionUtilHelper::calc_tx_hash(CoinType::Cosmos, encoded_tx); + + assert_eq!( + tx_hash, + "85392373F54577562067030BF0D61596C91188AA5E6CA8FFE731BD0349296411" + ); +} diff --git a/rust/tw_any_coin/tests/chains/cosmos/mod.rs b/rust/tw_any_coin/tests/chains/cosmos/mod.rs index c81241c5434..fbf72ad83a8 100644 --- a/rust/tw_any_coin/tests/chains/cosmos/mod.rs +++ b/rust/tw_any_coin/tests/chains/cosmos/mod.rs @@ -4,3 +4,4 @@ mod cosmos_address; mod cosmos_sign; +mod cosmos_transaction_util; diff --git a/rust/tw_any_coin/tests/chains/ethereum/ethereum_transaction_util.rs b/rust/tw_any_coin/tests/chains/ethereum/ethereum_transaction_util.rs new file mode 100644 index 00000000000..4b38c8b9ff6 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/ethereum/ethereum_transaction_util.rs @@ -0,0 +1,13 @@ +use tw_any_coin::test_utils::transaction_calc_tx_hash_utils::TransactionUtilHelper; +use tw_coin_registry::coin_type::CoinType; + +#[test] +fn test_ethereum_transaction_util_calc_tx_hash() { + let encoded_tx = "f8aa808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec8000025a0724c62ad4fbf47346b02de06e603e013f26f26b56fdc0be7ba3d6273401d98cea0032131cae15da7ddcda66963e8bef51ca0d9962bfef0547d3f02597a4a58c931"; + let tx_hash = TransactionUtilHelper::calc_tx_hash(CoinType::Ethereum, encoded_tx); + + assert_eq!( + tx_hash, + "0x199a7829fc5149e49b452c2cab76d8fa5a9682fee6e4891b8acb697ac142513e" + ); +} diff --git a/rust/tw_any_coin/tests/chains/ethereum/mod.rs b/rust/tw_any_coin/tests/chains/ethereum/mod.rs index d6e5a36b07b..2587748ad06 100644 --- a/rust/tw_any_coin/tests/chains/ethereum/mod.rs +++ b/rust/tw_any_coin/tests/chains/ethereum/mod.rs @@ -6,3 +6,4 @@ mod ethereum_address; mod ethereum_compile; mod ethereum_message_sign; mod ethereum_sign; +mod ethereum_transaction_util; diff --git a/rust/tw_any_coin/tests/chains/solana/mod.rs b/rust/tw_any_coin/tests/chains/solana/mod.rs index 069e9709509..a680f8aba73 100644 --- a/rust/tw_any_coin/tests/chains/solana/mod.rs +++ b/rust/tw_any_coin/tests/chains/solana/mod.rs @@ -6,4 +6,5 @@ mod solana_address; mod solana_compile; mod solana_sign; mod solana_transaction; +mod solana_transaction_util; mod solana_wallet_connect; diff --git a/rust/tw_any_coin/tests/chains/solana/solana_transaction_util.rs b/rust/tw_any_coin/tests/chains/solana/solana_transaction_util.rs new file mode 100644 index 00000000000..72d19c6a864 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/solana/solana_transaction_util.rs @@ -0,0 +1,13 @@ +use tw_any_coin::test_utils::transaction_calc_tx_hash_utils::TransactionUtilHelper; +use tw_coin_registry::coin_type::CoinType; + +#[test] +fn test_solana_transaction_util_calc_tx_hash() { + let encoded_tx = "AnQTYwZpkm3fs4SdLxnV6gQj3hSLsyacpxDdLMALYWObm722f79IfYFTbZeFK9xHtMumiDOWAM2hHQP4r/GtbARpncaXgOVFv7OgbRLMbuCEJHO1qwcdCbtH72VzyzU8yw9sqqHIAaCUE8xaQTgT6Z5IyZfeyMe2QGJIfOjz65UPAgACBssq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXlMOJD6jUvASrKmdtLK/qXNyJns2Vqcvlk+nfJYdZaFpIWiT/tAcEYbttfxyLdYxrLckAKdVRtf1OrNgtZeMCII4SAn6SYaaidrX/AN3s/aVn/zrlEKW0cEUIatHVDKtXO0Qss5EhV/E6kz0BNCgtAytf/s0Botvxt3kGCN8ALqcG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8Aqe6sdLXiXSDILEtzckCjkjchiSf6zVGpMYiAE5BE2IqHAQUEAgQDAQoMoA8AAAAAAAAG"; + let tx_hash = TransactionUtilHelper::calc_tx_hash(CoinType::Solana, encoded_tx); + + assert_eq!( + tx_hash, + "3KbvREZUat76wgWMtnJfWbJL74Vzh4U2eabVJa3Z3bb2fPtW8AREP5pbmRwUrxZCESbTomWpL41PeKDcPGbojsej" + ); +} diff --git a/rust/tw_any_coin/tests/chains/sui/mod.rs b/rust/tw_any_coin/tests/chains/sui/mod.rs index 53876af861f..c19d11c0bc7 100644 --- a/rust/tw_any_coin/tests/chains/sui/mod.rs +++ b/rust/tw_any_coin/tests/chains/sui/mod.rs @@ -7,6 +7,7 @@ use tw_proto::Sui::Proto; mod sui_address; mod sui_compile; mod sui_sign; +mod sui_transaction_util; mod test_cases; fn object_ref(id: &'static str, version: u64, digest: &'static str) -> Proto::ObjectRef<'static> { diff --git a/rust/tw_any_coin/tests/chains/sui/sui_transaction_util.rs b/rust/tw_any_coin/tests/chains/sui/sui_transaction_util.rs new file mode 100644 index 00000000000..42119d0b4e6 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/sui/sui_transaction_util.rs @@ -0,0 +1,10 @@ +use tw_any_coin::test_utils::transaction_calc_tx_hash_utils::TransactionUtilHelper; +use tw_coin_registry::coin_type::CoinType; + +#[test] +fn test_sui_transaction_util_calc_tx_hash() { + let encoded_tx = "AAACAAgQJwAAAAAAAAAgJZ/4B0q0Jcu0ifI24Y4I8D8aeFa998eih3vWT3OLUBUCAgABAQAAAQEDAAAAAAEBANV1rX8Y6UhGKlz2mPVk7zlKdSpx/sYkk6+KBVwBLA1QAQbywsjB2JZN8QGdZhbpcFcZvrq9kx2idVy5SM635olk7AIAAAAAAAAgYEVuxmf1zRBGdoDr+VDtMpIFF12s2Ua7I2ru1XyGF8/Vda1/GOlIRipc9pj1ZO85SnUqcf7GJJOvigVcASwNUAEAAAAAAAAA0AcAAAAAAAAA"; + let tx_hash = TransactionUtilHelper::calc_tx_hash(CoinType::Sui, encoded_tx); + + assert_eq!(tx_hash, "HkPo6rYPyDY53x1MBszvSZVZyixVN7CHvCJGX381czAh"); +} diff --git a/rust/tw_any_coin/tests/chains/ton/mod.rs b/rust/tw_any_coin/tests/chains/ton/mod.rs index 83843825fdb..ede77ad1aef 100644 --- a/rust/tw_any_coin/tests/chains/ton/mod.rs +++ b/rust/tw_any_coin/tests/chains/ton/mod.rs @@ -7,3 +7,4 @@ mod ton_address; mod ton_compile; mod ton_sign; mod ton_sign_wallet_v5r1; +mod ton_transaction_util; diff --git a/rust/tw_any_coin/tests/chains/ton/ton_transaction_util.rs b/rust/tw_any_coin/tests/chains/ton/ton_transaction_util.rs new file mode 100644 index 00000000000..78a6d727f60 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/ton/ton_transaction_util.rs @@ -0,0 +1,13 @@ +use tw_any_coin::test_utils::transaction_calc_tx_hash_utils::TransactionUtilHelper; +use tw_coin_registry::coin_type::CoinType; + +#[test] +fn test_ton_transaction_util_calc_tx_hash() { + let encoded_tx = "te6cckECGwEAA2sAAkWIACm9HPyVOpjCNOG6nbf+EwCONRHHpeMQsIlCoWNKhUaaHgECAgE0AwQBoXNpZ25///8R/////wAAAACACOfqY7L3l3aKc58eNxuJTaeH/fgBw2aG0coM+hjDpjWhJKbYKmsAD8v054HYSuO6vN3bQnV5U19BhsGfe1MDoAUBFP8A9KQT9LzyyAsGAFGAAAAAP///iKrcG+d35KaMMtuxik4jqNofFL51Mu1f8Qf19onqsDlyIAIKDsPIbQMWBwIBIAgJAWJiAC90HaFSSy94Zxb85WYu97uTKIxAlLvolycpcYkWc+8giFAAAAAAAAAAAAAAAAABFgIBSAoLAQLyDALc0CDXScEgkVuPYyDXCx8gghBleHRuvSGCEHNpbnS9sJJfA+CCEGV4dG66jrSAINchAdB01yH6QDD6RPgo+kQwWL2RW+DtRNCBAUHXIfQFgwf0Dm+hMZEw4YBA1yFwf9s84DEg10mBAoC5kTDgcOIXDQIBIA4PAR4g1wsfghBzaWduuvLgin8NAeaO8O2i7fshgwjXIgKDCNcjIIAg1yHTH9Mf0x/tRNDSANMfINMf0//XCgAK+QFAzPkQmiiUXwrbMeHywIffArNQB7Dy0IRRJbry4IVQNrry4Ib4I7vy0IgikvgA3gGkf8jKAMsfAc8Wye1UIJL4D95w2zzYFwIBIBARABm+Xw9qJoQICg65D6AsAgFuEhMCAUgUFQAZrc52omhAIOuQ64X/wAAZrx32omhAEOuQ64WPwAAXsyX7UTQcdch1wsfgABGyYvtRNDXCgCAAAAP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKGBkaAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQAcjDXLAgkji0h8uCS0gDtRNDSAFETuvLQj1RQMJExnAGBAUDXIdcKAPLgjuLIygBYzxbJ7VST8sCN4gAQk1vbMeHXTNB8Ui06"; + let tx_hash = TransactionUtilHelper::calc_tx_hash(CoinType::TON, encoded_tx); + + assert_eq!( + tx_hash, + "437dae441a95a6bccdcdcea2560c313de24f13dd85c76d5d7ecaab1e70a1e52b" + ); +} diff --git a/rust/tw_bitcoin/src/entry.rs b/rust/tw_bitcoin/src/entry.rs index 850bf638e58..5d6b81508ec 100644 --- a/rust/tw_bitcoin/src/entry.rs +++ b/rust/tw_bitcoin/src/entry.rs @@ -1,6 +1,7 @@ use crate::modules::compiler::BitcoinCompiler; use crate::modules::planner::BitcoinPlanner; use crate::modules::signer::BitcoinSigner; +use crate::modules::transaction_util::BitcoinTransactionUtil; use std::str::FromStr; use tw_coin_entry::coin_context::CoinContext; use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; @@ -29,6 +30,7 @@ impl CoinEntry for BitcoinEntry { type MessageSigner = NoMessageSigner; type WalletConnector = NoWalletConnector; type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = BitcoinTransactionUtil; #[inline] fn parse_address( @@ -89,4 +91,9 @@ impl CoinEntry for BitcoinEntry { fn plan_builder(&self) -> Option { Some(BitcoinPlanner) } + + #[inline] + fn transaction_util(&self) -> Option { + Some(BitcoinTransactionUtil) + } } diff --git a/rust/tw_bitcoin/src/modules/mod.rs b/rust/tw_bitcoin/src/modules/mod.rs index 29d57ad224e..d7ac86f787f 100644 --- a/rust/tw_bitcoin/src/modules/mod.rs +++ b/rust/tw_bitcoin/src/modules/mod.rs @@ -7,4 +7,5 @@ pub mod planner; pub mod protobuf_builder; pub mod signer; pub mod signing_request; +pub mod transaction_util; pub mod tx_builder; diff --git a/rust/tw_bitcoin/src/modules/transaction_util.rs b/rust/tw_bitcoin/src/modules/transaction_util.rs new file mode 100644 index 00000000000..19862095334 --- /dev/null +++ b/rust/tw_bitcoin/src/modules/transaction_util.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use bitcoin::consensus::deserialize; +use bitcoin::Transaction; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::transaction_util::TransactionUtil; +use tw_encoding::hex::decode; + +pub struct BitcoinTransactionUtil; + +impl TransactionUtil for BitcoinTransactionUtil { + fn calc_tx_hash(&self, coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + Self::calc_tx_hash_impl(coin, encoded_tx) + } +} + +impl BitcoinTransactionUtil { + fn calc_tx_hash_impl(_coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + let tx = decode(encoded_tx).map_err(|_| SigningErrorType::Error_input_parse)?; + + // Deserialize the transaction + let tx: Transaction = deserialize(&tx).map_err(|_| SigningErrorType::Error_input_parse)?; + + // Calculate the transaction ID + let txid = tx.txid(); + + // Note: to_string() returns the reversed byte order, which is the RPC format + Ok(txid.to_string()) + } +} diff --git a/rust/tw_coin_entry/src/coin_entry.rs b/rust/tw_coin_entry/src/coin_entry.rs index e9609515d68..85d9cd89f05 100644 --- a/rust/tw_coin_entry/src/coin_entry.rs +++ b/rust/tw_coin_entry/src/coin_entry.rs @@ -15,6 +15,7 @@ use tw_proto::{MessageRead, MessageWrite}; use crate::modules::message_signer::MessageSigner; use crate::modules::transaction_decoder::TransactionDecoder; +use crate::modules::transaction_util::TransactionUtil; use crate::modules::wallet_connector::WalletConnector; pub use tw_proto::{ProtoError, ProtoResult}; @@ -69,6 +70,8 @@ pub trait CoinEntry { /// **Optional**. Use `NoTransactionDecoder` if the blockchain does not support transaction decoding yet. type TransactionDecoder: TransactionDecoder; + type TransactionUtil: TransactionUtil; + /// Tries to parse `Self::Address` from the given `address` string by `coin` type and address `prefix`. fn parse_address( &self, @@ -147,4 +150,11 @@ pub trait CoinEntry { fn transaction_decoder(&self) -> Option { None } + + /// It is optional, Transaction util, for example, calculating the TX hash of a transaction. + /// Returns `Ok(None)` if the blockchain does not support transaction util yet. + #[inline] + fn transaction_util(&self) -> Option { + None + } } diff --git a/rust/tw_coin_entry/src/coin_entry_ext.rs b/rust/tw_coin_entry/src/coin_entry_ext.rs index a07546ae190..6983bb99848 100644 --- a/rust/tw_coin_entry/src/coin_entry_ext.rs +++ b/rust/tw_coin_entry/src/coin_entry_ext.rs @@ -10,6 +10,7 @@ use crate::modules::json_signer::JsonSigner; use crate::modules::message_signer::MessageSigner; use crate::modules::plan_builder::PlanBuilder; use crate::modules::transaction_decoder::TransactionDecoder; +use crate::modules::transaction_util::TransactionUtil; use crate::modules::wallet_connector::WalletConnector; use crate::prefix::AddressPrefix; use tw_keypair::tw::{PrivateKey, PublicKey}; @@ -94,6 +95,9 @@ pub trait CoinEntryExt { /// Decodes a transaction from binary representation. fn decode_transaction(&self, coin: &dyn CoinContext, tx: &[u8]) -> SigningResult; + + /// Calculate the TX hash of a transaction. + fn calc_tx_hash(&self, coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult; } impl CoinEntryExt for T @@ -242,4 +246,12 @@ where let output = tx_decoder.decode_transaction(coin, tx); serialize(&output).map_err(SigningError::from) } + + fn calc_tx_hash(&self, coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + let Some(tx_util) = self.transaction_util() else { + return TWError::err(SigningErrorType::Error_not_supported); + }; + + tx_util.calc_tx_hash(coin, encoded_tx) + } } diff --git a/rust/tw_coin_entry/src/modules/mod.rs b/rust/tw_coin_entry/src/modules/mod.rs index bf9d37ccd6e..89a93030d4b 100644 --- a/rust/tw_coin_entry/src/modules/mod.rs +++ b/rust/tw_coin_entry/src/modules/mod.rs @@ -8,4 +8,5 @@ pub mod json_signer; pub mod message_signer; pub mod plan_builder; pub mod transaction_decoder; +pub mod transaction_util; pub mod wallet_connector; diff --git a/rust/tw_coin_entry/src/modules/transaction_util.rs b/rust/tw_coin_entry/src/modules/transaction_util.rs new file mode 100644 index 00000000000..122216f5137 --- /dev/null +++ b/rust/tw_coin_entry/src/modules/transaction_util.rs @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::coin_context::CoinContext; +use crate::error::prelude::*; + +pub trait TransactionUtil { + /// Calculate the TX hash of a transaction. + fn calc_tx_hash(&self, coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult; +} + +/// `NoTransactionUtil` can't be created since there are no enum variants. +pub enum NoTransactionUtil {} + +impl TransactionUtil for NoTransactionUtil { + fn calc_tx_hash(&self, _coin: &dyn CoinContext, _encoded_tx: &str) -> SigningResult { + panic!("`NoTransactionUtil` should never be constructed and used") + } +} diff --git a/rust/tw_cosmos_sdk/src/modules/mod.rs b/rust/tw_cosmos_sdk/src/modules/mod.rs index e49153b3c92..78d55f5dedd 100644 --- a/rust/tw_cosmos_sdk/src/modules/mod.rs +++ b/rust/tw_cosmos_sdk/src/modules/mod.rs @@ -6,4 +6,5 @@ pub mod broadcast_msg; pub mod compiler; pub mod serializer; pub mod signer; +pub mod transaction_util; pub mod tx_builder; diff --git a/rust/tw_cosmos_sdk/src/modules/transaction_util.rs b/rust/tw_cosmos_sdk/src/modules/transaction_util.rs new file mode 100644 index 00000000000..44ed31dcf34 --- /dev/null +++ b/rust/tw_cosmos_sdk/src/modules/transaction_util.rs @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::context::CosmosContext; +use std::marker::PhantomData; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::transaction_util::TransactionUtil; +use tw_encoding::base64; +use tw_encoding::base64::STANDARD; +use tw_encoding::hex::encode; + +pub struct CosmosTransactionUtil { + _phantom: PhantomData, +} + +impl Default for CosmosTransactionUtil { + fn default() -> Self { + Self { + _phantom: PhantomData, + } + } +} + +impl TransactionUtil for CosmosTransactionUtil { + fn calc_tx_hash(&self, coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + Self::calc_tx_hash_impl(coin, encoded_tx) + } +} + +impl CosmosTransactionUtil { + fn calc_tx_hash_impl(_coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + let tx = base64::decode(encoded_tx, STANDARD)?; + + let tx_hash = Context::default_tx_hasher().hash(&tx); + + Ok(encode(&tx_hash, false).to_uppercase()) + } +} diff --git a/rust/tw_evm/src/modules/mod.rs b/rust/tw_evm/src/modules/mod.rs index 526e4bf78a1..fb246766ac1 100644 --- a/rust/tw_evm/src/modules/mod.rs +++ b/rust/tw_evm/src/modules/mod.rs @@ -7,4 +7,5 @@ pub mod compiler; pub mod message_signer; pub mod rlp_encoder; pub mod signer; +pub mod transaction_util; pub mod tx_builder; diff --git a/rust/tw_evm/src/modules/transaction_util.rs b/rust/tw_evm/src/modules/transaction_util.rs new file mode 100644 index 00000000000..632634653d8 --- /dev/null +++ b/rust/tw_evm/src/modules/transaction_util.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::transaction_util::TransactionUtil; +use tw_encoding::hex::{decode, encode}; +use tw_hash::sha3::keccak256; + +pub struct EvmTransactionUtil; + +impl TransactionUtil for EvmTransactionUtil { + fn calc_tx_hash(&self, coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + Self::calc_tx_hash_impl(coin, encoded_tx) + } +} + +impl EvmTransactionUtil { + fn calc_tx_hash_impl(_coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + let tx = decode(encoded_tx).map_err(|_| SigningErrorType::Error_input_parse)?; + + Ok(encode(keccak256(&tx), true)) + } +} diff --git a/src/interface/TWTranscationUtil.cpp b/src/interface/TWTranscationUtil.cpp new file mode 100644 index 00000000000..3f99dd81a83 --- /dev/null +++ b/src/interface/TWTranscationUtil.cpp @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TrustWalletCore/TWTransactionUtil.h" +#include "rust/Wrapper.h" + +using namespace TW; + +TWString* _Nullable TWTransactionUtilCalcTxHash(enum TWCoinType coinType, TWString* _Nonnull encodedTx) { + try { + if (encodedTx == nullptr) { + return nullptr; + } + const Rust::TWStringWrapper encodedTxWrapper = TWStringUTF8Bytes(encodedTx); + + const Rust::TWStringWrapper outputDataPtr = Rust::tw_transaction_util_calc_tx_hash(static_cast(coinType), encodedTxWrapper.get()); + if (!outputDataPtr) { + return nullptr; + } + + return TWStringCreateWithUTF8Bytes(outputDataPtr.c_str()); + } catch (...) { + return nullptr; + } +} diff --git a/tests/interface/TWTransactionUtilTests.cpp b/tests/interface/TWTransactionUtilTests.cpp new file mode 100644 index 00000000000..c6b59420e6b --- /dev/null +++ b/tests/interface/TWTransactionUtilTests.cpp @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include +#include +#include + +#include "Bitcoin/Script.h" +#include "uint256.h" + +#include "TestUtilities.h" +#include + +#include + +using namespace TW; + +TEST(TWTransactionUtil, CalcTxHashBitcoin) { + constexpr auto coin = TWCoinTypeBitcoin; + const auto encodedTxPtr = WRAPS(TWStringCreateWithUTF8Bytes("02000000000101089098890d2653567b9e8df2d1fbe5c3c8bf1910ca7184e301db0ad3b495c88e0100000000ffffffff02581b000000000000225120e8b706a97732e705e22ae7710703e7f589ed13c636324461afa443016134cc051040000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d02483045022100a44aa28446a9a886b378a4a65e32ad9a3108870bd725dc6105160bed4f317097022069e9de36422e4ce2e42b39884aa5f626f8f94194d1013007d5a1ea9220a06dce0121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000")); + const auto txHashResult = WRAPS(TWTransactionUtilCalcTxHash(coin, encodedTxPtr.get())); + + assertStringsEqual(txHashResult, "797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1"); +} + +TEST(TWTransactionUtil, CalcTxHashEthereum) { + constexpr auto coin = TWCoinTypeEthereum; + const auto encodedTxPtr = WRAPS(TWStringCreateWithUTF8Bytes("f8aa808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec8000025a0724c62ad4fbf47346b02de06e603e013f26f26b56fdc0be7ba3d6273401d98cea0032131cae15da7ddcda66963e8bef51ca0d9962bfef0547d3f02597a4a58c931")); + const auto txHashResult = WRAPS(TWTransactionUtilCalcTxHash(coin, encodedTxPtr.get())); + + assertStringsEqual(txHashResult, "0x199a7829fc5149e49b452c2cab76d8fa5a9682fee6e4891b8acb697ac142513e"); +} + +TEST(TWTransactionUtil, CalcTxHashSolana) { + constexpr auto coin = TWCoinTypeSolana; + const auto encodedTxPtr = WRAPS(TWStringCreateWithUTF8Bytes("AnQTYwZpkm3fs4SdLxnV6gQj3hSLsyacpxDdLMALYWObm722f79IfYFTbZeFK9xHtMumiDOWAM2hHQP4r/GtbARpncaXgOVFv7OgbRLMbuCEJHO1qwcdCbtH72VzyzU8yw9sqqHIAaCUE8xaQTgT6Z5IyZfeyMe2QGJIfOjz65UPAgACBssq8Im1alV3N7wXGODL8jLPWwLhTuCqfGZ1Iz9fb5tXlMOJD6jUvASrKmdtLK/qXNyJns2Vqcvlk+nfJYdZaFpIWiT/tAcEYbttfxyLdYxrLckAKdVRtf1OrNgtZeMCII4SAn6SYaaidrX/AN3s/aVn/zrlEKW0cEUIatHVDKtXO0Qss5EhV/E6kz0BNCgtAytf/s0Botvxt3kGCN8ALqcG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8Aqe6sdLXiXSDILEtzckCjkjchiSf6zVGpMYiAE5BE2IqHAQUEAgQDAQoMoA8AAAAAAAAG")); + const auto txHashResult = WRAPS(TWTransactionUtilCalcTxHash(coin, encodedTxPtr.get())); + + assertStringsEqual(txHashResult, "3KbvREZUat76wgWMtnJfWbJL74Vzh4U2eabVJa3Z3bb2fPtW8AREP5pbmRwUrxZCESbTomWpL41PeKDcPGbojsej"); +} + +TEST(TWTransactionUtil, CalcTxHashCosmos) { + constexpr auto coin = TWCoinTypeCosmos; + const auto encodedTxPtr = WRAPS(TWStringCreateWithUTF8Bytes("CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseBItY29zbW9zMThzMGhkbnNsbGdjY2x3ZXU5YXltdzRuZ2t0cjJrMHJreWdkemRwGg8KBXVhdG9tEgY0MDAwMDASZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJEgQKAggBEhMKDQoFdWF0b20SBDEwMDAQwJoMGkCvvVE6d29P30cO9/lnXyGunWMPxNY12NuqDcCnFkNM0H4CUQdl1Gc9+ogIJbro5nyzZzlv9rl2/GsZox/JXoCX")); + const auto txHashResult = WRAPS(TWTransactionUtilCalcTxHash(coin, encodedTxPtr.get())); + + assertStringsEqual(txHashResult, "85392373F54577562067030BF0D61596C91188AA5E6CA8FFE731BD0349296411"); +} + +TEST(TWTransactionUtil, CalcTxHashTon) { + constexpr auto coin = TWCoinTypeTON; + const auto encodedTxPtr = WRAPS(TWStringCreateWithUTF8Bytes("te6cckECGwEAA2sAAkWIACm9HPyVOpjCNOG6nbf+EwCONRHHpeMQsIlCoWNKhUaaHgECAgE0AwQBoXNpZ25///8R/////wAAAACACOfqY7L3l3aKc58eNxuJTaeH/fgBw2aG0coM+hjDpjWhJKbYKmsAD8v054HYSuO6vN3bQnV5U19BhsGfe1MDoAUBFP8A9KQT9LzyyAsGAFGAAAAAP///iKrcG+d35KaMMtuxik4jqNofFL51Mu1f8Qf19onqsDlyIAIKDsPIbQMWBwIBIAgJAWJiAC90HaFSSy94Zxb85WYu97uTKIxAlLvolycpcYkWc+8giFAAAAAAAAAAAAAAAAABFgIBSAoLAQLyDALc0CDXScEgkVuPYyDXCx8gghBleHRuvSGCEHNpbnS9sJJfA+CCEGV4dG66jrSAINchAdB01yH6QDD6RPgo+kQwWL2RW+DtRNCBAUHXIfQFgwf0Dm+hMZEw4YBA1yFwf9s84DEg10mBAoC5kTDgcOIXDQIBIA4PAR4g1wsfghBzaWduuvLgin8NAeaO8O2i7fshgwjXIgKDCNcjIIAg1yHTH9Mf0x/tRNDSANMfINMf0//XCgAK+QFAzPkQmiiUXwrbMeHywIffArNQB7Dy0IRRJbry4IVQNrry4Ib4I7vy0IgikvgA3gGkf8jKAMsfAc8Wye1UIJL4D95w2zzYFwIBIBARABm+Xw9qJoQICg65D6AsAgFuEhMCAUgUFQAZrc52omhAIOuQ64X/wAAZrx32omhAEOuQ64WPwAAXsyX7UTQcdch1wsfgABGyYvtRNDXCgCAAAAP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKGBkaAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQAcjDXLAgkji0h8uCS0gDtRNDSAFETuvLQj1RQMJExnAGBAUDXIdcKAPLgjuLIygBYzxbJ7VST8sCN4gAQk1vbMeHXTNB8Ui06")); + const auto txHashResult = WRAPS(TWTransactionUtilCalcTxHash(coin, encodedTxPtr.get())); + + assertStringsEqual(txHashResult, "437dae441a95a6bccdcdcea2560c313de24f13dd85c76d5d7ecaab1e70a1e52b"); +} + +TEST(TWTransactionUtil, CalcTxHashAptos) { + constexpr auto coin = TWCoinTypeAptos; + const auto encodedTxPtr = WRAPS(TWStringCreateWithUTF8Bytes("07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c405707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01")); + const auto txHashResult = WRAPS(TWTransactionUtilCalcTxHash(coin, encodedTxPtr.get())); + + assertStringsEqual(txHashResult, "0xb4d62afd3862116e060dd6ad9848ccb50c2bc177799819f1d29c059ae2042467"); +} + +TEST(TWTransactionUtil, CalcTxHashSui) { + constexpr auto coin = TWCoinTypeSui; + const auto encodedTxPtr = WRAPS(TWStringCreateWithUTF8Bytes("AAACAAgQJwAAAAAAAAAgJZ/4B0q0Jcu0ifI24Y4I8D8aeFa998eih3vWT3OLUBUCAgABAQAAAQEDAAAAAAEBANV1rX8Y6UhGKlz2mPVk7zlKdSpx/sYkk6+KBVwBLA1QAQbywsjB2JZN8QGdZhbpcFcZvrq9kx2idVy5SM635olk7AIAAAAAAAAgYEVuxmf1zRBGdoDr+VDtMpIFF12s2Ua7I2ru1XyGF8/Vda1/GOlIRipc9pj1ZO85SnUqcf7GJJOvigVcASwNUAEAAAAAAAAA0AcAAAAAAAAA")); + const auto txHashResult = WRAPS(TWTransactionUtilCalcTxHash(coin, encodedTxPtr.get())); + + assertStringsEqual(txHashResult, "HkPo6rYPyDY53x1MBszvSZVZyixVN7CHvCJGX381czAh"); +}