From f510b1d0d90f5c25ef993dda578fefb1ee2f933f Mon Sep 17 00:00:00 2001 From: 10gic <2391796+10gic@users.noreply.github.com> Date: Tue, 21 Jan 2025 15:40:07 +0800 Subject: [PATCH] Support calculating the TX hash of a Substrate transaction --- rust/frameworks/tw_substrate/Cargo.toml | 1 + rust/frameworks/tw_substrate/src/entry.rs | 9 +++++-- rust/frameworks/tw_substrate/src/lib.rs | 2 ++ .../tw_substrate/src/modules/mod.rs | 5 ++++ .../src/modules/transaction_util.rs | 27 +++++++++++++++++++ ...nscationUtil.cpp => TWTransactionUtil.cpp} | 0 tests/chains/Acala/TWAnySignerTests.cpp | 8 +++--- tests/interface/TWTransactionUtilTests.cpp | 16 +++++++++++ 8 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 rust/frameworks/tw_substrate/src/modules/mod.rs create mode 100644 rust/frameworks/tw_substrate/src/modules/transaction_util.rs rename src/interface/{TWTranscationUtil.cpp => TWTransactionUtil.cpp} (100%) diff --git a/rust/frameworks/tw_substrate/Cargo.toml b/rust/frameworks/tw_substrate/Cargo.toml index ada930cd707..a0fd5489f5f 100644 --- a/rust/frameworks/tw_substrate/Cargo.toml +++ b/rust/frameworks/tw_substrate/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] tw_coin_entry = { path = "../../tw_coin_entry" } +tw_encoding = { path = "../../tw_encoding" } tw_hash = { path = "../../tw_hash" } tw_keypair = { path = "../../tw_keypair" } tw_memory = { path = "../../tw_memory" } diff --git a/rust/frameworks/tw_substrate/src/entry.rs b/rust/frameworks/tw_substrate/src/entry.rs index 8f930110fd3..fdbfdaabcbf 100644 --- a/rust/frameworks/tw_substrate/src/entry.rs +++ b/rust/frameworks/tw_substrate/src/entry.rs @@ -3,6 +3,7 @@ // Copyright © 2017 Trust Wallet. use crate::address::{SubstrateAddress, SubstratePrefix}; +use crate::modules::transaction_util::SubstrateTransactionUtil; use crate::substrate_coin_entry::SubstrateCoinEntry; use std::str::FromStr; use tw_coin_entry::coin_context::CoinContext; @@ -14,7 +15,6 @@ 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_keypair::{ed25519, traits::KeyPairTrait, tw::PublicKey}; use tw_scale::RawOwned; @@ -83,7 +83,7 @@ impl CoinEntry for SubstrateEntry { type MessageSigner = NoMessageSigner; type WalletConnector = NoWalletConnector; type TransactionDecoder = NoTransactionDecoder; - type TransactionUtil = NoTransactionUtil; + type TransactionUtil = SubstrateTransactionUtil; #[inline] fn parse_address( @@ -150,4 +150,9 @@ impl CoinEntry for SubstrateEntry { let res = self.compile_impl(coin, input, signatures, public_keys); self.0.signing_output(coin, res) } + + #[inline] + fn transaction_util(&self) -> Option { + Some(SubstrateTransactionUtil) + } } diff --git a/rust/frameworks/tw_substrate/src/lib.rs b/rust/frameworks/tw_substrate/src/lib.rs index 0412862eb82..30e1eae4b22 100644 --- a/rust/frameworks/tw_substrate/src/lib.rs +++ b/rust/frameworks/tw_substrate/src/lib.rs @@ -16,6 +16,8 @@ pub use extrinsic::*; pub mod extensions; pub use extensions::*; +pub mod modules; + #[derive(Debug, Clone, Eq, PartialEq)] pub enum EncodeError { InvalidNetworkId, diff --git a/rust/frameworks/tw_substrate/src/modules/mod.rs b/rust/frameworks/tw_substrate/src/modules/mod.rs new file mode 100644 index 00000000000..c083bb0102e --- /dev/null +++ b/rust/frameworks/tw_substrate/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/frameworks/tw_substrate/src/modules/transaction_util.rs b/rust/frameworks/tw_substrate/src/modules/transaction_util.rs new file mode 100644 index 00000000000..dc814d66994 --- /dev/null +++ b/rust/frameworks/tw_substrate/src/modules/transaction_util.rs @@ -0,0 +1,27 @@ +// 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::blake2::blake2_b; + +pub struct SubstrateTransactionUtil; + +impl TransactionUtil for SubstrateTransactionUtil { + fn calc_tx_hash(&self, coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + Self::calc_tx_hash_impl(coin, encoded_tx) + } +} + +impl SubstrateTransactionUtil { + fn calc_tx_hash_impl(_coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + let tx_bytes = hex::decode(encoded_tx).map_err(|_| SigningErrorType::Error_input_parse)?; + + let tx_hash = blake2_b(&tx_bytes, 32).map_err(|_| SigningErrorType::Error_internal)?; + + Ok(hex::encode(&tx_hash, true)) + } +} diff --git a/src/interface/TWTranscationUtil.cpp b/src/interface/TWTransactionUtil.cpp similarity index 100% rename from src/interface/TWTranscationUtil.cpp rename to src/interface/TWTransactionUtil.cpp diff --git a/tests/chains/Acala/TWAnySignerTests.cpp b/tests/chains/Acala/TWAnySignerTests.cpp index 8bce503bce8..edd69951838 100644 --- a/tests/chains/Acala/TWAnySignerTests.cpp +++ b/tests/chains/Acala/TWAnySignerTests.cpp @@ -16,7 +16,7 @@ namespace TW::Polkadot::tests { TEST(TWAnySignerAcala, Sign) { - // Successfully broadcasted: https://acala.subscan.io/extrinsic/3893620-3 + // Successfully broadcasted: https://acala.subscan.io/extrinsic/0xb2450990defef55f075f41969c8ae7965ddf8446a0aae9510b8bbdbacb4ff344 const auto coin = TWCoinTypePolkadot; auto secret = parse_hex("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0"); @@ -28,7 +28,7 @@ TEST(TWAnySignerAcala, Sign) { input.set_block_hash(blockHash.data(), blockHash.size()); input.set_nonce(0); - input.set_spec_version(2270); + input.set_spec_version(2170); // New transactions should use a newer specification version, such as 2270. input.set_private_key(secret.data(), secret.size()); input.set_network(10); // Acala input.set_transaction_version(2); @@ -59,12 +59,12 @@ TEST(TWAnySignerAcala, Sign) { const auto preImageHashData = data(preSigningOutput.data_hash()); - EXPECT_EQ(hex(preImageHashData), "0a0000c8c602ded977c56076ae38d98026fa669ca10d6a2b5a0bfc4086ae7668ed1c60070010a5d4e8d502000000de08000002000000fc41b9bd8ef8fe53d58c7ea67c794c7ec9a73daf05e6d54b14ff6342c99ba64c707ffa05b7dc6cdb6356bd8bd51ff20b2757c3214a76277516080a10f1bc753700"); + EXPECT_EQ(hex(preImageHashData), "0a0000c8c602ded977c56076ae38d98026fa669ca10d6a2b5a0bfc4086ae7668ed1c60070010a5d4e8d50200007a08000002000000fc41b9bd8ef8fe53d58c7ea67c794c7ec9a73daf05e6d54b14ff6342c99ba64c707ffa05b7dc6cdb6356bd8bd51ff20b2757c3214a76277516080a10f1bc7537"); Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypePolkadot); - EXPECT_EQ(hex(output.encoded()), "45028400e9590e4d99264a14a85e21e69537e4a64f66a875d38cb8f76b305f41fabe24a900a9c3111fb98507f929e4da9aea30f996c69d2790e5a1e789f91634dc5d4f6afb155e0f1ea623498c04778f06dbc698109c3490c3e6b4c33d8e58ebab82a0f40bd5020000000a0000c8c602ded977c56076ae38d98026fa669ca10d6a2b5a0bfc4086ae7668ed1c60070010a5d4e8"); + EXPECT_EQ(hex(output.encoded()), "41028400e9590e4d99264a14a85e21e69537e4a64f66a875d38cb8f76b305f41fabe24a900dd54466dffd1e3c80b76013e9459fbdcd17805bd5fdbca0961a643bad1cbd2b7fe005c62c51c18b67f31eb9e61b187a911952fee172ef18402d07c703eec3100d50200000a0000c8c602ded977c56076ae38d98026fa669ca10d6a2b5a0bfc4086ae7668ed1c60070010a5d4e8"); } } // namespace TW::Polkadot::tests diff --git a/tests/interface/TWTransactionUtilTests.cpp b/tests/interface/TWTransactionUtilTests.cpp index c6b59420e6b..20ffbbdd6bf 100644 --- a/tests/interface/TWTransactionUtilTests.cpp +++ b/tests/interface/TWTransactionUtilTests.cpp @@ -71,3 +71,19 @@ TEST(TWTransactionUtil, CalcTxHashSui) { assertStringsEqual(txHashResult, "HkPo6rYPyDY53x1MBszvSZVZyixVN7CHvCJGX381czAh"); } + +TEST(TWTransactionUtil, CalcTxHashPolkadot) { + constexpr auto coin = TWCoinTypePolkadot; + const auto encodedTxPtr = WRAPS(TWStringCreateWithUTF8Bytes("3d02849dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f783009025843bc49c1c4fbc99dbbd290c92f9879665d55b02f110abfb4800f0e7630877d2cffd853deae7466c22fbc8616a609e1b92615bb365ea8adccba5ef7624050503000007009dca538b7a925b8ea979cc546464a3c5f81d2398a3a272f6f93bdf4803f2f7830700aea68f0201")); + const auto txHashResult = WRAPS(TWTransactionUtilCalcTxHash(coin, encodedTxPtr.get())); + + assertStringsEqual(txHashResult, "0x8da66d3fe0f592cff714ec107289370365117a1abdb72a19ac91181fdcf62bba"); +} + +TEST(TWTransactionUtil, CalcTxHashAcala) { + constexpr auto coin = TWCoinTypeAcala; + const auto encodedTxPtr = WRAPS(TWStringCreateWithUTF8Bytes("41028400e9590e4d99264a14a85e21e69537e4a64f66a875d38cb8f76b305f41fabe24a900dd54466dffd1e3c80b76013e9459fbdcd17805bd5fdbca0961a643bad1cbd2b7fe005c62c51c18b67f31eb9e61b187a911952fee172ef18402d07c703eec3100d50200000a0000c8c602ded977c56076ae38d98026fa669ca10d6a2b5a0bfc4086ae7668ed1c60070010a5d4e8")); + const auto txHashResult = WRAPS(TWTransactionUtilCalcTxHash(coin, encodedTxPtr.get())); + + assertStringsEqual(txHashResult, "0xb2450990defef55f075f41969c8ae7965ddf8446a0aae9510b8bbdbacb4ff344"); +}