diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkSigner.kt index 6fb0edc455c..59b3fcdd465 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkSigner.kt @@ -25,7 +25,6 @@ class TestTheOpenNetworkSigner { val privateKey = PrivateKey("c38f49de2fb13223a9e7d37d5d0ffbdd89a5eb7c8b0ee4d1c299f2cefe7dc4a0".toHexByteArray()) val transfer = TheOpenNetwork.Transfer.newBuilder() - .setWalletVersion(TheOpenNetwork.WalletVersion.WALLET_V4_R2) .setDest("EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q") .setAmount(10) .setMode(TheOpenNetwork.SendMode.PAY_FEES_SEPARATELY_VALUE or TheOpenNetwork.SendMode.IGNORE_ACTION_PHASE_ERRORS_VALUE) @@ -37,6 +36,7 @@ class TestTheOpenNetworkSigner { .addMessages(transfer) .setSequenceNumber(6) .setExpireAt(1671132440) + .setWalletVersion(TheOpenNetwork.WalletVersion.WALLET_V4_R2) .build() val output = AnySigner.sign(input, CoinType.TON, SigningOutput.parser()) @@ -59,7 +59,6 @@ class TestTheOpenNetworkSigner { .build() val transfer = TheOpenNetwork.Transfer.newBuilder() - .setWalletVersion(TheOpenNetwork.WalletVersion.WALLET_V4_R2) .setDest("EQBiaD8PO1NwfbxSkwbcNT9rXDjqhiIvXWymNO-edV0H5lja") .setAmount(100 * 1000 * 1000) .setMode(TheOpenNetwork.SendMode.PAY_FEES_SEPARATELY_VALUE or TheOpenNetwork.SendMode.IGNORE_ACTION_PHASE_ERRORS_VALUE) @@ -72,6 +71,7 @@ class TestTheOpenNetworkSigner { .addMessages(transfer) .setSequenceNumber(1) .setExpireAt(1787693046) + .setWalletVersion(TheOpenNetwork.WalletVersion.WALLET_V4_R2) .build() val output = AnySigner.sign(input, CoinType.TON, SigningOutput.parser()) @@ -103,7 +103,6 @@ class TestTheOpenNetworkSigner { .build() val transfer = TheOpenNetwork.Transfer.newBuilder() - .setWalletVersion(TheOpenNetwork.WalletVersion.WALLET_V4_R2) .setDest(dogeChatbotDeployingAddress) // 0.069 TON .setAmount(69_000_000) @@ -116,6 +115,7 @@ class TestTheOpenNetworkSigner { .addMessages(transfer) .setSequenceNumber(4) .setExpireAt(1721939714) + .setWalletVersion(TheOpenNetwork.WalletVersion.WALLET_V4_R2) .build() val output = AnySigner.sign(input, CoinType.TON, SigningOutput.parser()) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestLiquidStaking.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestLiquidStaking.kt index 41adb7f1d4f..44701d5f2b1 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestLiquidStaking.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestLiquidStaking.kt @@ -29,7 +29,7 @@ class TestLiquidStaking { stake = LiquidStaking.Stake.newBuilder().apply { amount = "1000000000000000000" asset = LiquidStaking.Asset.newBuilder().apply { - stakingToken = LiquidStaking.Coin.MATIC + stakingToken = LiquidStaking.Coin.POL }.build() }.build() } 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/docs/registry.md b/docs/registry.md index b278eaba2a0..bfbc3b27235 100644 --- a/docs/registry.md +++ b/docs/registry.md @@ -75,7 +75,7 @@ This list is generated from [./registry.json](../registry.json) | 889 | Viction | VIC | | | | 899 | eCash | XEC | | | | 931 | THORChain | RUNE | | | -| 966 | Polygon | MATIC | | | +| 966 | Polygon | POL | | | | 996 | OKX Chain | OKT | | | | 999 | Bitcoin Diamond | BCD | | | | 1001 | ThunderCore | TT | | | 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/protobuf-plugin/CMakeLists.txt b/protobuf-plugin/CMakeLists.txt index 52694a9c14e..237789be7be 100644 --- a/protobuf-plugin/CMakeLists.txt +++ b/protobuf-plugin/CMakeLists.txt @@ -2,30 +2,25 @@ # # Copyright © 2017 Trust Wallet. -cmake_minimum_required(VERSION 3.2 FATAL_ERROR) +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) project(TrustWalletCoreProtobufPlugin) set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "Minimum OS X deployment version" FORCE) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -if ("$ENV{PREFIX}" STREQUAL "") +if("$ENV{PREFIX}" STREQUAL "") set(PREFIX "${CMAKE_SOURCE_DIR}/../build/local") else() set(PREFIX "$ENV{PREFIX}") endif() -include_directories(${PREFIX}/include) -link_directories(${PREFIX}/lib) +find_package(Protobuf CONFIG REQUIRED PATH ${PREFIX}/lib/pkgconfig) -find_package(Protobuf REQUIRED PATH ${PREFIX}/lib/pkgconfig) -include_directories(${Protobuf_INCLUDE_DIRS}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) +add_executable(protoc-gen-c-typedef c_typedef.cc) +target_link_libraries(protoc-gen-c-typedef protobuf::libprotobuf protobuf::libprotoc) -add_executable(protoc-gen-c-typedef c_typedef.cc ${PROTO_SRCS} ${PROTO_HDRS}) -target_link_libraries(protoc-gen-c-typedef protobuf -lprotoc -pthread) - -add_executable(protoc-gen-swift-typealias swift_typealias.cc ${PROTO_SRCS} ${PROTO_HDRS}) -target_link_libraries(protoc-gen-swift-typealias protobuf -lprotoc -pthread) +add_executable(protoc-gen-swift-typealias swift_typealias.cc) +target_link_libraries(protoc-gen-swift-typealias protobuf::libprotobuf protobuf::libprotoc) install(TARGETS protoc-gen-c-typedef protoc-gen-swift-typealias DESTINATION bin) diff --git a/protobuf-plugin/c_typedef.cc b/protobuf-plugin/c_typedef.cc index 5197185f9ac..1352898cb73 100644 --- a/protobuf-plugin/c_typedef.cc +++ b/protobuf-plugin/c_typedef.cc @@ -14,7 +14,7 @@ class Generator : public compiler::CodeGenerator { return "TW" + proto_file.substr(0, index) + "Proto.h"; } - bool Generate(const FileDescriptor* file, const std::string& parameter, compiler::GeneratorContext* generator_context, string* error) const { + bool Generate(const FileDescriptor* file, const std::string& parameter, compiler::GeneratorContext* generator_context, std::string* error) const { std::unique_ptr output(generator_context->Open(GetOutputFilename(file->name()))); io::Printer printer(output.get(), '$'); diff --git a/protobuf-plugin/swift_typealias.cc b/protobuf-plugin/swift_typealias.cc index 40e84df0ee9..3d0983c2ca3 100644 --- a/protobuf-plugin/swift_typealias.cc +++ b/protobuf-plugin/swift_typealias.cc @@ -16,7 +16,7 @@ class Generator : public compiler::CodeGenerator { return proto_file.substr(0, index) + "+Proto.swift"; } - bool Generate(const FileDescriptor* file, const std::string& parameter, compiler::GeneratorContext* generator_context, string* error) const { + bool Generate(const FileDescriptor* file, const std::string& parameter, compiler::GeneratorContext* generator_context, std::string* error) const { std::unique_ptr output(generator_context->Open(GetOutputFilename(file->name()))); io::Printer printer(output.get(), '$'); diff --git a/registry.json b/registry.json index ab83bc849f1..f04b7224460 100644 --- a/registry.json +++ b/registry.json @@ -3108,7 +3108,7 @@ "id": "polygon", "name": "Polygon", "coinId": 966, - "symbol": "MATIC", + "symbol": "POL", "decimals": 18, "blockchain": "Ethereum", "derivation": [ @@ -3129,9 +3129,9 @@ }, "info": { "url": "https://polygon.technology", - "source": "https://github.com/maticnetwork/contracts", + "source": "https://github.com/maticnetwork", "rpc": "https://polygon-rpc.com", - "documentation": "https://eth.wiki/json-rpc/API" + "documentation": "https://docs.polygon.technology" } }, { 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/Cargo.toml b/rust/chains/tw_ton/Cargo.toml index 61485b8f034..145508522a6 100644 --- a/rust/chains/tw_ton/Cargo.toml +++ b/rust/chains/tw_ton/Cargo.toml @@ -11,6 +11,7 @@ tw_hash = { path = "../../tw_hash" } tw_keypair = { path = "../../tw_keypair" } tw_memory = { path = "../../tw_memory" } tw_number = { path = "../../tw_number" } +tw_misc = { path = "../../tw_misc" } tw_proto = { path = "../../tw_proto" } tw_ton_sdk = { path = "../../frameworks/tw_ton_sdk" } zeroize = "1.8.1" diff --git a/rust/chains/tw_ton/resources/wallet/wallet_v5r1.code b/rust/chains/tw_ton/resources/wallet/wallet_v5r1.code new file mode 100644 index 00000000000..586cfc39e88 --- /dev/null +++ b/rust/chains/tw_ton/resources/wallet/wallet_v5r1.code @@ -0,0 +1 @@ +te6cckECFAEAAoEAART/APSkE/S88sgLAQIBIAINAgFIAwQC3NAg10nBIJFbj2Mg1wsfIIIQZXh0br0hghBzaW50vbCSXwPgghBleHRuuo60gCDXIQHQdNch+kAw+kT4KPpEMFi9kVvg7UTQgQFB1yH0BYMH9A5voTGRMOGAQNchcH/bPOAxINdJgQKAuZEw4HDiEA8CASAFDAIBIAYJAgFuBwgAGa3OdqJoQCDrkOuF/8AAGa8d9qJoQBDrkOuFj8ACAUgKCwAXsyX7UTQcdch1wsfgABGyYvtRNDXCgCAAGb5fD2omhAgKDrkPoCwBAvIOAR4g1wsfghBzaWduuvLgin8PAeaO8O2i7fshgwjXIgKDCNcjIIAg1yHTH9Mf0x/tRNDSANMfINMf0//XCgAK+QFAzPkQmiiUXwrbMeHywIffArNQB7Dy0IRRJbry4IVQNrry4Ib4I7vy0IgikvgA3gGkf8jKAMsfAc8Wye1UIJL4D95w2zzYEAP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKERITAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQAcjDXLAgkji0h8uCS0gDtRNDSAFETuvLQj1RQMJExnAGBAUDXIdcKAPLgjuLIygBYzxbJ7VST8sCN4gAQk1vbMeHXTNC01sNe \ No newline at end of file diff --git a/rust/chains/tw_ton/src/compiler.rs b/rust/chains/tw_ton/src/compiler.rs index 060e6879acd..2bcea271351 100644 --- a/rust/chains/tw_ton/src/compiler.rs +++ b/rust/chains/tw_ton/src/compiler.rs @@ -2,12 +2,20 @@ // // Copyright © 2017 Trust Wallet. +use crate::signing_request::builder::SigningRequestBuilder; +use crate::signing_request::cell_creator::ExternalMessageCreator; +use std::borrow::Cow; use tw_coin_entry::coin_context::CoinContext; use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; use tw_coin_entry::error::prelude::*; use tw_coin_entry::signing_output_error; +use tw_keypair::ed25519::Signature; use tw_proto::TheOpenNetwork::Proto; use tw_proto::TxCompiler::Proto as CompilerProto; +use tw_ton_sdk::boc::BagOfCells; +use tw_ton_sdk::error::cell_to_signing_error; + +pub(crate) const HAS_CRC32: bool = true; pub struct TheOpenNetworkCompiler; @@ -23,10 +31,18 @@ impl TheOpenNetworkCompiler { fn preimage_hashes_impl( _coin: &dyn CoinContext, - _input: Proto::SigningInput<'_>, + input: Proto::SigningInput<'_>, ) -> SigningResult> { - SigningError::err(SigningErrorType::Error_not_supported) - .context("Transaction pre-image hashing is not supported for TON blockchain yet") + let signing_request = SigningRequestBuilder::build(&input)?; + + let external_message = + ExternalMessageCreator::create_external_message_to_sign(&signing_request) + .map_err(cell_to_signing_error)?; + + Ok(CompilerProto::PreSigningOutput { + data: Cow::from(external_message.cell_hash().to_vec()), + ..CompilerProto::PreSigningOutput::default() + }) } #[inline] @@ -42,11 +58,46 @@ impl TheOpenNetworkCompiler { fn compile_impl( _coin: &dyn CoinContext, - _input: Proto::SigningInput<'_>, - _signatures: Vec, + input: Proto::SigningInput<'_>, + signatures: Vec, _public_keys: Vec, ) -> SigningResult> { - SigningError::err(SigningErrorType::Error_not_supported) - .context("Transaction compiling is not supported for TON blockchain yet") + if signatures.len() != 1 { + return TWError::err(SigningErrorType::Error_signatures_count) + .context("Expected exactly one signature"); + } + let signature = Signature::try_from(signatures[0].as_slice())?; + + let signing_request = SigningRequestBuilder::build(&input)?; + + let external_message = + ExternalMessageCreator::create_external_message_to_sign(&signing_request) + .map_err(cell_to_signing_error)?; + + let signed_external_message = signing_request + .wallet + .compile_signed_external_message(external_message, signature)?; + + // Whether to add 'StateInit' reference. + let state_init = signing_request.seqno == 0; + let signed_tx = signing_request + .wallet + .compile_transaction(signed_external_message, state_init) + .context("Error compiling an external message")? + .build() + .context("Error generating signed message cell") + .map_err(cell_to_signing_error)?; + + let signed_tx_hash = signed_tx.cell_hash(); + let signed_tx_encoded = BagOfCells::from_root(signed_tx) + .to_base64(HAS_CRC32) + .context("Error serializing signed transaction as BoC") + .map_err(cell_to_signing_error)?; + + Ok(Proto::SigningOutput { + encoded: signed_tx_encoded.into(), + hash: signed_tx_hash.to_vec().into(), + ..Proto::SigningOutput::default() + }) } } diff --git a/rust/chains/tw_ton/src/entry.rs b/rust/chains/tw_ton/src/entry.rs index a0bbf9cd165..a2bd5aadc2d 100644 --- a/rust/chains/tw_ton/src/entry.rs +++ b/rust/chains/tw_ton/src/entry.rs @@ -4,8 +4,9 @@ use crate::address::TonAddress; use crate::compiler::TheOpenNetworkCompiler; +use crate::modules::transaction_util::TonTransactionUtil; use crate::signer::TheOpenNetworkSigner; -use crate::wallet::TonWallet; +use crate::wallet::{wallet_v4, VersionedTonWallet}; use std::str::FromStr; use tw_coin_entry::coin_context::CoinContext; use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; @@ -36,6 +37,7 @@ impl CoinEntry for TheOpenNetworkEntry { type MessageSigner = NoMessageSigner; type WalletConnector = NoWalletConnector; type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = TonTransactionUtil; #[inline] fn parse_address( @@ -68,9 +70,12 @@ impl CoinEntry for TheOpenNetworkEntry { let ed25519_pubkey = public_key .to_ed25519() .ok_or(AddressError::PublicKeyTypeMismatch)?; - TonWallet::std_with_public_key(ed25519_pubkey.clone()) - .map(|wallet| wallet.address().clone()) - .map_err(|_| AddressError::Internal) + // Currently, we use the V4R2 wallet + let wallet = VersionedTonWallet::V4R2( + wallet_v4::WalletV4R2::std_with_public_key(ed25519_pubkey.clone()) + .map_err(|_| AddressError::Internal)?, + ); + Ok(wallet.address().clone()) } #[inline] @@ -97,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/message/external_message/mod.rs b/rust/chains/tw_ton/src/message/external_message/mod.rs new file mode 100644 index 00000000000..88c077ae30c --- /dev/null +++ b/rust/chains/tw_ton/src/message/external_message/mod.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod wallet_v4; +pub mod wallet_v5; diff --git a/rust/chains/tw_ton/src/message/external_message.rs b/rust/chains/tw_ton/src/message/external_message/wallet_v4.rs similarity index 71% rename from rust/chains/tw_ton/src/message/external_message.rs rename to rust/chains/tw_ton/src/message/external_message/wallet_v4.rs index 874c45b8469..d23d0090de9 100644 --- a/rust/chains/tw_ton/src/message/external_message.rs +++ b/rust/chains/tw_ton/src/message/external_message/wallet_v4.rs @@ -7,26 +7,21 @@ use tw_ton_sdk::cell::cell_builder::CellBuilder; use tw_ton_sdk::cell::Cell; use tw_ton_sdk::error::CellResult; -pub struct ExternalMessage { +pub struct ExternalMessageWalletV4 { pub wallet_id: i32, pub expire_at: u32, pub seqno: u32, - /// Whether the wallet version supports OP codes, - /// eg https://github.com/ton-blockchain/wallet-contract/blob/4111fd9e3313ec17d99ca9b5b1656445b5b49d8f/func/wallet-v4-code.fc#L94 - pub has_op: bool, pub internal_messages: Vec, } -impl ExternalMessage { +impl ExternalMessageWalletV4 { pub fn build(&self) -> CellResult { let mut builder = CellBuilder::new(); builder .store_i32(32, self.wallet_id)? .store_u32(32, self.expire_at)? .store_u32(32, self.seqno)?; - if self.has_op { - builder.store_u8(8, 0)?; - } + builder.store_u8(8, 0)?; // has op for internal_message in self.internal_messages.iter() { builder.store_u8(8, internal_message.mode)?; builder.store_reference(&internal_message.message)?; diff --git a/rust/chains/tw_ton/src/message/external_message/wallet_v5.rs b/rust/chains/tw_ton/src/message/external_message/wallet_v5.rs new file mode 100644 index 00000000000..af4a2863323 --- /dev/null +++ b/rust/chains/tw_ton/src/message/external_message/wallet_v5.rs @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::message::out_list::build_out_list; +use crate::message::out_list::out_action::OutAction; +use tw_coin_entry::error::prelude::ResultContext; +use tw_ton_sdk::cell::cell_builder::CellBuilder; +use tw_ton_sdk::cell::Cell; +use tw_ton_sdk::error::{CellError, CellErrorType, CellResult}; + +const SEND_MODE_IGNORE_ACTION_PHASE_ERRORS: u8 = 0x02; + +pub enum V5R1OpCode { + // Currently, only AuthSignedExternal is supported. AuthSignedInternal/AuthExtension are not supported. + AuthSignedExternal, +} + +impl V5R1OpCode { + // See https://github.com/ton-blockchain/wallet-contract-v5/blob/88557ebc33047a95207f6e47ac8aadb102dff744/contracts/wallet_v5.fc#L33 + pub fn to_ser_tag(&self) -> u32 { + match self { + V5R1OpCode::AuthSignedExternal => 0x7369676e, + } + } +} + +pub struct ExternalMessageWalletV5 { + pub opcode: V5R1OpCode, + pub wallet_id: i32, + pub expire_at: u32, + pub seqno: u32, + /// Currently, only basic actions are supported. Extended actions are not supported. + pub basic_actions: Vec, +} + +impl ExternalMessageWalletV5 { + /// Build the external message for wallet v5. + pub fn build(&self) -> CellResult { + // Check the number of basic actions + if self.basic_actions.len() > 255 { + return CellError::err(CellErrorType::InternalError) + .context("Maximum number of actions in a single request is 255"); + } + + let mut builder = CellBuilder::new(); + match self.opcode { + V5R1OpCode::AuthSignedExternal => { + builder.store_u32(32, self.opcode.to_ser_tag())?; + + // Make sure +2 flag (ignore errors send mode) is set for all external send messages + // See https://github.com/ton-blockchain/wallet-contract-v5/blob/88557ebc33047a95207f6e47ac8aadb102dff744/contracts/wallet_v5.fc#L82 + for action in &self.basic_actions { + if (action.mode & SEND_MODE_IGNORE_ACTION_PHASE_ERRORS) == 0 { + return CellError::err(CellErrorType::InternalError) + .context("External send message must have ignore errors send mode"); + } + } + }, + } + builder + .store_i32(32, self.wallet_id)? + .store_u32(32, self.expire_at)? + .store_u32(32, self.seqno)?; + builder.store_bit(true)?; // true means basic actions is stored in reference + + let mut basic_actions = self.basic_actions.clone(); + basic_actions.reverse(); + + builder.store_child(build_out_list(&basic_actions)?)?; + builder.store_bit(false)?; // false means no extended actions + builder.build() + } +} diff --git a/rust/chains/tw_ton/src/message/mod.rs b/rust/chains/tw_ton/src/message/mod.rs index 1f8c298d45c..0996f7b8276 100644 --- a/rust/chains/tw_ton/src/message/mod.rs +++ b/rust/chains/tw_ton/src/message/mod.rs @@ -1,4 +1,5 @@ pub mod external_message; pub mod internal_message; +pub mod out_list; pub mod payload; pub mod signed_message; diff --git a/rust/chains/tw_ton/src/message/out_list/mod.rs b/rust/chains/tw_ton/src/message/out_list/mod.rs new file mode 100644 index 00000000000..85f6df623d8 --- /dev/null +++ b/rust/chains/tw_ton/src/message/out_list/mod.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::message::out_list::out_action::OutAction; +use tw_ton_sdk::cell::cell_builder::CellBuilder; +use tw_ton_sdk::cell::Cell; +use tw_ton_sdk::error::CellResult; + +pub mod out_action; + +pub fn build_out_list(actions: &[OutAction]) -> CellResult { + let cell = actions + .iter() + .fold(CellBuilder::new().build(), |acc, action| { + let mut builder = CellBuilder::new(); + builder.store_child(acc?)?; + builder.store_cell(&action.build()?)?; + builder.build() + }); + + let mut builder = CellBuilder::new(); + builder.store_cell(&cell?)?; + builder.build() +} diff --git a/rust/chains/tw_ton/src/message/out_list/out_action.rs b/rust/chains/tw_ton/src/message/out_list/out_action.rs new file mode 100644 index 00000000000..bf270c307b2 --- /dev/null +++ b/rust/chains/tw_ton/src/message/out_list/out_action.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_ton_sdk::cell::cell_builder::CellBuilder; +use tw_ton_sdk::cell::{Cell, CellArc}; +use tw_ton_sdk::error::CellResult; + +#[derive(Clone)] +pub enum OutActionType { + // Currently, only SendMsg is supported. SetCode is not supported. + SendMsg, +} + +impl OutActionType { + // See https://ton.org/tblkch.pdf 4.4.11. Serialization of output actions + pub fn to_ser_tag(&self) -> u32 { + match self { + OutActionType::SendMsg => 0x0ec3c86d, + } + } +} + +#[derive(Clone)] +pub struct OutAction { + pub typ: OutActionType, + pub mode: u8, + pub data: CellArc, // out msg (SendMsg) or new code (SetCode) +} + +impl OutAction { + pub fn new(typ: OutActionType, mode: u8, out_msg: CellArc) -> Self { + OutAction { + typ, + mode, + data: out_msg, + } + } + + pub fn build(&self) -> CellResult { + match self.typ { + OutActionType::SendMsg => self.build_out_action_send_msg(), + } + } + + fn build_out_action_send_msg(&self) -> CellResult { + let mut builder = CellBuilder::new(); + builder + .store_u32(32, self.typ.to_ser_tag())? + .store_u8(8, self.mode)? + .store_reference(&self.data)?; + builder.build() + } +} diff --git a/rust/chains/tw_ton/src/message/signed_message/mod.rs b/rust/chains/tw_ton/src/message/signed_message/mod.rs new file mode 100644 index 00000000000..cfe3788fe74 --- /dev/null +++ b/rust/chains/tw_ton/src/message/signed_message/mod.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod signed_message_v4; +pub mod signed_message_v5; diff --git a/rust/chains/tw_ton/src/message/signed_message/signed_message_v4.rs b/rust/chains/tw_ton/src/message/signed_message/signed_message_v4.rs new file mode 100644 index 00000000000..4b624b247f0 --- /dev/null +++ b/rust/chains/tw_ton/src/message/signed_message/signed_message_v4.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_hash::H512; +use tw_ton_sdk::cell::cell_builder::CellBuilder; +use tw_ton_sdk::cell::Cell; +use tw_ton_sdk::error::*; + +pub struct SignedMessageV4 { + pub signature: H512, + pub external_message: Cell, +} + +impl SignedMessageV4 { + pub fn build(&self) -> CellResult { + let mut body_builder = CellBuilder::new(); + + // In the case of WALLET_V4_R2, the signature is stored before the external message. + body_builder.store_slice(self.signature.as_slice())?; + body_builder.store_cell(&self.external_message)?; + + body_builder.build() + } +} diff --git a/rust/chains/tw_ton/src/message/signed_message.rs b/rust/chains/tw_ton/src/message/signed_message/signed_message_v5.rs similarity index 77% rename from rust/chains/tw_ton/src/message/signed_message.rs rename to rust/chains/tw_ton/src/message/signed_message/signed_message_v5.rs index 3791d8d20f6..932fafa9894 100644 --- a/rust/chains/tw_ton/src/message/signed_message.rs +++ b/rust/chains/tw_ton/src/message/signed_message/signed_message_v5.rs @@ -7,16 +7,19 @@ use tw_ton_sdk::cell::cell_builder::CellBuilder; use tw_ton_sdk::cell::Cell; use tw_ton_sdk::error::*; -pub struct SignedMessage { +pub struct SignedMessageV5 { pub signature: H512, pub external_message: Cell, } -impl SignedMessage { +impl SignedMessageV5 { pub fn build(&self) -> CellResult { let mut body_builder = CellBuilder::new(); - body_builder.store_slice(self.signature.as_slice())?; + + // In the case of WALLET_V5_R1, the signature is stored after the external message. body_builder.store_cell(&self.external_message)?; + body_builder.store_slice(self.signature.as_slice())?; + body_builder.build() } } 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/chains/tw_ton/src/modules/wallet_provider.rs b/rust/chains/tw_ton/src/modules/wallet_provider.rs index 212e0617126..ed09e7f93df 100644 --- a/rust/chains/tw_ton/src/modules/wallet_provider.rs +++ b/rust/chains/tw_ton/src/modules/wallet_provider.rs @@ -2,8 +2,7 @@ // // Copyright © 2017 Trust Wallet. -use crate::wallet::wallet_v4::WalletV4; -use crate::wallet::TonWallet; +use crate::wallet::{wallet_v4, wallet_v5}; use tw_keypair::ed25519::sha512::PublicKey; use tw_ton_sdk::boc::BagOfCells; use tw_ton_sdk::error::CellResult; @@ -20,10 +19,21 @@ impl WalletProvider { workchain: i32, wallet_id: i32, ) -> CellResult { - let state_init = - TonWallet::with_public_key(workchain, WalletV4::r2()?, public_key, wallet_id)? - .state_init()? - .to_cell()?; + let state_init = wallet_v4::WalletV4R2::with_public_key(workchain, public_key, wallet_id)? + .state_init()? + .to_cell()?; + BagOfCells::from_root(state_init).to_base64(HAS_CRC32) + } + + /// Constructs a TON Wallet V5R1 stateInit encoded as BoC (BagOfCells) for the given `public_key`. + pub fn v5r1_state_init( + public_key: PublicKey, + workchain: i32, + wallet_id: i32, + ) -> CellResult { + let state_init = wallet_v5::WalletV5R1::with_public_key(workchain, public_key, wallet_id)? + .state_init()? + .to_cell()?; BagOfCells::from_root(state_init).to_base64(HAS_CRC32) } } diff --git a/rust/chains/tw_ton/src/resources.rs b/rust/chains/tw_ton/src/resources.rs index 01f4e26a646..04a1ad37c81 100644 --- a/rust/chains/tw_ton/src/resources.rs +++ b/rust/chains/tw_ton/src/resources.rs @@ -6,6 +6,9 @@ use lazy_static::lazy_static; use tw_ton_sdk::boc::BagOfCells; pub const DEFAULT_WALLET_ID: i32 = 0x29a9a317; +/// The wallet id 2147483409 comes from: https://github.com/ton-org/ton/blob/f9842909ac0e7d6f66d055dd18a4c41ec3416c02/src/wallets/v5r1/WalletV5R1WalletId.ts#L21C22-L21C32 +/// The V5R1 wallet id differs between the mainnet and testnet. We support V5R1 only on the mainnet. +pub const WALLET_ID_V5R1_TON_MAINNET: i32 = 2147483409; /// https://docs.ton.org/develop/howto/step-by-step#1-smart-contract-addresses pub const BASE_WORKCHAIN: i32 = 0; pub const MASTER_WORKCHAIN: i32 = -1; @@ -15,4 +18,8 @@ lazy_static! { let code = include_str!("../resources/wallet/wallet_v4r2.code"); BagOfCells::parse_base64(code).expect("Cannot decode wallet_v4r2.code") }; + pub static ref WALLET_V5R1_CODE: BagOfCells = { + let code = include_str!("../resources/wallet/wallet_v5r1.code"); + BagOfCells::parse_base64(code).expect("Cannot decode wallet_v5r1.code") + }; } diff --git a/rust/chains/tw_ton/src/signer.rs b/rust/chains/tw_ton/src/signer.rs index 3a1417b7088..44306434b73 100644 --- a/rust/chains/tw_ton/src/signer.rs +++ b/rust/chains/tw_ton/src/signer.rs @@ -2,17 +2,17 @@ // // Copyright © 2017 Trust Wallet. +use crate::compiler::HAS_CRC32; use crate::signing_request::builder::SigningRequestBuilder; use crate::signing_request::cell_creator::ExternalMessageCreator; use tw_coin_entry::coin_context::CoinContext; use tw_coin_entry::error::prelude::*; use tw_coin_entry::signing_output_error; +use tw_misc::traits::ToBytesVec; use tw_proto::TheOpenNetwork::Proto; use tw_ton_sdk::boc::BagOfCells; use tw_ton_sdk::error::cell_to_signing_error; -const HAS_CRC32: bool = true; - pub struct TheOpenNetworkSigner; impl TheOpenNetworkSigner { diff --git a/rust/chains/tw_ton/src/signing_request/builder.rs b/rust/chains/tw_ton/src/signing_request/builder.rs index 0b180435bb6..5324546deb4 100644 --- a/rust/chains/tw_ton/src/signing_request/builder.rs +++ b/rust/chains/tw_ton/src/signing_request/builder.rs @@ -6,8 +6,7 @@ use crate::address::TonAddress; use crate::signing_request::{ JettonTransferRequest, SigningRequest, TransferCustomRequest, TransferPayload, TransferRequest, }; -use crate::wallet::wallet_v4::WalletV4; -use crate::wallet::TonWallet; +use crate::wallet::{wallet_v4, wallet_v5, VersionedTonWallet}; use std::str::FromStr; use tw_coin_entry::error::prelude::*; use tw_keypair::ed25519::sha512::{KeyPair, PublicKey}; @@ -47,27 +46,46 @@ impl SigningRequestBuilder { }) } - /// Currently, V4R2 wallet supported only. - fn wallet(input: &Proto::SigningInput) -> SigningResult> { + /// Currently, V4R2 and V5R1 wallets supported. + fn wallet(input: &Proto::SigningInput) -> SigningResult { if !input.private_key.is_empty() { let key_pair = KeyPair::try_from(input.private_key.as_ref()) .into_tw() .context("Invalid private key")?; - return TonWallet::std_with_key_pair(&key_pair).map_err(cell_to_signing_error); + + return match input.wallet_version { + Proto::WalletVersion::WALLET_V4_R2 => Ok(VersionedTonWallet::V4R2( + wallet_v4::WalletV4R2::std_with_key_pair(&key_pair) + .map_err(cell_to_signing_error)?, + )), + Proto::WalletVersion::WALLET_V5_R1 => Ok(VersionedTonWallet::V5R1( + wallet_v5::WalletV5R1::std_with_key_pair(&key_pair) + .map_err(cell_to_signing_error)?, + )), + _ => SigningError::err(SigningErrorType::Error_not_supported) + .context("Wallet version not supported"), + }; } let public_key = PublicKey::try_from(input.public_key.as_ref()) .into_tw() .context("Expected either 'private_key' or 'public_key' to be set")?; - TonWallet::std_with_public_key(public_key).map_err(cell_to_signing_error) - } - fn transfer_request(input: &Proto::Transfer) -> SigningResult { - if input.wallet_version != Proto::WalletVersion::WALLET_V4_R2 { - return SigningError::err(SigningErrorType::Error_not_supported) - .context("'WALLET_V4_R2' version is supported only"); + match input.wallet_version { + Proto::WalletVersion::WALLET_V4_R2 => Ok(VersionedTonWallet::V4R2( + wallet_v4::WalletV4R2::std_with_public_key(public_key) + .map_err(cell_to_signing_error)?, + )), + Proto::WalletVersion::WALLET_V5_R1 => Ok(VersionedTonWallet::V5R1( + wallet_v5::WalletV5R1::std_with_public_key(public_key) + .map_err(cell_to_signing_error)?, + )), + _ => SigningError::err(SigningErrorType::Error_not_supported) + .context("Wallet version not supported"), } + } + fn transfer_request(input: &Proto::Transfer) -> SigningResult { let dest = TonAddress::from_str(input.dest.as_ref()) .into_tw() .context("Invalid 'dest' address")? diff --git a/rust/chains/tw_ton/src/signing_request/mod.rs b/rust/chains/tw_ton/src/signing_request/mod.rs index ec3b3109cfb..d0d0e3b05bf 100644 --- a/rust/chains/tw_ton/src/signing_request/mod.rs +++ b/rust/chains/tw_ton/src/signing_request/mod.rs @@ -3,8 +3,7 @@ // Copyright © 2017 Trust Wallet. use crate::address::TonAddress; -use crate::wallet::wallet_v4::WalletV4; -use crate::wallet::TonWallet; +use crate::wallet::VersionedTonWallet; use tw_number::U256; pub mod builder; @@ -57,7 +56,7 @@ pub struct TransferCustomRequest { pub struct SigningRequest { /// Wallet initialized with the user's key-pair or public key. - pub wallet: TonWallet, + pub wallet: VersionedTonWallet, pub messages: Vec, /// External message counter. /// https://ton.org/docs/develop/smart-contracts/guidelines/external-messages diff --git a/rust/chains/tw_ton/src/transaction.rs b/rust/chains/tw_ton/src/transaction.rs index 37dd1f6fdce..1fdf8e8d925 100644 --- a/rust/chains/tw_ton/src/transaction.rs +++ b/rust/chains/tw_ton/src/transaction.rs @@ -3,7 +3,6 @@ // Copyright © 2017 Trust Wallet. use crate::address::TonAddress; -use crate::message::signed_message::SignedMessage; use tw_number::U256; use tw_ton_sdk::cell::cell_builder::CellBuilder; use tw_ton_sdk::cell::Cell; @@ -19,7 +18,7 @@ pub struct SignedTransaction { pub import_fee: U256, /// Created via `StateInit`. pub state_init: Option, - pub signed_body: SignedMessage, + pub signed_body: Cell, } impl SignedTransaction { @@ -40,7 +39,7 @@ impl SignedTransaction { } wrap_builder.store_bit(true)?; // signed_body is always defined - wrap_builder.store_child(self.signed_body.build()?)?; // Signed body + wrap_builder.store_child(self.signed_body.clone())?; // Signed body wrap_builder.build() } diff --git a/rust/chains/tw_ton/src/wallet/mod.rs b/rust/chains/tw_ton/src/wallet/mod.rs index e6257528e76..8b4a01311b7 100644 --- a/rust/chains/tw_ton/src/wallet/mod.rs +++ b/rust/chains/tw_ton/src/wallet/mod.rs @@ -3,97 +3,42 @@ // Copyright © 2017 Trust Wallet. use crate::address::TonAddress; -use crate::message::external_message::ExternalMessage; use crate::message::internal_message::InternalMessage; -use crate::message::signed_message::SignedMessage; -use crate::resources::{BASE_WORKCHAIN, DEFAULT_WALLET_ID}; +use crate::message::signed_message::signed_message_v4::SignedMessageV4; +use crate::message::signed_message::signed_message_v5::SignedMessageV5; use crate::transaction::SignedTransaction; use tw_coin_entry::error::prelude::*; -use tw_hash::H256; -use tw_keypair::ed25519::sha512::{KeyPair, PrivateKey, PublicKey}; -use tw_keypair::traits::{KeyPairTrait, SigningKeyTrait}; +use tw_keypair::ed25519::Signature; +use tw_keypair::traits::SigningKeyTrait; use tw_number::U256; -use tw_ton_sdk::cell::{Cell, CellArc}; +use tw_ton_sdk::cell::Cell; use tw_ton_sdk::error::{cell_to_signing_error, CellResult}; use tw_ton_sdk::message::state_init::StateInit; pub mod wallet_v4; - -pub trait WalletVersion { - /// Returns data that is (will be) stored in the wallet smart contract - /// when it is first deployed to the blockchain. - fn initial_data(&self, wallet_id: i32, public_key: H256) -> CellResult; - - /// Returns this wallet specific version contract code as a Cell. - fn code(&self) -> CellResult; - - /// Whether the wallet version supports OP codes. - /// For example, plugin OP codes: https://github.com/ton-blockchain/wallet-contract/blob/main/func/wallet-v4-code.fc#L102 - fn has_op(&self) -> bool; -} - -pub struct TonWallet { - public_key: PublicKey, - private_key: Option, - version: Version, - /// TON address derived from the [`TonWallet::public_key`]. - address: TonAddress, - wallet_id: i32, -} - -impl TonWallet { - /// Creates a standard TON wallet from the given public key. - /// Please note when created with public key only, wallet cannot be used to sign messages. - pub fn std_with_public_key(public_key: PublicKey) -> CellResult { - Self::with_public_key( - BASE_WORKCHAIN, - wallet_v4::WalletV4::r2()?, - public_key, - DEFAULT_WALLET_ID, - ) - } - - /// Creates a standard TON wallet from the given key-pair. - pub fn std_with_key_pair(key_pair: &KeyPair) -> CellResult { - Self::with_key_pair( - BASE_WORKCHAIN, - wallet_v4::WalletV4::r2()?, - key_pair, - DEFAULT_WALLET_ID, - ) - } +pub mod wallet_v5; + +/// Notes: Rust specialization for generic code is not completed. See https://github.com/rust-lang/rust/issues/31844 +/// Currently, we use a workaround to implement the versioned TonWallet struct. +/// After the Rust specialization feature is finished, maybe we can remove this workaround. +pub enum VersionedTonWallet { + V4R2(wallet_v4::WalletV4R2), + V5R1(wallet_v5::WalletV5R1), } -impl TonWallet { - /// Creates a TON wallet from the given public key. - /// Please note when created with public key only, wallet cannot be used to sign messages. - pub fn with_public_key( - workchain: i32, - version: Version, - public_key: PublicKey, - wallet_id: i32, - ) -> CellResult { - Self::new(workchain, version, public_key, None, wallet_id) - } - - /// Creates a TON wallet from the given key-pair. - pub fn with_key_pair( - workchain: i32, - version: Version, - key_pair: &KeyPair, - wallet_id: i32, - ) -> CellResult { - let public = key_pair.public().clone(); - let private = key_pair.private().clone(); - Self::new(workchain, version, public, Some(private), wallet_id) - } - +impl VersionedTonWallet { pub fn address(&self) -> &TonAddress { - &self.address + match self { + Self::V4R2(wallet_v4r2) => &wallet_v4r2.address, + Self::V5R1(wallet_v5r1) => &wallet_v5r1.address, + } } pub fn state_init(&self) -> CellResult { - Self::state_init_impl(&self.version, &self.public_key, self.wallet_id) + match self { + Self::V4R2(wallet_v4r2) => wallet_v4r2.state_init(), + Self::V5R1(wallet_v5r1) => wallet_v5r1.state_init(), + } } pub fn create_external_body( @@ -102,36 +47,64 @@ impl TonWallet { seqno: u32, internal_messages: Vec, ) -> CellResult { - ExternalMessage { - wallet_id: self.wallet_id, - expire_at, - seqno, - has_op: self.version.has_op(), - internal_messages, + match self { + Self::V4R2(wallet_v4r2) => { + wallet_v4r2.create_external_body(expire_at, seqno, internal_messages) + }, + Self::V5R1(wallet_v5r1) => { + wallet_v5r1.create_external_body(expire_at, seqno, internal_messages) + }, } - .build() } - pub fn sign_external_message(&self, external_message: Cell) -> SigningResult { + pub fn sign_external_message(&self, external_message: Cell) -> SigningResult { let message_hash = external_message.cell_hash(); - let sig = self - .private_key - .as_ref() - .or_tw_err(SigningErrorType::Error_internal) - .context( - "'TonWallet' should be initialized with a key-pair to be able to sign a message", - )? - .sign(message_hash.to_vec())?; - Ok(SignedMessage { - signature: sig.to_bytes(), - external_message, - }) + let sig = match self { + Self::V4R2(wallet_v4r2) => wallet_v4r2.private_key.as_ref(), + Self::V5R1(wallet_v5r1) => wallet_v5r1.private_key.as_ref(), + } + .or_tw_err(SigningErrorType::Error_internal) + .context("'TonWallet' should be initialized with a key-pair to be able to sign a message")? + .sign(message_hash.to_vec())?; + + self.compile_signed_external_message(external_message, sig) + } + + pub fn compile_signed_external_message( + &self, + external_message: Cell, + sig: Signature, + ) -> SigningResult { + match self { + Self::V4R2(_) => Ok(SignedMessageV4 { + signature: sig.to_bytes(), + external_message, + } + .build() + .map_err(cell_to_signing_error)?), + + Self::V5R1(_) => Ok(SignedMessageV5 { + signature: sig.to_bytes(), + external_message, + } + .build() + .map_err(cell_to_signing_error)?), + } } pub fn sign_transaction( &self, external_message: Cell, state_init: bool, + ) -> SigningResult { + let signed_external_message = self.sign_external_message(external_message.clone())?; + self.compile_transaction(signed_external_message, state_init) + } + + pub fn compile_transaction( + &self, + signed_external_message: Cell, + state_init: bool, ) -> SigningResult { let state_init = if state_init { let state_init = self.state_init().map_err(cell_to_signing_error)?; @@ -140,49 +113,13 @@ impl TonWallet { None }; - let signed_body = self.sign_external_message(external_message)?; Ok(SignedTransaction { src_address: TonAddress::null(), // The wallet contract address. - dest_address: self.address.clone(), + dest_address: self.address().clone(), import_fee: U256::zero(), state_init, - signed_body, - }) - } - - /// Private function to create the TonWallet with the given public and optional private keys. - /// Do not make it public as the function caller can provide unrelated keys. - fn new( - workchain: i32, - version: Version, - public_key: PublicKey, - private_key: Option, - wallet_id: i32, - ) -> CellResult { - let state_init_hash = - Self::state_init_impl(&version, &public_key, wallet_id)?.create_account_id()?; - let address = TonAddress::new(workchain, state_init_hash); - Ok(TonWallet { - public_key, - private_key, - version, - address, - wallet_id, + signed_body: signed_external_message, }) } - - fn state_init_impl( - version: &Version, - public_key: &PublicKey, - wallet_id: i32, - ) -> CellResult { - let public_key_bytes = public_key.to_bytes(); - - let initial_data = version - .initial_data(wallet_id, public_key_bytes)? - .into_arc(); - let code = version.code()?; - Ok(StateInit::default().set_code(code).set_data(initial_data)) - } } diff --git a/rust/chains/tw_ton/src/wallet/wallet_v4.rs b/rust/chains/tw_ton/src/wallet/wallet_v4.rs index 9beae8846ec..2ecb279a1e1 100644 --- a/rust/chains/tw_ton/src/wallet/wallet_v4.rs +++ b/rust/chains/tw_ton/src/wallet/wallet_v4.rs @@ -2,34 +2,82 @@ // // Copyright © 2017 Trust Wallet. -use crate::resources::WALLET_V4R2_CODE; -use crate::wallet::WalletVersion; +use crate::address::TonAddress; +use crate::message::external_message::wallet_v4::ExternalMessageWalletV4; +use crate::message::internal_message::InternalMessage; +use crate::resources::{BASE_WORKCHAIN, DEFAULT_WALLET_ID, WALLET_V4R2_CODE}; use std::sync::Arc; -use tw_hash::H256; +use tw_keypair::ed25519::sha512::{KeyPair, PrivateKey, PublicKey}; +use tw_keypair::traits::KeyPairTrait; use tw_ton_sdk::cell::cell_builder::CellBuilder; -use tw_ton_sdk::cell::{Cell, CellArc}; +use tw_ton_sdk::cell::Cell; use tw_ton_sdk::error::CellResult; +use tw_ton_sdk::message::state_init::StateInit; -/// We support WalletV4R2 version only. -/// Consider adding a new `WalletVR` if needed. -enum Revision { - R2, +pub struct WalletV4R2 { + pub public_key: PublicKey, + pub(crate) private_key: Option, + /// TON address derived from the [`TonWallet::public_key`]. + pub address: TonAddress, + pub wallet_id: i32, } -pub struct WalletV4 { - revision: Revision, -} +impl WalletV4R2 { + /// Creates a standard TON wallet from the given public key. + /// Please note when created with public key only, wallet cannot be used to sign messages. + pub fn std_with_public_key(public_key: PublicKey) -> CellResult { + let wallet_id = DEFAULT_WALLET_ID; + Self::with_public_key(BASE_WORKCHAIN, public_key, wallet_id) + } + + /// Creates a standard TON wallet from the given key-pair. + pub fn std_with_key_pair(key_pair: &KeyPair) -> CellResult { + let wallet_id = DEFAULT_WALLET_ID; + Self::with_key_pair(BASE_WORKCHAIN, key_pair, wallet_id) + } + + /// Creates a TON wallet from the given public key. + /// Please note when created with public key only, wallet cannot be used to sign messages. + pub(crate) fn with_public_key( + workchain: i32, + public_key: PublicKey, + wallet_id: i32, + ) -> CellResult { + Self::new(workchain, public_key, None, wallet_id) + } + + /// Creates a TON wallet from the given key-pair. + fn with_key_pair(workchain: i32, key_pair: &KeyPair, wallet_id: i32) -> CellResult { + let public = key_pair.public().clone(); + let private = key_pair.private().clone(); + Self::new(workchain, public, Some(private), wallet_id) + } -impl WalletV4 { - pub fn r2() -> CellResult { - Ok(WalletV4 { - revision: Revision::R2, + /// Private function to create the VersionedTonWallet with the given public and optional private keys. + /// Do not make it public as the function caller can provide unrelated keys. + fn new( + workchain: i32, + public_key: PublicKey, + private_key: Option, + wallet_id: i32, + ) -> CellResult { + let state_init_hash = Self::state_init_impl(&public_key, wallet_id)?.create_account_id()?; + let address = TonAddress::new(workchain, state_init_hash); + + Ok(Self { + public_key, + private_key, + address, + wallet_id, }) } -} -impl WalletVersion for WalletV4 { - fn initial_data(&self, wallet_id: i32, public_key: H256) -> CellResult { + /// Return the stateInit for the wallet. + pub fn state_init(&self) -> CellResult { + Self::state_init_impl(&self.public_key, self.wallet_id) + } + + fn state_init_impl(public_key: &PublicKey, wallet_id: i32) -> CellResult { let seqno = 0; let mut builder = CellBuilder::new(); @@ -39,16 +87,25 @@ impl WalletVersion for WalletV4 { .store_slice(public_key.as_slice())? // empty plugin dict .store_bit(false)?; - builder.build() - } - fn code(&self) -> CellResult { - match self.revision { - Revision::R2 => WALLET_V4R2_CODE.single_root().map(Arc::clone), - } + let initial_data = builder.build()?.into_arc(); + let code = WALLET_V4R2_CODE.single_root().map(Arc::clone)?; + + Ok(StateInit::default().set_code(code).set_data(initial_data)) } - fn has_op(&self) -> bool { - matches!(self.revision, Revision::R2) + pub(crate) fn create_external_body( + &self, + expire_at: u32, + seqno: u32, + internal_messages: Vec, + ) -> CellResult { + ExternalMessageWalletV4 { + wallet_id: self.wallet_id, + expire_at, + seqno, + internal_messages, + } + .build() } } diff --git a/rust/chains/tw_ton/src/wallet/wallet_v5.rs b/rust/chains/tw_ton/src/wallet/wallet_v5.rs new file mode 100644 index 00000000000..98a45b6a94e --- /dev/null +++ b/rust/chains/tw_ton/src/wallet/wallet_v5.rs @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::TonAddress; +use crate::message::external_message::wallet_v5::{ExternalMessageWalletV5, V5R1OpCode}; +use crate::message::internal_message::InternalMessage; +use crate::message::out_list::out_action::{OutAction, OutActionType}; +use crate::resources::{BASE_WORKCHAIN, WALLET_ID_V5R1_TON_MAINNET, WALLET_V5R1_CODE}; +use std::sync::Arc; +use tw_keypair::ed25519::sha512::{KeyPair, PrivateKey, PublicKey}; +use tw_keypair::traits::KeyPairTrait; +use tw_ton_sdk::cell::cell_builder::CellBuilder; +use tw_ton_sdk::cell::Cell; +use tw_ton_sdk::error::CellResult; +use tw_ton_sdk::message::state_init::StateInit; + +pub struct WalletV5R1 { + pub public_key: PublicKey, + pub(crate) private_key: Option, + /// TON address derived from the [`TonWallet::public_key`]. + pub address: TonAddress, + pub wallet_id: i32, +} + +impl WalletV5R1 { + /// Creates a standard TON wallet from the given public key. + /// Please note when created with public key only, wallet cannot be used to sign messages. + pub fn std_with_public_key(public_key: PublicKey) -> CellResult { + let wallet_id = WALLET_ID_V5R1_TON_MAINNET; + Self::with_public_key(BASE_WORKCHAIN, public_key, wallet_id) + } + + /// Creates a standard TON wallet from the given key-pair. + pub fn std_with_key_pair(key_pair: &KeyPair) -> CellResult { + let wallet_id = WALLET_ID_V5R1_TON_MAINNET; + Self::with_key_pair(BASE_WORKCHAIN, key_pair, wallet_id) + } + + /// Creates a TON wallet from the given public key. + /// Please note when created with public key only, wallet cannot be used to sign messages. + pub(crate) fn with_public_key( + workchain: i32, + public_key: PublicKey, + wallet_id: i32, + ) -> CellResult { + Self::new(workchain, public_key, None, wallet_id) + } + + /// Creates a TON wallet from the given key-pair. + fn with_key_pair(workchain: i32, key_pair: &KeyPair, wallet_id: i32) -> CellResult { + let public = key_pair.public().clone(); + let private = key_pair.private().clone(); + Self::new(workchain, public, Some(private), wallet_id) + } + + /// Private function to create the VersionedTonWallet with the given public and optional private keys. + /// Do not make it public as the function caller can provide unrelated keys. + fn new( + workchain: i32, + public_key: PublicKey, + private_key: Option, + wallet_id: i32, + ) -> CellResult { + let state_init_hash = Self::state_init_impl(&public_key, wallet_id)?.create_account_id()?; + let address = TonAddress::new(workchain, state_init_hash); + + Ok(Self { + public_key, + private_key, + address, + wallet_id, + }) + } + + /// Return the stateInit for the wallet. + pub(crate) fn state_init(&self) -> CellResult { + Self::state_init_impl(&self.public_key, self.wallet_id) + } + + fn state_init_impl(public_key: &PublicKey, wallet_id: i32) -> CellResult { + let seqno = 0; + + let mut builder = CellBuilder::new(); + builder + .store_bit(true)? // signature auth allowed + .store_u32(32, seqno)? + .store_i32(32, wallet_id)? + .store_slice(public_key.as_slice())? + // empty plugin dict + .store_bit(false)?; + + let initial_data = builder.build()?.into_arc(); + let code = WALLET_V5R1_CODE.single_root().map(Arc::clone)?; + + Ok(StateInit::default().set_code(code).set_data(initial_data)) + } + + pub(crate) fn create_external_body( + &self, + expire_at: u32, + seqno: u32, + internal_messages: Vec, + ) -> CellResult { + // Convert internal_messages to basic_actions + let basic_actions: Vec = internal_messages + .into_iter() + .map(|msg| OutAction { + typ: OutActionType::SendMsg, + mode: msg.mode, + data: msg.message, + }) + .collect(); + + ExternalMessageWalletV5 { + opcode: V5R1OpCode::AuthSignedExternal, + wallet_id: self.wallet_id, + expire_at, + seqno, + basic_actions, + } + .build() + } +} diff --git a/rust/chains/tw_ton/tests/address.rs b/rust/chains/tw_ton/tests/address.rs new file mode 100644 index 00000000000..fb99cd0c008 --- /dev/null +++ b/rust/chains/tw_ton/tests/address.rs @@ -0,0 +1,39 @@ +use tw_keypair::ed25519::sha512::KeyPair; +use tw_keypair::traits::KeyPairTrait; +use tw_ton::wallet::wallet_v5; + +/// Tests for TON V5R1 address. +/// +/// Note: These tests should move to rust/tw_any_coin/tests/chains/ton/ton_address.rs after we define +/// a new Derivation for the TON V5R1 address. +#[test] +fn test_ton_v5r1_address_derive() { + let test_cases = [( + "3570e35f54cfb843f2cfaf2b8cae7ceeb7b32225d7dbbd86f611056d74d9073e", // private key + "UQAU3o5-Sp1MYRpw3U7b_wmARxqI49LxiFhEoVCxpUKjTYXk", // V5R1 address on the mainnet + )]; + + for test_case in test_cases.iter() { + let private_key_hex = test_case.0; + let expected_address = test_case.1; + + // Decode the private key from hex + let private_key_bytes = + tw_encoding::hex::decode(private_key_hex).expect("Invalid hex string"); + + // Create a KeyPair from the private key bytes + let key_pair = + KeyPair::try_from(private_key_bytes.as_slice()).expect("Failed to create key pair"); + + // Extract the public key from the KeyPair + let public_key = key_pair.public().clone(); + + // Create the VersionedTonWallet using the public key + let wallet = wallet_v5::WalletV5R1::std_with_public_key(public_key) + .expect("Failed to create wallet"); + + let actual_address = wallet.address.to_string(); + + assert_eq!(actual_address, expected_address); + } +} diff --git a/rust/frameworks/tw_ton_sdk/src/boc/boc_to_raw_boc.rs b/rust/frameworks/tw_ton_sdk/src/boc/boc_to_raw_boc.rs index efcbddd4001..c3f4e3d60e0 100644 --- a/rust/frameworks/tw_ton_sdk/src/boc/boc_to_raw_boc.rs +++ b/rust/frameworks/tw_ton_sdk/src/boc/boc_to_raw_boc.rs @@ -9,12 +9,13 @@ use crate::boc::BagOfCells; use crate::cell::{Cell, CellArc}; use crate::error::{CellErrorType, CellResult}; use std::cell::RefCell; -use std::collections::HashMap; +use std::collections::BTreeMap; use std::sync::Arc; use tw_coin_entry::error::prelude::{OrTWError, ResultContext}; use tw_hash::H256; type IndexedCellRef = RefCell; +type CellsByHash = BTreeMap; #[derive(Debug, Clone)] struct IndexedCell { @@ -47,10 +48,14 @@ pub(crate) fn convert_to_raw_boc(boc: &BagOfCells) -> CellResult }) } -fn build_and_verify_index(roots: &[CellArc]) -> HashMap { +fn build_and_verify_index(roots: &[CellArc]) -> CellsByHash { let mut current_cells: Vec<_> = roots.iter().map(Arc::clone).collect(); let mut new_hash_index = 0; - let mut cells_by_hash = HashMap::new(); + + // The Bag of Cells serialization process is not deterministic, + // and these uncertainties make it difficult to write test cases. + // Therefore, we use a BTreeMap instead of a HashMap to remove the uncertainty. + let mut cells_by_hash = BTreeMap::new(); // Process cells to build the initial index. while !current_cells.is_empty() { @@ -99,10 +104,7 @@ fn build_and_verify_index(roots: &[CellArc]) -> HashMap { cells_by_hash } -fn root_indices( - roots: &[CellArc], - cells_dict: &HashMap, -) -> CellResult> { +fn root_indices(roots: &[CellArc], cells_dict: &CellsByHash) -> CellResult> { roots .iter() .map(|root_cell| root_cell.cell_hash()) @@ -122,17 +124,14 @@ fn root_indices( fn raw_cells_from_cells( cells: impl Iterator, - cells_by_hash: &HashMap, + cells_by_hash: &CellsByHash, ) -> CellResult> { cells .map(|cell| raw_cell_from_cell(&cell, cells_by_hash)) .collect() } -fn raw_cell_from_cell( - cell: &Cell, - cells_by_hash: &HashMap, -) -> CellResult { +fn raw_cell_from_cell(cell: &Cell, cells_by_hash: &CellsByHash) -> CellResult { raw_cell_reference_indices(cell, cells_by_hash).map(|reference_indices| { RawCell::new( cell.data().to_vec(), @@ -144,10 +143,7 @@ fn raw_cell_from_cell( }) } -fn raw_cell_reference_indices( - cell: &Cell, - cells_by_hash: &HashMap, -) -> CellResult> { +fn raw_cell_reference_indices(cell: &Cell, cells_by_hash: &CellsByHash) -> CellResult> { cell.references() .iter() .map(|cell| { 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 1a5d122cbdf..ede77ad1aef 100644 --- a/rust/tw_any_coin/tests/chains/ton/mod.rs +++ b/rust/tw_any_coin/tests/chains/ton/mod.rs @@ -6,3 +6,5 @@ mod cell_example; 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_compile.rs b/rust/tw_any_coin/tests/chains/ton/ton_compile.rs index 43fa1e9daf6..b13d6fc472e 100644 --- a/rust/tw_any_coin/tests/chains/ton/ton_compile.rs +++ b/rust/tw_any_coin/tests/chains/ton/ton_compile.rs @@ -2,35 +2,104 @@ // // Copyright © 2017 Trust Wallet. +use crate::chains::ton::ton_sign::assert_eq_boc; 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::{DecodeHex, ToHex}; use tw_proto::Common::Proto::SigningError; use tw_proto::TheOpenNetwork::Proto; use tw_proto::TxCompiler::Proto as CompilerProto; #[test] -fn test_ton_compile_not_supported() { - let input = Proto::SigningInput::default(); +fn test_ton_compile_wallet_v4r2_transfer_and_deploy() { + let public_key = "f42c77f931bea20ec5d0150731276bbb2e2860947661245b2319ef8133ee8d41"; + + let transfer = Proto::Transfer { + dest: "EQDYW_1eScJVxtitoBRksvoV9cCYo4uKGWLVNIHB1JqRR3n0".into(), + amount: 10, + mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 + | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, + bounceable: true, + ..Proto::Transfer::default() + }; + + let input = Proto::SigningInput { + public_key: public_key.decode_hex().unwrap().into(), + messages: vec![transfer], + expire_at: 1671135440, + wallet_version: Proto::WalletVersion::WALLET_V4_R2, + ..Proto::SigningInput::default() + }; // Step 2: Obtain preimage hash let mut pre_imager = PreImageHelper::::default(); let preimage_output = pre_imager.pre_image_hashes(CoinType::TON, &input); - assert_eq!(preimage_output.error, SigningError::Error_not_supported); + assert_eq!(preimage_output.error, SigningError::OK); + assert_eq!( + preimage_output.data.to_hex(), + "9da0ab29120fd96b48b2bb76843e77c0a1770b47c2f78da23299e2c32f50192e" + ); // Step 3: Compile transaction info - let signature_bytes = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".decode_hex().unwrap(); - let public_key = "0000000000000000000000000000000000000000000000000000000000000000" - .decode_hex() - .unwrap(); + + // Simulate signature, normally obtained from signature server. + let signature_bytes = "e7c6f18186845784fa20c4d63c4261e58a1256c60e6de8aa6da9c008665564b35afe67eaf56af0963427aa78b0bfa1a895378983c7f135f99862df71b6e6b708".decode_hex().unwrap(); let mut compiler = CompilerHelper::::default(); - let output = compiler.compile( - CoinType::TON, - &input, - vec![signature_bytes], - vec![public_key], + let output = compiler.compile(CoinType::TON, &input, vec![signature_bytes], vec![]); + + assert_eq!(output.error, SigningError::OK, "{}", output.error_message); + // Successfully broadcasted: https://tonviewer.com/transaction/6ZzWOFKZt_m3kZjbwfbATwLaVwmUOdDp0xjhuY7PO3k= + assert_eq_boc(&output.encoded, "te6ccgICABoAAQAAA8sAAAJFiADN98eLgHfrkE8l8gmT8X5REpTVR6QnqDhArTbKlVvbZh4ABAABAZznxvGBhoRXhPogxNY8QmHlihJWxg5t6KptqcAIZlVks1r+Z+r1avCWNCeqeLC/oaiVN4mDx/E1+Zhi33G25rcIKamjF/////8AAAAAAAMAAgFiYgBsLf6vJOEq42xW0AoyWX0K+uBMUcXFDLFqmkDg6k1Io4hQAAAAAAAAAAAAAAAAAQADAAACATQABgAFAFEAAAAAKamjF/Qsd/kxvqIOxdAVBzEna7suKGCUdmEkWyMZ74Ez7o1BQAEU/wD0pBP0vPLICwAHAgEgAA0ACAT48oMI1xgg0x/TH9MfAvgju/Jk7UTQ0x/TH9P/9ATRUUO68qFRUbryogX5AVQQZPkQ8qP4ACSkyMsfUkDLH1Iwy/9SEPQAye1U+A8B0wchwACfbFGTINdKltMH1AL7AOgw4CHAAeMAIcAC4wABwAORMOMNA6TIyx8Syx/L/wAMAAsACgAJAAr0AMntVABsgQEI1xj6ANM/MFIkgQEI9Fnyp4IQZHN0cnB0gBjIywXLAlAFzxZQA/oCE8tqyx8Syz/Jc/sAAHCBAQjXGPoA0z/IVCBHgQEI9FHyp4IQbm90ZXB0gBjIywXLAlAGzxZQBPoCFMtqEssfyz/Jc/sAAgBu0gf6ANTUIvkABcjKBxXL/8nQd3SAGMjLBcsCIs8WUAX6AhTLaxLMzMlz+wDIQBSBAQj0UfKnAgIBSAAXAA4CASAAEAAPAFm9JCtvaiaECAoGuQ+gIYRw1AgIR6STfSmRDOaQPp/5g3gSgBt4EBSJhxWfMYQCASAAEgARABG4yX7UTQ1wsfgCAVgAFgATAgEgABUAFAAZrx32omhAEGuQ64WPwAAZrc52omhAIGuQ64X/wAA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYALm0AHQ0wMhcbCSXwTgItdJwSCSXwTgAtMfIYIQcGx1Z70ighBkc3RyvbCSXwXgA/pAMCD6RAHIygfL/8nQ7UTQgQFA1yH0BDBcgQEI9ApvoTGzkl8H4AXTP8glghBwbHVnupI4MOMNA4IQZHN0crqSXwbjDQAZABgAilAEgQEI9Fkw7UTQgQFA1yDIAc8W9ADJ7VQBcrCOI4IQZHN0coMesXCAGFAFywVQA88WI/oCE8tqyx/LP8mAQPsAkl8D4gB4AfoA9AQw+CdvIjBQCqEhvvLgUIIQcGx1Z4MesXCAGFAEywUmzxZY+gIZ9ADLaRfLH1Jgyz8gyYBA+wAG"); + assert_eq!( + output.hash.to_hex(), + "b3d9462c13a8c67e19b62002447839c386de51415ace3ff6473b1e6294299819" ); +} - assert_eq!(output.error, SigningError::Error_not_supported); +#[test] +fn test_ton_compile_wallet_v5r1_transfer_and_deploy() { + let public_key = "55b837ceefc94d1865b763149c4751b43e297cea65dabfe20febed13d56072e4"; + + let transfer = Proto::Transfer { + dest: "EQBe6DtCpJZe8M4t-crMXe93JlEYgSl30S5OUuMSLOfeQfBu".into(), + amount: 10, + mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 + | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, + bounceable: true, + ..Proto::Transfer::default() + }; + + let input = Proto::SigningInput { + public_key: public_key.decode_hex().unwrap().into(), + messages: vec![transfer], + expire_at: 0xffffffff, + wallet_version: Proto::WalletVersion::WALLET_V5_R1, + ..Proto::SigningInput::default() + }; + + // Step 2: Obtain preimage hash + let mut pre_imager = PreImageHelper::::default(); + let preimage_output = pre_imager.pre_image_hashes(CoinType::TON, &input); + + assert_eq!(preimage_output.error, SigningError::OK); + assert_eq!( + preimage_output.data.to_hex(), + "d3b8dbedb049074d90bcae341484e0385789e19f69cd86d570939b94043739a7" + ); + + // Step 3: Compile transaction info + + // Simulate signature, normally obtained from signature server. + let signature_bytes = "00239fa98ecbde5dda29ce7c78dc6e25369e1ff7e0070d9a1b472833e8630e98d684929b60a9ac003f2fd39e07612b8eeaf3776d09d5e54d7d061b067ded4c0e".decode_hex().unwrap(); + let mut compiler = CompilerHelper::::default(); + let output = compiler.compile(CoinType::TON, &input, vec![signature_bytes], vec![]); + + assert_eq!(output.error, SigningError::OK, "{}", output.error_message); + // Successfully broadcasted: https://tonviewer.com/transaction/6ZzWOFKZt_m3kZjbwfbATwLaVwmUOdDp0xjhuY7PO3k= + assert_eq_boc(&output.encoded, "te6cckECGwEAA2sAAkWIACm9HPyVOpjCNOG6nbf+EwCONRHHpeMQsIlCoWNKhUaaHgECAgE0AwQBoXNpZ25///8R/////wAAAACACOfqY7L3l3aKc58eNxuJTaeH/fgBw2aG0coM+hjDpjWhJKbYKmsAD8v054HYSuO6vN3bQnV5U19BhsGfe1MDoAUBFP8A9KQT9LzyyAsGAFGAAAAAP///iKrcG+d35KaMMtuxik4jqNofFL51Mu1f8Qf19onqsDlyIAIKDsPIbQMWBwIBIAgJAWJiAC90HaFSSy94Zxb85WYu97uTKIxAlLvolycpcYkWc+8giFAAAAAAAAAAAAAAAAABFgIBSAoLAQLyDALc0CDXScEgkVuPYyDXCx8gghBleHRuvSGCEHNpbnS9sJJfA+CCEGV4dG66jrSAINchAdB01yH6QDD6RPgo+kQwWL2RW+DtRNCBAUHXIfQFgwf0Dm+hMZEw4YBA1yFwf9s84DEg10mBAoC5kTDgcOIXDQIBIA4PAR4g1wsfghBzaWduuvLgin8NAeaO8O2i7fshgwjXIgKDCNcjIIAg1yHTH9Mf0x/tRNDSANMfINMf0//XCgAK+QFAzPkQmiiUXwrbMeHywIffArNQB7Dy0IRRJbry4IVQNrry4Ib4I7vy0IgikvgA3gGkf8jKAMsfAc8Wye1UIJL4D95w2zzYFwIBIBARABm+Xw9qJoQICg65D6AsAgFuEhMCAUgUFQAZrc52omhAIOuQ64X/wAAZrx32omhAEOuQ64WPwAAXsyX7UTQcdch1wsfgABGyYvtRNDXCgCAAAAP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKGBkaAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQAcjDXLAgkji0h8uCS0gDtRNDSAFETuvLQj1RQMJExnAGBAUDXIdcKAPLgjuLIygBYzxbJ7VST8sCN4gAQk1vbMeHXTNB8Ui06"); + assert_eq!( + output.hash.to_hex(), + "437dae441a95a6bccdcdcea2560c313de24f13dd85c76d5d7ecaab1e70a1e52b" + ); } diff --git a/rust/tw_any_coin/tests/chains/ton/ton_sign.rs b/rust/tw_any_coin/tests/chains/ton/ton_sign.rs index a34b9b2cdbc..5437b95bb20 100644 --- a/rust/tw_any_coin/tests/chains/ton/ton_sign.rs +++ b/rust/tw_any_coin/tests/chains/ton/ton_sign.rs @@ -15,7 +15,7 @@ use tw_proto::TheOpenNetwork::Proto::mod_Transfer::OneOfpayload as PayloadType; /// The same Cell can be BoC encoded differently. /// Use this function to compare inner Cells closing eyes on the encoding. #[track_caller] -fn assert_eq_boc(left: &str, right: &str) { +pub(crate) fn assert_eq_boc(left: &str, right: &str) { use tw_ton_sdk::boc::BagOfCells; let left_boc = BagOfCells::parse_base64(left).unwrap(); @@ -29,7 +29,6 @@ fn test_ton_sign_transfer_and_deploy() { let private_key = "63474e5fe9511f1526a50567ce142befc343e71a49b865ac3908f58667319cb8"; let transfer = Proto::Transfer { - wallet_version: Proto::WalletVersion::WALLET_V4_R2, dest: "EQDYW_1eScJVxtitoBRksvoV9cCYo4uKGWLVNIHB1JqRR3n0".into(), amount: 10, mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 @@ -42,6 +41,7 @@ fn test_ton_sign_transfer_and_deploy() { private_key: private_key.decode_hex().unwrap().into(), messages: vec![transfer], expire_at: 1671135440, + wallet_version: Proto::WalletVersion::WALLET_V4_R2, ..Proto::SigningInput::default() }; @@ -63,7 +63,6 @@ fn test_ton_sign_transfer_and_deploy_4b1d9f() { let private_key = "e97620499dfee0107c0cd7f0ecb2afb3323d385b3a82320a5e3fa1fbdca6e722"; let transfer = Proto::Transfer { - wallet_version: Proto::WalletVersion::WALLET_V4_R2, dest: "UQA6whN_oU5h9jPljnlDSWRYQNDPkLaUqqaEWULNB_Zoykuu".into(), // 0.0001 TON amount: 100_000, @@ -77,6 +76,7 @@ fn test_ton_sign_transfer_and_deploy_4b1d9f() { private_key: private_key.decode_hex().unwrap().into(), messages: vec![transfer], expire_at: 1721892371, + wallet_version: Proto::WalletVersion::WALLET_V4_R2, ..Proto::SigningInput::default() }; @@ -97,7 +97,6 @@ fn test_ton_sign_transfer_ordinary() { let private_key = "c38f49de2fb13223a9e7d37d5d0ffbdd89a5eb7c8b0ee4d1c299f2cefe7dc4a0"; let transfer = Proto::Transfer { - wallet_version: Proto::WalletVersion::WALLET_V4_R2, dest: "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q".into(), amount: 10, mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 @@ -111,6 +110,7 @@ fn test_ton_sign_transfer_ordinary() { messages: vec![transfer], sequence_number: 6, expire_at: 1671132440, + wallet_version: Proto::WalletVersion::WALLET_V4_R2, ..Proto::SigningInput::default() }; @@ -131,7 +131,6 @@ fn test_ton_sign_transfer_all_balance() { let private_key = "c38f49de2fb13223a9e7d37d5d0ffbdd89a5eb7c8b0ee4d1c299f2cefe7dc4a0"; let transfer = Proto::Transfer { - wallet_version: Proto::WalletVersion::WALLET_V4_R2, dest: "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q".into(), amount: 0, mode: Proto::SendMode::ATTACH_ALL_CONTRACT_BALANCE as u32 @@ -145,6 +144,7 @@ fn test_ton_sign_transfer_all_balance() { messages: vec![transfer], sequence_number: 7, expire_at: 1681102222, + wallet_version: Proto::WalletVersion::WALLET_V4_R2, ..Proto::SigningInput::default() }; @@ -165,7 +165,6 @@ fn test_ton_sign_transfer_all_balance_non_bounceable() { let private_key = "c38f49de2fb13223a9e7d37d5d0ffbdd89a5eb7c8b0ee4d1c299f2cefe7dc4a0"; let transfer = Proto::Transfer { - wallet_version: Proto::WalletVersion::WALLET_V4_R2, dest: "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV".into(), amount: 0, mode: Proto::SendMode::ATTACH_ALL_CONTRACT_BALANCE as u32 @@ -179,6 +178,7 @@ fn test_ton_sign_transfer_all_balance_non_bounceable() { messages: vec![transfer], sequence_number: 8, expire_at: 1681102222, + wallet_version: Proto::WalletVersion::WALLET_V4_R2, ..Proto::SigningInput::default() }; @@ -199,7 +199,6 @@ fn test_ton_sign_transfer_with_ascii_comment() { let private_key = "c38f49de2fb13223a9e7d37d5d0ffbdd89a5eb7c8b0ee4d1c299f2cefe7dc4a0"; let transfer = Proto::Transfer { - wallet_version: Proto::WalletVersion::WALLET_V4_R2, dest: "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q".into(), amount: 10, mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 @@ -214,6 +213,7 @@ fn test_ton_sign_transfer_with_ascii_comment() { messages: vec![transfer], sequence_number: 10, expire_at: 1681102222, + wallet_version: Proto::WalletVersion::WALLET_V4_R2, ..Proto::SigningInput::default() }; @@ -234,7 +234,6 @@ fn test_ton_sign_transfer_with_utf8_comment() { let private_key = "c38f49de2fb13223a9e7d37d5d0ffbdd89a5eb7c8b0ee4d1c299f2cefe7dc4a0"; let transfer = Proto::Transfer { - wallet_version: Proto::WalletVersion::WALLET_V4_R2, dest: "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q".into(), amount: 10, mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 @@ -249,6 +248,7 @@ fn test_ton_sign_transfer_with_utf8_comment() { messages: vec![transfer], sequence_number: 11, expire_at: 1681102222, + wallet_version: Proto::WalletVersion::WALLET_V4_R2, ..Proto::SigningInput::default() }; @@ -269,7 +269,6 @@ fn test_ton_sign_transfer_invalid_wallet_version() { let private_key = "63474e5fe9511f1526a50567ce142befc343e71a49b865ac3908f58667319cb8"; let transfer = Proto::Transfer { - wallet_version: Proto::WalletVersion::WALLET_V3_R2, dest: "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q".into(), amount: 10, mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 @@ -282,6 +281,7 @@ fn test_ton_sign_transfer_invalid_wallet_version() { private_key: private_key.decode_hex().unwrap().into(), messages: vec![transfer], expire_at: 1671135440, + wallet_version: Proto::WalletVersion::WALLET_V3_R2, ..Proto::SigningInput::default() }; @@ -306,7 +306,6 @@ fn test_ton_sign_transfer_jettons() { }; let transfer = Proto::Transfer { - wallet_version: Proto::WalletVersion::WALLET_V4_R2, dest: "EQBiaD8PO1NwfbxSkwbcNT9rXDjqhiIvXWymNO-edV0H5lja".into(), amount: 100 * 1000 * 1000, mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 @@ -321,6 +320,7 @@ fn test_ton_sign_transfer_jettons() { messages: vec![transfer], sequence_number: 0, expire_at: 1787693046, + wallet_version: Proto::WalletVersion::WALLET_V4_R2, ..Proto::SigningInput::default() }; @@ -351,7 +351,6 @@ fn test_ton_sign_transfer_jettons_with_comment() { }; let transfer = Proto::Transfer { - wallet_version: Proto::WalletVersion::WALLET_V4_R2, dest: "EQBiaD8PO1NwfbxSkwbcNT9rXDjqhiIvXWymNO-edV0H5lja".into(), amount: 100 * 1000 * 1000, mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 @@ -367,6 +366,7 @@ fn test_ton_sign_transfer_jettons_with_comment() { messages: vec![transfer], sequence_number: 1, expire_at: 1787693046, + wallet_version: Proto::WalletVersion::WALLET_V4_R2, ..Proto::SigningInput::default() }; @@ -388,7 +388,6 @@ fn test_ton_sign_transfer_custom_payload() { let private_key = "e97620499dfee0107c0cd7f0ecb2afb3323d385b3a82320a5e3fa1fbdca6e722"; let transfer = Proto::Transfer { - wallet_version: Proto::WalletVersion::WALLET_V4_R2, dest: "UQA6whN_oU5h9jPljnlDSWRYQNDPkLaUqqaEWULNB_Zoykuu".into(), // 0.00025 TON amount: 250_000, @@ -407,6 +406,7 @@ fn test_ton_sign_transfer_custom_payload() { expire_at: 1721906219, messages: vec![transfer], sequence_number: 2, + wallet_version: Proto::WalletVersion::WALLET_V4_R2, ..Proto::SigningInput::default() }; @@ -414,7 +414,7 @@ fn test_ton_sign_transfer_custom_payload() { let output = signer.sign(CoinType::TON, input); assert_eq!(output.error, SigningError::OK, "{}", output.error_message); - // Successfully broadcasted: https://tonviewer.com/transaction/4b1d9f09856af70ea1058b557a87c9ba2abb0bca2029e0cbbe8c659d5dae4ce1 + // Successfully broadcasted: https://tonviewer.com/transaction/4ca4442921d0737d518b4863f8eafc2eb26824e9362ebaa9bc59373950b7fa86 assert_eq_boc(&output.encoded, "te6cckEBBAEAvwABRYgAUunJPoppOUA+tmGjPUbtz/BjUoB+QZYAbdqNMq3gIWQMAQGc981GQ9a8Yr4m2YeIeuuNIWlzdHliyW6MRq3RDs5kgvXJP+iNhdZU7o79DJnm/OKuzWI5FbiNy3SF0fGGBObDDCmpoxdmojQrAAAAAgADAgFmYgAdYQm/0Kcw+xnyxzyhpLIsIGhnyFtKVVNCLKFmg/s0ZRgehIAAAAAAAAAAAAAAAAABAwAgAAAAAEhpIHRoZXJlIHNpcoAlI8E="); assert_eq!( output.hash.to_hex(), @@ -442,7 +442,6 @@ fn test_ton_sign_transfer_custom_payload_with_state_init() { ); let transfer = Proto::Transfer { - wallet_version: Proto::WalletVersion::WALLET_V4_R2, dest: doge_contract_address.into(), // 0.069 TON amount: 69_000_000, @@ -461,6 +460,7 @@ fn test_ton_sign_transfer_custom_payload_with_state_init() { expire_at, messages: vec![transfer], sequence_number: 4, + wallet_version: Proto::WalletVersion::WALLET_V4_R2, ..Proto::SigningInput::default() }; diff --git a/rust/tw_any_coin/tests/chains/ton/ton_sign_wallet_v5r1.rs b/rust/tw_any_coin/tests/chains/ton/ton_sign_wallet_v5r1.rs new file mode 100644 index 00000000000..9e5c0f5308c --- /dev/null +++ b/rust/tw_any_coin/tests/chains/ton/ton_sign_wallet_v5r1.rs @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::chains::ton::cell_example::{ + comment_cell, contract_address_from_state_init, doge_chatbot_state_init, +}; +use crate::chains::ton::ton_sign::assert_eq_boc; +use tw_any_coin::test_utils::sign_utils::AnySignerHelper; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_proto::Common::Proto::SigningError; +use tw_proto::TheOpenNetwork::Proto; +use tw_proto::TheOpenNetwork::Proto::mod_Transfer::OneOfpayload as PayloadType; + +#[test] +fn test_ton_sign_wallet_v5r1_transfer_and_deploy() { + let private_key = "3570e35f54cfb843f2cfaf2b8cae7ceeb7b32225d7dbbd86f611056d74d9073e"; + + let transfer = Proto::Transfer { + dest: "EQBe6DtCpJZe8M4t-crMXe93JlEYgSl30S5OUuMSLOfeQfBu".into(), + amount: 10, + mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 + | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, + bounceable: true, + ..Proto::Transfer::default() + }; + + let input = Proto::SigningInput { + private_key: private_key.decode_hex().unwrap().into(), + messages: vec![transfer], + expire_at: 0xffffffff, + wallet_version: Proto::WalletVersion::WALLET_V5_R1, + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::TON, input); + + assert_eq!(output.error, SigningError::OK, "{}", output.error_message); + // Successfully broadcasted: https://tonviewer.com/transaction/Q32uRBqVprzNzc6iVgwxPeJPE92Fx21dfsqrHnCh5Ss= + assert_eq_boc(&output.encoded, "te6cckECGwEAA2sAAkWIACm9HPyVOpjCNOG6nbf+EwCONRHHpeMQsIlCoWNKhUaaHgECAgE0AwQBoXNpZ25///8R/////wAAAACACOfqY7L3l3aKc58eNxuJTaeH/fgBw2aG0coM+hjDpjWhJKbYKmsAD8v054HYSuO6vN3bQnV5U19BhsGfe1MDoAUBFP8A9KQT9LzyyAsGAFGAAAAAP///iKrcG+d35KaMMtuxik4jqNofFL51Mu1f8Qf19onqsDlyIAIKDsPIbQMWBwIBIAgJAWJiAC90HaFSSy94Zxb85WYu97uTKIxAlLvolycpcYkWc+8giFAAAAAAAAAAAAAAAAABFgIBSAoLAQLyDALc0CDXScEgkVuPYyDXCx8gghBleHRuvSGCEHNpbnS9sJJfA+CCEGV4dG66jrSAINchAdB01yH6QDD6RPgo+kQwWL2RW+DtRNCBAUHXIfQFgwf0Dm+hMZEw4YBA1yFwf9s84DEg10mBAoC5kTDgcOIXDQIBIA4PAR4g1wsfghBzaWduuvLgin8NAeaO8O2i7fshgwjXIgKDCNcjIIAg1yHTH9Mf0x/tRNDSANMfINMf0//XCgAK+QFAzPkQmiiUXwrbMeHywIffArNQB7Dy0IRRJbry4IVQNrry4Ib4I7vy0IgikvgA3gGkf8jKAMsfAc8Wye1UIJL4D95w2zzYFwIBIBARABm+Xw9qJoQICg65D6AsAgFuEhMCAUgUFQAZrc52omhAIOuQ64X/wAAZrx32omhAEOuQ64WPwAAXsyX7UTQcdch1wsfgABGyYvtRNDXCgCAAAAP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKGBkaAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQAcjDXLAgkji0h8uCS0gDtRNDSAFETuvLQj1RQMJExnAGBAUDXIdcKAPLgjuLIygBYzxbJ7VST8sCN4gAQk1vbMeHXTNB8Ui06"); + assert_eq!( + output.hash.to_hex(), + "437dae441a95a6bccdcdcea2560c313de24f13dd85c76d5d7ecaab1e70a1e52b" + ); +} + +#[test] +fn test_ton_sign_wallet_v5r1_transfer_ordinary() { + let private_key = "3570e35f54cfb843f2cfaf2b8cae7ceeb7b32225d7dbbd86f611056d74d9073e"; + + let transfer = Proto::Transfer { + dest: "EQBe6DtCpJZe8M4t-crMXe93JlEYgSl30S5OUuMSLOfeQfBu".into(), + amount: 10, + mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 + | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, + bounceable: true, + ..Proto::Transfer::default() + }; + + let input = Proto::SigningInput { + private_key: private_key.decode_hex().unwrap().into(), + messages: vec![transfer], + sequence_number: 1, + expire_at: 1723469663, + wallet_version: Proto::WalletVersion::WALLET_V5_R1, + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::TON, input); + + assert_eq!(output.error, SigningError::OK, "{}", output.error_message); + // Successfully broadcasted: https://tonviewer.com/transaction/UhZFShj8QG6M4nUPELVROdY_IAxSSkeZojh-t2XC2IU= + assert_eq_boc(&output.encoded, "te6cckEBBQEAuQABRYgAKb0c/JU6mMI04bqdt/4TAI41Ecel4xCwiUKhY0qFRpoMAQGhc2lnbn///xFmug9fAAAAAbsTmIIbTn/5n8piniYykIpNGhw0mstvB4PfGuTjdh7eIY3HAokbPMSdaAoOXq8mTWzT2p3v6XSoqTh7XKacOIDgAgIKDsPIbQMEAwFiYgAvdB2hUksveGcW/OVmLve7kyiMQJS76JcnKXGJFnPvIIhQAAAAAAAAAAAAAAAAAQQAAN6nHJg="); + + assert_eq!( + output.hash.to_hex(), + "5216454a18fc406e8ce2750f10b55139d63f200c524a4799a2387eb765c2d885" + ); +} + +#[test] +fn test_ton_sign_wallet_v5r1_transfer_all_balance() { + let private_key = "502d60b0f3327382e7d0585b789f1db9aa04907fe5cddc5c28818ec163ebf4ba"; + + let transfer = Proto::Transfer { + dest: "UQAU3o5-Sp1MYRpw3U7b_wmARxqI49LxiFhEoVCxpUKjTYXk".into(), + amount: 0, + mode: Proto::SendMode::ATTACH_ALL_CONTRACT_BALANCE as u32 + | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, + bounceable: true, + ..Proto::Transfer::default() + }; + + let input = Proto::SigningInput { + private_key: private_key.decode_hex().unwrap().into(), + messages: vec![transfer], + sequence_number: 4, + expire_at: 1723482783, + wallet_version: Proto::WalletVersion::WALLET_V5_R1, + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::TON, input); + + assert_eq!(output.error, SigningError::OK, "{}", output.error_message); + // Successfully broadcasted: https://tonviewer.com/transaction/q7MJULnzKZ1DY4itqKfHg_cj48ueFRIlT6I2O8rSLt0= + println!("{}", output.encoded); + assert_eq_boc(&output.encoded, "te6cckEBBQEAuAABRYgBQ8awIH6gcEaSzgT9ZAPxWhgEMUrjdKCkL1PJZ6pq4mgMAQGhc2lnbn///xFmukKfAAAABKg9bRJRcUN/+LR7Bny8XjKzf3+GzopWEf+n7ibzPpr9ZOZs5CRon4i0XFBiFMVVwfcnDI4RVWht6Jf5eSzOjwEgAgIKDsPIbYIEAwFgYgAKb0c/JU6mMI04bqdt/4TAI41Ecel4xCwiUKhY0qFRpoAAAAAAAAAAAAAAAAABBAAANXkwpA=="); + assert_eq!( + output.hash.to_hex(), + "abb30950b9f3299d436388ada8a7c783f723e3cb9e1512254fa2363bcad22edd" + ); +} + +#[test] +fn test_ton_sign_wallet_v5r1_transfer_all_balance_non_bounceable() { + let private_key = "3570e35f54cfb843f2cfaf2b8cae7ceeb7b32225d7dbbd86f611056d74d9073e"; + + let transfer = Proto::Transfer { + dest: "UQBe6DtCpJZe8M4t-crMXe93JlEYgSl30S5OUuMSLOfeQa2r".into(), + amount: 0, + mode: Proto::SendMode::ATTACH_ALL_CONTRACT_BALANCE as u32 + | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, + bounceable: false, + ..Proto::Transfer::default() + }; + + let input = Proto::SigningInput { + private_key: private_key.decode_hex().unwrap().into(), + messages: vec![transfer], + sequence_number: 2, + expire_at: 1723470825, + wallet_version: Proto::WalletVersion::WALLET_V5_R1, + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::TON, input); + + assert_eq!(output.error, SigningError::OK, "{}", output.error_message); + // Successfully broadcasted: https://tonviewer.com/transaction/f8meQabVMmfvUOJ0D_NTyji1qr4LJ58tTkPI6dlYSPE= + assert_eq_boc(&output.encoded, "te6cckEBBQEAuAABRYgAKb0c/JU6mMI04bqdt/4TAI41Ecel4xCwiUKhY0qFRpoMAQGhc2lnbn///xFmuhPpAAAAAq9jgguGOlFmpN566XBRCth1GfF8kVEc0DJkvLPKOa7MQMGzr0L1xpLt5OLovDpy4KwPqNxALo/b9NluZDLcukPgAgIKDsPIbYIEAwFgQgAvdB2hUksveGcW/OVmLve7kyiMQJS76JcnKXGJFnPvIIAAAAAAAAAAAAAAAAABBAAAI3vzjA=="); + assert_eq!( + output.hash.to_hex(), + "7fc99e41a6d53267ef50e2740ff353ca38b5aabe0b279f2d4e43c8e9d95848f1" + ); +} + +#[test] +fn test_ton_sign_wallet_v5r1_transfer_with_ascii_comment() { + let private_key = "3570e35f54cfb843f2cfaf2b8cae7ceeb7b32225d7dbbd86f611056d74d9073e"; + + let transfer = Proto::Transfer { + dest: "EQBe6DtCpJZe8M4t-crMXe93JlEYgSl30S5OUuMSLOfeQfBu".into(), + amount: 10, + mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 + | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, + bounceable: true, + comment: "test comment".into(), + ..Proto::Transfer::default() + }; + + let input = Proto::SigningInput { + private_key: private_key.decode_hex().unwrap().into(), + messages: vec![transfer], + sequence_number: 3, + expire_at: 1723472681, + wallet_version: Proto::WalletVersion::WALLET_V5_R1, + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::TON, input); + + assert_eq!(output.error, SigningError::OK, "{}", output.error_message); + // Successfully broadcasted: https://tonviewer.com/transaction/uc9sMl9Ey3yQ6sX48EKM0nFSeJoq2JcEi0xMzTLYV6M= + assert_eq_boc(&output.encoded, "te6cckEBBgEAywABRYgAKb0c/JU6mMI04bqdt/4TAI41Ecel4xCwiUKhY0qFRpoMAQGhc2lnbn///xFmuhspAAAAA6oLkiOh1mWEhej/y/dCZAZZT1hFo6aX9ROjIOX5HvwBz99SLLlCmTJd4QV1Vp9D9eaTXL7aT28rswEjyBSYpwPgAgIKDsPIbQMDBAAAAWJiAC90HaFSSy94Zxb85WYu97uTKIxAlLvolycpcYkWc+8giFAAAAAAAAAAAAAAAAABBQAgAAAAAHRlc3QgY29tbWVudDHkkjo="); + assert_eq!( + output.hash.to_hex(), + "b9cf6c325f44cb7c90eac5f8f0428cd27152789a2ad897048b4c4ccd32d857a3" + ); +} + +#[test] +fn test_ton_sign_wallet_v5r1_transfer_with_utf8_comment() { + let private_key = "3570e35f54cfb843f2cfaf2b8cae7ceeb7b32225d7dbbd86f611056d74d9073e"; + + let transfer = Proto::Transfer { + dest: "EQBe6DtCpJZe8M4t-crMXe93JlEYgSl30S5OUuMSLOfeQfBu".into(), + amount: 10, + mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 + | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, + bounceable: true, + comment: "тестовый комментарий".into(), + ..Proto::Transfer::default() + }; + + let input = Proto::SigningInput { + private_key: private_key.decode_hex().unwrap().into(), + messages: vec![transfer], + sequence_number: 4, + expire_at: 1723472948, + wallet_version: Proto::WalletVersion::WALLET_V5_R1, + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::TON, input); + + assert_eq!(output.error, SigningError::OK, "{}", output.error_message); + // Successfully broadcasted: https://tonviewer.com/transaction/tijrXAMehLvfESLWWgGf1R5jhW4CMoio11ZFD_musFg= + assert_eq_boc(&output.encoded, "te6cckEBBgEA5gABRYgAKb0c/JU6mMI04bqdt/4TAI41Ecel4xCwiUKhY0qFRpoMAQGhc2lnbn///xFmuhw0AAAABIDSMWXCM7WX+aQyurxO4nsYaqKhHm4ndzkjAS00AhtXe5fpKHUvVAFDvyqCi5T/PI/fwyZdralzzpXTrQaRP0CgAgIKDsPIbQMDBAAAAWJiAC90HaFSSy94Zxb85WYu97uTKIxAlLvolycpcYkWc+8giFAAAAAAAAAAAAAAAAABBQBWAAAAANGC0LXRgdGC0L7QstGL0Lkg0LrQvtC80LzQtdC90YLQsNGA0LjQuV6Inwc="); + assert_eq!( + output.hash.to_hex(), + "b628eb5c031e84bbdf1122d65a019fd51e63856e023288a8d756450ff9aeb058" + ); +} + +#[test] +fn test_ton_sign_wallet_v5r1_transfer_jettons() { + let private_key = "502d60b0f3327382e7d0585b789f1db9aa04907fe5cddc5c28818ec163ebf4ba"; + + let jetton_transfer = Proto::JettonTransfer { + query_id: 69, + // Transfer 0.12 USDT (decimal precision is 6). + jetton_amount: 120000, + to_owner: "UQAU3o5-Sp1MYRpw3U7b_wmARxqI49LxiFhEoVCxpUKjTYXk".into(), + // Send unused toncoins back to sender. + response_address: "UQCh41gQP1A4I0lnAn6yAfitDAIYpXG6UFIXqeSz1TVxNOJ_".into(), + forward_amount: 1, + }; + + let transfer = Proto::Transfer { + dest: "EQDg4AjfaxQBVsUFueenkKlHLhhYWrcBvCEzbEgfrT0nxuGC".into(), + amount: 100 * 1000 * 1000, + mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 + | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, + bounceable: true, + payload: PayloadType::jetton_transfer(jetton_transfer), + ..Proto::Transfer::default() + }; + + let input = Proto::SigningInput { + private_key: private_key.decode_hex().unwrap().into(), + messages: vec![transfer], + sequence_number: 0, + expire_at: 1723479990, + wallet_version: Proto::WalletVersion::WALLET_V5_R1, + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::TON, input); + + assert_eq!(output.error, SigningError::OK, "{}", output.error_message); + // Successfully broadcasted: https://tonviewer.com/transaction/CapFFJlwYNa_p8-fImgUPyKLxNuq4mR5K7U-G3zbFNg= + assert_eq_boc(&output.encoded, "te6cckECHAEAA8QAAkWIAUPGsCB+oHBGks4E/WQD8VoYBDFK43SgpC9TyWeqauJoHgECAgE0AwQBoXNpZ25///8R/////wAAAACK9TWZkcw/aJwl28F2cStjO8o6owv7/VKsx3RW0XtPaiwwFgi+CkmtzzJLGGp24JEwbwslQ1jNHyRqqUEM41AD4AUBFP8A9KQT9LzyyAsGAFGAAAAAP///iOEJZED3G4tDJvijkvHmVvq03ZLJ8Db8Bmagv84HH0SSIAIKDsPIbQMHCAIBIAkKAAABaGIAcHAEb7WKAKtigtzz08hUo5cMLC1bgN4QmbYkD9aek+MgL68IAAAAAAAAAAAAAAAAAAELAgFIDA0BAvIOAKgPin6lAAAAAAAAAEUwHUwIACm9HPyVOpjCNOG6nbf+EwCONRHHpeMQsIlCoWNKhUabACh41gQP1A4I0lnAn6yAfitDAIYpXG6UFIXqeSz1TVxNAgIC3NAg10nBIJFbj2Mg1wsfIIIQZXh0br0hghBzaW50vbCSXwPgghBleHRuuo60gCDXIQHQdNch+kAw+kT4KPpEMFi9kVvg7UTQgQFB1yH0BYMH9A5voTGRMOGAQNchcH/bPOAxINdJgQKAuZEw4HDiGA8CASAQEQEeINcLH4IQc2lnbrry4Ip/DwHmjvDtou37IYMI1yICgwjXIyCAINch0x/TH9Mf7UTQ0gDTHyDTH9P/1woACvkBQMz5EJoolF8K2zHh8sCH3wKzUAew8tCEUSW68uCFUDa68uCG+CO78tCIIpL4AN4BpH/IygDLHwHPFsntVCCS+A/ecNs82BgCASASEwAZvl8PaiaECAoOuQ+gLAIBbhQVAgFIFhcAGa3OdqJoQCDrkOuF/8AAGa8d9qJoQBDrkOuFj8AAF7Ml+1E0HHXIdcLH4AARsmL7UTQ1woAgA/btou37AvQEIW6SbCGOTAIh1zkwcJQhxwCzji0B1yggdh5DbCDXScAI8uCTINdKwALy4JMg1x0GxxLCAFIwsPLQiddM1zkwAaTobBKEB7vy4JPXSsAA8uCT7VXi0gABwACRW+Dr1ywIFCCRcJYB1ywIHBLiUhCx4w8g10oZGhsAlgH6QAH6RPgo+kQwWLry4JHtRNCBAUHXGPQFBJ1/yMoAQASDB/RT8uCLjhQDgwf0W/LgjCLXCgAhbgGzsPLQkOLIUAPPFhL0AMntVAByMNcsCCSOLSHy4JLSAO1E0NIAURO68tCPVFAwkTGcAYEBQNch1woA8uCO4sjKAFjPFsntVJPywI3iABCTW9sx4ddM0JlgUSM="); + assert_eq!( + output.hash.to_hex(), + "09aa4514997060d6bfa7cf9f2268143f228bc4dbaae264792bb53e1b7cdb14d8" + ); +} + +#[test] +fn test_ton_sign_wallet_v5r1_transfer_jettons_with_comment() { + let private_key = "502d60b0f3327382e7d0585b789f1db9aa04907fe5cddc5c28818ec163ebf4ba"; + + let jetton_transfer = Proto::JettonTransfer { + query_id: 0, + // Transfer 0.11 USDT (decimal precision is 6). + jetton_amount: 110000, + to_owner: "UQAU3o5-Sp1MYRpw3U7b_wmARxqI49LxiFhEoVCxpUKjTYXk".into(), + // Send unused toncoins back to sender. + response_address: "UQCh41gQP1A4I0lnAn6yAfitDAIYpXG6UFIXqeSz1TVxNOJ_".into(), + forward_amount: 1, + }; + + let transfer = Proto::Transfer { + dest: "EQDg4AjfaxQBVsUFueenkKlHLhhYWrcBvCEzbEgfrT0nxuGC".into(), + amount: 100 * 1000 * 1000, + mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 + | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, + bounceable: true, + comment: "test comment".into(), + payload: PayloadType::jetton_transfer(jetton_transfer), + ..Proto::Transfer::default() + }; + + let input = Proto::SigningInput { + private_key: private_key.decode_hex().unwrap().into(), + messages: vec![transfer], + sequence_number: 1, + expire_at: 1723480954, + wallet_version: Proto::WalletVersion::WALLET_V5_R1, + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::TON, input); + + assert_eq!(output.error, SigningError::OK, "{}", output.error_message); + // Successfully broadcasted: https://tonviewer.com/transaction/gkyDtRqJa_5-GGut6BZcVS5JmRLbObGiKrpmaIBPoxc= + assert_eq_boc(&output.encoded, "te6cckECBgEAASIAAUWIAUPGsCB+oHBGks4E/WQD8VoYBDFK43SgpC9TyWeqauJoDAEBoXNpZ25///8RZro7egAAAAGYUu8+ZHZK/wWb9ojB++h5tz2ie7e4GktWZfr475zJcnpRLZADd9wbyuuV7GXr8QzKeqvQbSDVJlgv4bcdnjwDYAICCg7DyG0DAwQAAAFoYgBwcARvtYoAq2KC3PPTyFSjlwwsLVuA3hCZtiQP1p6T4yAvrwgAAAAAAAAAAAAAAAAAAQUAyA+KfqUAAAAAAAAAADAa2wgAKb0c/JU6mMI04bqdt/4TAI41Ecel4xCwiUKhY0qFRpsAKHjWBA/UDgjSWcCfrIB+K0MAhilcbpQUhep5LPVNXE0CAgAAAAB0ZXN0IGNvbW1lbnQIl4du"); + assert_eq!( + output.hash.to_hex(), + "824c83b51a896bfe7e186bade8165c552e499912db39b1a22aba6668804fa317" + ); +} + +#[test] +fn test_ton_sign_wallet_v5r1_transfer_custom_payload() { + // UQCh41gQP1A4I0lnAn6yAfitDAIYpXG6UFIXqeSz1TVxNOJ_ + let private_key = "502d60b0f3327382e7d0585b789f1db9aa04907fe5cddc5c28818ec163ebf4ba"; + + let transfer = Proto::Transfer { + dest: "UQAU3o5-Sp1MYRpw3U7b_wmARxqI49LxiFhEoVCxpUKjTYXk".into(), + // 0.00025 TON + amount: 250_000, + mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 + | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, + bounceable: true, + payload: PayloadType::custom_payload(Proto::CustomPayload { + state_init: "".into(), + payload: comment_cell("Hi there sir").into(), + }), + ..Proto::Transfer::default() + }; + + let input = Proto::SigningInput { + private_key: private_key.decode_hex().unwrap().into(), + expire_at: 1723481664, + messages: vec![transfer], + sequence_number: 2, + wallet_version: Proto::WalletVersion::WALLET_V5_R1, + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::TON, input); + + assert_eq!(output.error, SigningError::OK, "{}", output.error_message); + // Successfully broadcasted: https://tonviewer.com/transaction/_Pr9kkthRh5qQtALT4HPpWJKRF3qK7L7DUa_hmu3VAI= + assert_eq_boc(&output.encoded, "te6cckEBBgEAzQABRYgBQ8awIH6gcEaSzgT9ZAPxWhgEMUrjdKCkL1PJZ6pq4mgMAQGhc2lnbn///xFmuj5AAAAAAqWnjYMI5ukKZ/38yUBJq3dj7fHGvxzmY4WgFTqo6utiQ1rAW1gZTm8tErHXXXEZykBfG9qDSKaTB/XEvbvTK4CgAgIKDsPIbQMDBAAAAWZiAApvRz8lTqYwjThup23/hMAjjURx6XjELCJQqFjSoVGmmB6EgAAAAAAAAAAAAAAAAAEFACAAAAAASGkgdGhlcmUgc2lyZT4q+g=="); + assert_eq!( + output.hash.to_hex(), + "fcfafd924b61461e6a42d00b4f81cfa5624a445dea2bb2fb0d46bf866bb75402" + ); +} + +#[test] +fn test_ton_sign_wallet_v5r1_transfer_custom_payload_with_state_init() { + // UQCh41gQP1A4I0lnAn6yAfitDAIYpXG6UFIXqeSz1TVxNOJ_ + let private_key = "502d60b0f3327382e7d0585b789f1db9aa04907fe5cddc5c28818ec163ebf4ba"; + + let current_timestamp = 1723481400; + let expire_at = current_timestamp + 60 * 10; + + let doge_state_init = doge_chatbot_state_init(current_timestamp); + let doge_contract_address = contract_address_from_state_init(&doge_state_init); + assert_eq!( + doge_state_init, + "te6cckEBBAEAUwACATQBAgEU/wD0pBP0vPLICwMAEAAAAZFHfyLAAGrTMAGCCGlJILmRMODQ0wMx+kAwi0ZG9nZYcCCAGMjLBVAEzxaARfoCE8tqEssfAc8WyXP7AOj1qKs=" + ); + assert_eq!( + doge_contract_address, + "0:6af6c37f5e1704fc46174f0cbf5b558cbbf1dbc750c59c3c851bb98c7a94a145", + ); + + let transfer = Proto::Transfer { + dest: doge_contract_address.into(), + // 0.0069 TON + amount: 6_900_000, + mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32 + | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS as u32, + bounceable: false, + payload: PayloadType::custom_payload(Proto::CustomPayload { + state_init: doge_state_init.into(), + payload: comment_cell("This transaction deploys Doge Chatbot contract").into(), + }), + ..Proto::Transfer::default() + }; + + let input = Proto::SigningInput { + private_key: private_key.decode_hex().unwrap().into(), + expire_at, + messages: vec![transfer], + sequence_number: 3, + wallet_version: Proto::WalletVersion::WALLET_V5_R1, + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::TON, input); + + assert_eq!(output.error, SigningError::OK, "{}", output.error_message); + // Successfully broadcasted: https://tonviewer.com/transaction/Y35nc-J86gbrMC5CljEMqzCUG5vFLilLjNdHgb193eA= + assert_eq_boc(&output.encoded, "te6cckECCgEAAUQAAUWIAUPGsCB+oHBGks4E/WQD8VoYBDFK43SgpC9TyWeqauJoDAEBoXNpZ25///8RZro/kAAAAAOAA5mkphyRfc8/OtuqrYWeR9u1Eib135FXAntzMzjdjj+1xH96YruRHinS9gB3bfHxtK3QUvy+zrcqKliVvbtA4AICCg7DyG0DAwQAAAJnQgA1e2G/rwuCfiMLp4ZfrarGXfjt46hizh5CjdzGPUpQoptKSQAAAAAAAAAAAAAAAAADwAUGAgE0BwgAZAAAAABUaGlzIHRyYW5zYWN0aW9uIGRlcGxveXMgRG9nZSBDaGF0Ym90IGNvbnRyYWN0ART/APSkE/S88sgLCQAQAAABkUd/IsAAatMwAYIIaUkguZEw4NDTAzH6QDCLRkb2dlhwIIAYyMsFUATPFoBF+gITy2oSyx8BzxbJc/sArbH+dw=="); + assert_eq!( + output.hash.to_hex(), + "637e6773e27cea06eb302e4296310cab30941b9bc52e294b8cd74781bd7ddde0" + ); +} + +#[test] +fn test_ton_sign_wallet_v5r1_missing_required_send_mode() { + let private_key = "3570e35f54cfb843f2cfaf2b8cae7ceeb7b32225d7dbbd86f611056d74d9073e"; + + let transfer = Proto::Transfer { + dest: "EQBe6DtCpJZe8M4t-crMXe93JlEYgSl30S5OUuMSLOfeQfBu".into(), + amount: 10, + // Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS is required for wallet v5r1 external messages. + mode: Proto::SendMode::PAY_FEES_SEPARATELY as u32, + bounceable: true, + ..Proto::Transfer::default() + }; + + let input = Proto::SigningInput { + private_key: private_key.decode_hex().unwrap().into(), + messages: vec![transfer], + sequence_number: 1, + expire_at: 1723469663, + wallet_version: Proto::WalletVersion::WALLET_V5_R1, + ..Proto::SigningInput::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::TON, input); + + assert_eq!(output.error, SigningError::Error_internal); +} 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/rust/wallet_core_rs/src/ffi/wallet/ton_wallet.rs b/rust/wallet_core_rs/src/ffi/wallet/ton_wallet.rs index 7e785dca41d..5b467a15dbc 100644 --- a/rust/wallet_core_rs/src/ffi/wallet/ton_wallet.rs +++ b/rust/wallet_core_rs/src/ffi/wallet/ton_wallet.rs @@ -107,3 +107,25 @@ pub unsafe extern "C" fn tw_ton_wallet_build_v4_r2_state_init( ); TWString::from(state_init).into_ptr() } + +// Constructs a TON Wallet V5R1 stateInit encoded as BoC (BagOfCells) for the given `public_key`. +/// +/// \param public_key wallet's public key. +/// \param workchain TON workchain to which the wallet belongs. Usually, base chain is used (0). +/// \param wallet_id wallet's ID allows to create multiple wallets for the same private key. +/// \return Pointer to a base64 encoded Bag Of Cells (BoC) StateInit. Null if invalid public key provided. +#[no_mangle] +pub unsafe extern "C" fn tw_ton_wallet_build_v5_r1_state_init( + public_key: *const TWPublicKey, + workchain: i32, + wallet_id: i32, +) -> *mut TWString { + let public_key = try_or_else!(TWPublicKey::from_ptr_as_ref(public_key), std::ptr::null_mut); + let ed_pubkey = try_or_else!(public_key.as_ref().to_ed25519(), std::ptr::null_mut).clone(); + + let state_init = try_or_else!( + WalletProvider::v5r1_state_init(ed_pubkey, workchain, wallet_id), + std::ptr::null_mut + ); + TWString::from(state_init).into_ptr() +} diff --git a/rust/wallet_core_rs/tests/wallet/ton_wallet.rs b/rust/wallet_core_rs/tests/wallet/ton_wallet.rs index d2f583c02bb..a2187e038ed 100644 --- a/rust/wallet_core_rs/tests/wallet/ton_wallet.rs +++ b/rust/wallet_core_rs/tests/wallet/ton_wallet.rs @@ -8,9 +8,11 @@ use tw_keypair::test_utils::tw_public_key_helper::TWPublicKeyHelper; use tw_keypair::tw::PublicKeyType; use tw_memory::test_utils::tw_string_helper::TWStringHelper; use tw_memory::test_utils::tw_wrapper::TWWrapper; +use tw_ton::resources::WALLET_ID_V5R1_TON_MAINNET; use wallet_core_rs::ffi::wallet::ton_wallet::{ - tw_ton_wallet_build_v4_r2_state_init, tw_ton_wallet_create_with_mnemonic, tw_ton_wallet_delete, - tw_ton_wallet_get_key, tw_ton_wallet_is_valid_mnemonic, + tw_ton_wallet_build_v4_r2_state_init, tw_ton_wallet_build_v5_r1_state_init, + tw_ton_wallet_create_with_mnemonic, tw_ton_wallet_delete, tw_ton_wallet_get_key, + tw_ton_wallet_is_valid_mnemonic, }; #[test] @@ -39,7 +41,7 @@ fn test_ton_wallet_get_key() { } #[test] -fn test_ton_wallet_create_state_init() { +fn test_ton_wallet_v4_r2_create_state_init() { let public_key = TWPublicKeyHelper::with_hex( "f229a9371fa7c2108b3d90ea22c9be705ff5d0cfeaee9cbb9366ff0171579357", PublicKeyType::Ed25519, @@ -50,3 +52,16 @@ fn test_ton_wallet_create_state_init() { }); assert_eq!(state_init_boc.to_string().unwrap(), "te6cckECFgEAAwQAAgE0AQIBFP8A9KQT9LzyyAsDAFEAAAAAKamjF/IpqTcfp8IQiz2Q6iLJvnBf9dDP6u6cu5Nm/wFxV5NXQAIBIAQFAgFIBgcE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8ICQoLAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNDA0CASAODwBu0gf6ANTUIvkABcjKBxXL/8nQd3SAGMjLBcsCIs8WUAX6AhTLaxLMzMlz+wDIQBSBAQj0UfKnAgBwgQEI1xj6ANM/yFQgR4EBCPRR8qeCEG5vdGVwdIAYyMsFywJQBs8WUAT6AhTLahLLH8s/yXP7AAIAbIEBCNcY+gDTPzBSJIEBCPRZ8qeCEGRzdHJwdIAYyMsFywJQBc8WUAP6AhPLassfEss/yXP7AAAK9ADJ7VQAeAH6APQEMPgnbyIwUAqhIb7y4FCCEHBsdWeDHrFwgBhQBMsFJs8WWPoCGfQAy2kXyx9SYMs/IMmAQPsABgCKUASBAQj0WTDtRNCBAUDXIMgBzxb0AMntVAFysI4jghBkc3Rygx6xcIAYUAXLBVADzxYj+gITy2rLH8s/yYBA+wCSXwPiAgEgEBEAWb0kK29qJoQICga5D6AhhHDUCAhHpJN9KZEM5pA+n/mDeBKAG3gQFImHFZ8xhAIBWBITABG4yX7UTQ1wsfgAPbKd+1E0IEBQNch9AQwAsjKB8v/ydABgQEI9ApvoTGACASAUFQAZrc52omhAIGuQ64X/wAAZrx32omhAEGuQ64WPwEXtMkg="); } + +#[test] +fn test_ton_wallet_v5_r1_create_state_init() { + let public_key = TWPublicKeyHelper::with_hex( + "f229a9371fa7c2108b3d90ea22c9be705ff5d0cfeaee9cbb9366ff0171579357", + PublicKeyType::Ed25519, + ); + assert!(!public_key.is_null()); + let state_init_boc = TWStringHelper::wrap(unsafe { + tw_ton_wallet_build_v5_r1_state_init(public_key.ptr(), 0, WALLET_ID_V5R1_TON_MAINNET) + }); + assert_eq!(state_init_boc.to_string().unwrap(), "te6cckECFgEAArEAAgE0AQIBFP8A9KQT9LzyyAsDAFGAAAAAP///iPkU1JuP0+EIRZ7IdRFk3zgv+uhn9XdOXcmzf4C4q8mroAIBIAQFAgFIBgcBAvIIAtzQINdJwSCRW49jINcLHyCCEGV4dG69IYIQc2ludL2wkl8D4IIQZXh0brqOtIAg1yEB0HTXIfpAMPpE+Cj6RDBYvZFb4O1E0IEBQdch9AWDB/QOb6ExkTDhgEDXIXB/2zzgMSDXSYECgLmRMOBw4hIJAgEgCgsBHiDXCx+CEHNpZ2668uCKfwkB5o7w7aLt+yGDCNciAoMI1yMggCDXIdMf0x/TH+1E0NIA0x8g0x/T/9cKAAr5AUDM+RCaKJRfCtsx4fLAh98Cs1AHsPLQhFEluvLghVA2uvLghvgju/LQiCKS+ADeAaR/yMoAyx8BzxbJ7VQgkvgP3nDbPNgSAgEgDA0AGb5fD2omhAgKDrkPoCwCAW4ODwIBSBARABmtznaiaEAg65Drhf/AABmvHfaiaEAQ65DrhY/AABezJftRNBx1yHXCx+AAEbJi+1E0NcKAIAP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKExQVAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQAcjDXLAgkji0h8uCS0gDtRNDSAFETuvLQj1RQMJExnAGBAUDXIdcKAPLgjuLIygBYzxbJ7VST8sCN4gAQk1vbMeHXTNBAlw92"); +} 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/src/proto/LiquidStaking.proto b/src/proto/LiquidStaking.proto index 2193c5d283e..b91c1bdf3b4 100644 --- a/src/proto/LiquidStaking.proto +++ b/src/proto/LiquidStaking.proto @@ -13,7 +13,8 @@ import "Aptos.proto"; // Enum for supported coins for liquid staking enum Coin { - MATIC = 0; + // Previously, MATIC. + POL = 0; ATOM = 1; BNB = 2; APT = 3; diff --git a/src/proto/TheOpenNetwork.proto b/src/proto/TheOpenNetwork.proto index cc2424b9a75..092ec3bcd9b 100644 --- a/src/proto/TheOpenNetwork.proto +++ b/src/proto/TheOpenNetwork.proto @@ -13,6 +13,7 @@ enum WalletVersion { WALLET_V3_R1 = 0; WALLET_V3_R2 = 1; WALLET_V4_R2 = 2; + WALLET_V5_R1 = 3; }; enum SendMode { @@ -25,32 +26,29 @@ enum SendMode { }; message Transfer { - // Wallet version - WalletVersion wallet_version = 1; - // Recipient address - string dest = 2; + string dest = 1; // Amount to send in nanotons - uint64 amount = 3; + uint64 amount = 2; // Send mode (optional, 0 by default) // Learn more: https://ton.org/docs/develop/func/stdlib#send_raw_message - uint32 mode = 4; + uint32 mode = 3; // Transfer comment message (optional, empty by default) // Ignored if `custom_payload` is specified - string comment = 5; + string comment = 4; // If the address is bounceable - bool bounceable = 6; + bool bounceable = 5; // One of the Transfer message payloads (optional). oneof payload { // Jetton transfer payload. - JettonTransfer jetton_transfer = 7; + JettonTransfer jetton_transfer = 6; // TON transfer with custom stateInit and payload (contract call). - CustomPayload custom_payload = 8; + CustomPayload custom_payload = 7; } } @@ -97,6 +95,9 @@ message SigningInput { // Expiration UNIX timestamp (optional, now() + 60 by default) uint32 expire_at = 5; + + // Wallet version + WalletVersion wallet_version = 6; } // Transaction signing output. diff --git a/swift/Tests/Blockchains/TheOpenNetworkTests.swift b/swift/Tests/Blockchains/TheOpenNetworkTests.swift index 716d5fd8b9c..f27e3a2fbd8 100644 --- a/swift/Tests/Blockchains/TheOpenNetworkTests.swift +++ b/swift/Tests/Blockchains/TheOpenNetworkTests.swift @@ -82,7 +82,6 @@ class TheOpenNetworkTests: XCTestCase { let privateKeyData = Data(hexString: "c38f49de2fb13223a9e7d37d5d0ffbdd89a5eb7c8b0ee4d1c299f2cefe7dc4a0")! let transfer = TheOpenNetworkTransfer.with { - $0.walletVersion = TheOpenNetworkWalletVersion.walletV4R2 $0.dest = "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q" $0.amount = 10 $0.mode = UInt32(TheOpenNetworkSendMode.payFeesSeparately.rawValue | TheOpenNetworkSendMode.ignoreActionPhaseErrors.rawValue) @@ -94,6 +93,7 @@ class TheOpenNetworkTests: XCTestCase { $0.privateKey = privateKeyData $0.sequenceNumber = 6 $0.expireAt = 1671132440 + $0.walletVersion = TheOpenNetworkWalletVersion.walletV4R2 } let output: TheOpenNetworkSigningOutput = AnySigner.sign(input: input, coin: .ton) @@ -115,7 +115,6 @@ class TheOpenNetworkTests: XCTestCase { } let transfer = TheOpenNetworkTransfer.with { - $0.walletVersion = TheOpenNetworkWalletVersion.walletV4R2 $0.dest = "EQBiaD8PO1NwfbxSkwbcNT9rXDjqhiIvXWymNO-edV0H5lja" $0.amount = 100 * 1000 * 1000 $0.mode = UInt32(TheOpenNetworkSendMode.payFeesSeparately.rawValue | TheOpenNetworkSendMode.ignoreActionPhaseErrors.rawValue) @@ -129,6 +128,7 @@ class TheOpenNetworkTests: XCTestCase { $0.privateKey = privateKeyData $0.sequenceNumber = 1 $0.expireAt = 1787693046 + $0.walletVersion = TheOpenNetworkWalletVersion.walletV4R2 } let output: TheOpenNetworkSigningOutput = AnySigner.sign(input: input, coin: .ton) @@ -159,7 +159,6 @@ class TheOpenNetworkTests: XCTestCase { } let transfer = TheOpenNetworkTransfer.with { - $0.walletVersion = TheOpenNetworkWalletVersion.walletV4R2 $0.dest = dogeChatbotDeployingAddress // 0.069 TON $0.amount = 69_000_000 @@ -173,6 +172,7 @@ class TheOpenNetworkTests: XCTestCase { $0.privateKey = privateKeyData $0.sequenceNumber = 4 $0.expireAt = 1721939714 + $0.walletVersion = TheOpenNetworkWalletVersion.walletV4R2 } let output: TheOpenNetworkSigningOutput = AnySigner.sign(input: input, coin: .ton) diff --git a/swift/Tests/LiquidStakingTests.swift b/swift/Tests/LiquidStakingTests.swift index b5d721c40d6..009ad2759d1 100644 --- a/swift/Tests/LiquidStakingTests.swift +++ b/swift/Tests/LiquidStakingTests.swift @@ -15,7 +15,7 @@ class LiquidStakingTests: XCTestCase { $0.stake = LiquidStakingStake.with { $0.amount = "1000000000000000000" $0.asset = LiquidStakingAsset.with { - $0.stakingToken = .matic + $0.stakingToken = .pol } } } diff --git a/tests/chains/Polygon/TWCoinTypeTests.cpp b/tests/chains/Polygon/TWCoinTypeTests.cpp index bf535061355..e9360551de0 100644 --- a/tests/chains/Polygon/TWCoinTypeTests.cpp +++ b/tests/chains/Polygon/TWCoinTypeTests.cpp @@ -24,7 +24,7 @@ TEST(TWPolygonCoinType, TWCoinType) { ASSERT_EQ(TWBlockchainEthereum, TWCoinTypeBlockchain(TWCoinTypePolygon)); ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypePolygon)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypePolygon)); - assertStringsEqual(symbol, "MATIC"); + assertStringsEqual(symbol, "POL"); assertStringsEqual(txUrl, "https://polygonscan.com/tx/0xe26ed1470d5bf99a53d687843e7acdf7e4ba6620af93b4d672e714de90476e8e"); assertStringsEqual(accUrl, "https://polygonscan.com/address/0x720E1fa107A1Df39Db4E78A3633121ac36Bec132"); assertStringsEqual(id, "polygon"); diff --git a/tests/chains/TheOpenNetwork/TWAnySignerTests.cpp b/tests/chains/TheOpenNetwork/TWAnySignerTests.cpp index 467461bf1b8..ffd5d276a78 100644 --- a/tests/chains/TheOpenNetwork/TWAnySignerTests.cpp +++ b/tests/chains/TheOpenNetwork/TWAnySignerTests.cpp @@ -12,11 +12,10 @@ namespace TW::TheOpenNetwork::tests { -TEST(TWAnySignerTheOpenNetwork, SingMessageToTransferAndDeployWallet) { +TEST(TWAnySignerTheOpenNetwork, SignMessageToTransferAndDeployWalletV4R2) { Proto::SigningInput input; auto& transfer = *input.add_messages(); - transfer.set_wallet_version(Proto::WALLET_V4_R2); transfer.set_dest("EQDYW_1eScJVxtitoBRksvoV9cCYo4uKGWLVNIHB1JqRR3n0"); transfer.set_amount(10); transfer.set_mode(Proto::SendMode::PAY_FEES_SEPARATELY | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS); @@ -27,6 +26,8 @@ TEST(TWAnySignerTheOpenNetwork, SingMessageToTransferAndDeployWallet) { input.set_expire_at(1671135440); + input.set_wallet_version(Proto::WALLET_V4_R2); + Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeTON); @@ -36,5 +37,29 @@ TEST(TWAnySignerTheOpenNetwork, SingMessageToTransferAndDeployWallet) { ASSERT_EQ(output.encoded(), "te6cckECGgEAA7IAAkWIAM33x4uAd+uQTyXyCZPxflESlNVHpCeoOECtNsqVW9tmHgECAgE0AwQBnOfG8YGGhFeE+iDE1jxCYeWKElbGDm3oqm2pwAhmVWSzWv5n6vVq8JY0J6p4sL+hqJU3iYPH8TX5mGLfcbbmtwgpqaMX/////wAAAAAAAwUBFP8A9KQT9LzyyAsGAFEAAAAAKamjF/Qsd/kxvqIOxdAVBzEna7suKGCUdmEkWyMZ74Ez7o1BQAFiYgBsLf6vJOEq42xW0AoyWX0K+uBMUcXFDLFqmkDg6k1Io4hQAAAAAAAAAAAAAAAAAQcCASAICQAAAgFICgsE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8MDQ4PAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNEBECASASEwBu0gf6ANTUIvkABcjKBxXL/8nQd3SAGMjLBcsCIs8WUAX6AhTLaxLMzMlz+wDIQBSBAQj0UfKnAgBwgQEI1xj6ANM/yFQgR4EBCPRR8qeCEG5vdGVwdIAYyMsFywJQBs8WUAT6AhTLahLLH8s/yXP7AAIAbIEBCNcY+gDTPzBSJIEBCPRZ8qeCEGRzdHJwdIAYyMsFywJQBc8WUAP6AhPLassfEss/yXP7AAAK9ADJ7VQAeAH6APQEMPgnbyIwUAqhIb7y4FCCEHBsdWeDHrFwgBhQBMsFJs8WWPoCGfQAy2kXyx9SYMs/IMmAQPsABgCKUASBAQj0WTDtRNCBAUDXIMgBzxb0AMntVAFysI4jghBkc3Rygx6xcIAYUAXLBVADzxYj+gITy2rLH8s/yYBA+wCSXwPiAgEgFBUAWb0kK29qJoQICga5D6AhhHDUCAhHpJN9KZEM5pA+n/mDeBKAG3gQFImHFZ8xhAIBWBYXABG4yX7UTQ1wsfgAPbKd+1E0IEBQNch9AQwAsjKB8v/ydABgQEI9ApvoTGACASAYGQAZrc52omhAIGuQ64X/wAAZrx32omhAEGuQ64WPwJiaP4Q="); } +TEST(TWAnySignerTheOpenNetwork, SignMessageToTransferAndDeployWalletV5R1) { + Proto::SigningInput input; + + auto& transfer = *input.add_messages(); + transfer.set_dest("EQBe6DtCpJZe8M4t-crMXe93JlEYgSl30S5OUuMSLOfeQfBu"); + transfer.set_amount(10); + transfer.set_mode(Proto::SendMode::PAY_FEES_SEPARATELY | Proto::SendMode::IGNORE_ACTION_PHASE_ERRORS); + transfer.set_bounceable(true); + + const auto privateKey = parse_hex("3570e35f54cfb843f2cfaf2b8cae7ceeb7b32225d7dbbd86f611056d74d9073e"); + input.set_private_key(privateKey.data(), privateKey.size()); + + input.set_expire_at(0xffffffff); + + input.set_wallet_version(Proto::WALLET_V5_R1); + + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeTON); + + // Successfully broadcasted: https://tonviewer.com/transaction/Q32uRBqVprzNzc6iVgwxPeJPE92Fx21dfsqrHnCh5Ss= + ASSERT_EQ(hex(output.hash()), "437dae441a95a6bccdcdcea2560c313de24f13dd85c76d5d7ecaab1e70a1e52b"); + ASSERT_EQ(output.encoded(), "te6cckECGwEAA2sAAkWIACm9HPyVOpjCNOG6nbf+EwCONRHHpeMQsIlCoWNKhUaaHgECAgE0AwQBoXNpZ25///8R/////wAAAACACOfqY7L3l3aKc58eNxuJTaeH/fgBw2aG0coM+hjDpjWhJKbYKmsAD8v054HYSuO6vN3bQnV5U19BhsGfe1MDoAUBFP8A9KQT9LzyyAsGAFGAAAAAP///iKrcG+d35KaMMtuxik4jqNofFL51Mu1f8Qf19onqsDlyIAIKDsPIbQMWBwIBIAgJAWJiAC90HaFSSy94Zxb85WYu97uTKIxAlLvolycpcYkWc+8giFAAAAAAAAAAAAAAAAABFgIBSAoLAQLyDALc0CDXScEgkVuPYyDXCx8gghBleHRuvSGCEHNpbnS9sJJfA+CCEGV4dG66jrSAINchAdB01yH6QDD6RPgo+kQwWL2RW+DtRNCBAUHXIfQFgwf0Dm+hMZEw4YBA1yFwf9s84DEg10mBAoC5kTDgcOIXDQIBIA4PAR4g1wsfghBzaWduuvLgin8NAeaO8O2i7fshgwjXIgKDCNcjIIAg1yHTH9Mf0x/tRNDSANMfINMf0//XCgAK+QFAzPkQmiiUXwrbMeHywIffArNQB7Dy0IRRJbry4IVQNrry4Ib4I7vy0IgikvgA3gGkf8jKAMsfAc8Wye1UIJL4D95w2zzYFwIBIBARABm+Xw9qJoQICg65D6AsAgFuEhMCAUgUFQAZrc52omhAIOuQ64X/wAAZrx32omhAEOuQ64WPwAAXsyX7UTQcdch1wsfgABGyYvtRNDXCgCAAAAP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKGBkaAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQAcjDXLAgkji0h8uCS0gDtRNDSAFETuvLQj1RQMJExnAGBAUDXIdcKAPLgjuLIygBYzxbJ7VST8sCN4gAQk1vbMeHXTNB8Ui06"); +} + } // namespace TW::TheOpenNetwork::tests diff --git a/tests/common/LiquidStaking/LiquidStakingTests.cpp b/tests/common/LiquidStaking/LiquidStakingTests.cpp index 46f00178f57..ac5bad4c7b2 100644 --- a/tests/common/LiquidStaking/LiquidStakingTests.cpp +++ b/tests/common/LiquidStaking/LiquidStakingTests.cpp @@ -56,7 +56,7 @@ namespace TW::LiquidStaking::tests { input.set_protocol(Proto::Strader); Proto::Stake stake; Proto::Asset asset; - asset.set_staking_token(Proto::MATIC); + asset.set_staking_token(Proto::POL); *stake.mutable_asset() = asset; stake.set_amount("1000000000000000000"); *input.mutable_stake() = stake; @@ -80,14 +80,14 @@ namespace TW::LiquidStaking::tests { ASSERT_EQ(ls_output.status().code(), Proto::ERROR_TARGETED_BLOCKCHAIN_NOT_SUPPORTED_BY_PROTOCOL); } - TEST(LiquidStaking, PolygonStraderStakeMatic) { + TEST(LiquidStaking, PolygonStraderStakePol) { Proto::Input input; input.set_blockchain(Proto::POLYGON); input.set_protocol(Proto::Strader); input.set_smart_contract_address("0xfd225c9e6601c9d38d8f98d8731bf59efcf8c0e3"); Proto::Stake stake; Proto::Asset asset; - asset.set_staking_token(Proto::MATIC); + asset.set_staking_token(Proto::POL); *stake.mutable_asset() = asset; stake.set_amount("1000000000000000000"); *input.mutable_stake() = stake; @@ -146,7 +146,7 @@ namespace TW::LiquidStaking::tests { } } - TEST(LiquidStaking, PolygonStraderUnStakeMatic) { + TEST(LiquidStaking, PolygonStraderUnStakePol) { Proto::Input input; input.set_blockchain(Proto::POLYGON); input.set_protocol(Proto::Strader); @@ -184,7 +184,7 @@ namespace TW::LiquidStaking::tests { // Successfully broadcasted https://polygonscan.com/tx/0xa66855e4af8e654e458915f59acd77e88706c01b59a3e4aed1363a665458368a } - TEST(LiquidStaking, PolygonStraderWithdrawMatic) { + TEST(LiquidStaking, PolygonStraderWithdrawPol) { Proto::Input input; input.set_blockchain(Proto::POLYGON); input.set_protocol(Proto::Strader); 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"); +} diff --git a/tools/install-dependencies b/tools/install-dependencies index 3a04f8434bd..3ad3d77a54d 100755 --- a/tools/install-dependencies +++ b/tools/install-dependencies @@ -10,7 +10,10 @@ CMAKE=cmake MAKE=make # Load dependencies version -BASE_DIR=$(cd `dirname $0`; pwd) +BASE_DIR=$( + cd $(dirname $0) + pwd +) source ${BASE_DIR}/dependencies-version # Setup up folders @@ -50,28 +53,28 @@ function build_protobuf() { PROTOBUF_DIR="$ROOT/build/local/src/protobuf" cd ${PROTOBUF_DIR}/protobuf-$PROTOBUF_VERSION - ./configure --prefix="$PREFIX" - $MAKE -j4 - $MAKE install + $CMAKE -Scmake -B . -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_MODULE_COMPATIBLE=ON + $CMAKE --build . -j + $CMAKE --install . --prefix $PREFIX # after install, cleanup to save space (docker) - make clean + $CMAKE --build . --target clean "$PREFIX/bin/protoc" --version # Protobuf plugins cd "$ROOT/protobuf-plugin" - $CMAKE -H. -Bbuild -DCMAKE_INSTALL_PREFIX=$PREFIX - $MAKE -Cbuild -j12 - $MAKE -Cbuild install - rm -rf build + $CMAKE . -Bbuild -DProtobuf_DIR=$PREFIX/lib/cmake/protobuf + $CMAKE --build build -j + $CMAKE --install build --prefix $PREFIX + $CMAKE --build build --target clean - if [[ -x "$(command -v swift)" && `uname` == "Darwin" ]]; then + if [[ -x "$(command -v swift)" && $(uname) == "Darwin" ]]; then build_swift_plugin fi } function build_swift_plugin() { - # Download Swift Protobuf sources + # Download Swift Protobuf sources SWIFT_PROTOBUF_DIR="$ROOT/build/local/src/swift-protobuf" mkdir -p "$SWIFT_PROTOBUF_DIR" cd "$SWIFT_PROTOBUF_DIR" diff --git a/wasm/tests/Blockchain/TheOpenNetwork.test.ts b/wasm/tests/Blockchain/TheOpenNetwork.test.ts index 6da95ff578c..b96cb551675 100644 --- a/wasm/tests/Blockchain/TheOpenNetwork.test.ts +++ b/wasm/tests/Blockchain/TheOpenNetwork.test.ts @@ -71,7 +71,6 @@ describe("TheOpenNetwork", () => { let privateKeyData = HexCoding.decode("c38f49de2fb13223a9e7d37d5d0ffbdd89a5eb7c8b0ee4d1c299f2cefe7dc4a0"); let transfer = TW.TheOpenNetwork.Proto.Transfer.create({ - walletVersion: TW.TheOpenNetwork.Proto.WalletVersion.WALLET_V4_R2, dest: "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q", amount: new Long(10), mode: (TW.TheOpenNetwork.Proto.SendMode.PAY_FEES_SEPARATELY | TW.TheOpenNetwork.Proto.SendMode.IGNORE_ACTION_PHASE_ERRORS), @@ -83,6 +82,7 @@ describe("TheOpenNetwork", () => { privateKey: PrivateKey.createWithData(privateKeyData).data(), sequenceNumber: 6, expireAt: 1671132440, + walletVersion: TW.TheOpenNetwork.Proto.WalletVersion.WALLET_V4_R2, }); const encoded = TW.TheOpenNetwork.Proto.SigningInput.encode(input).finish(); @@ -108,7 +108,6 @@ describe("TheOpenNetwork", () => { }); let transfer = TW.TheOpenNetwork.Proto.Transfer.create({ - walletVersion: TW.TheOpenNetwork.Proto.WalletVersion.WALLET_V4_R2, dest: "EQBiaD8PO1NwfbxSkwbcNT9rXDjqhiIvXWymNO-edV0H5lja", amount: new Long(100 * 1000 * 1000), mode: (TW.TheOpenNetwork.Proto.SendMode.PAY_FEES_SEPARATELY | TW.TheOpenNetwork.Proto.SendMode.IGNORE_ACTION_PHASE_ERRORS), @@ -122,6 +121,7 @@ describe("TheOpenNetwork", () => { privateKey: PrivateKey.createWithData(privateKeyData).data(), sequenceNumber: 1, expireAt: 1787693046, + walletVersion: TW.TheOpenNetwork.Proto.WalletVersion.WALLET_V4_R2, }); const encoded = TW.TheOpenNetwork.Proto.SigningInput.encode(input).finish();