From e34e9ba2ed6e571e2b0531a5cfe1495cea5258f7 Mon Sep 17 00:00:00 2001 From: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Date: Wed, 20 Nov 2024 20:09:52 +0700 Subject: [PATCH] [BCH]: Fix Transaction Planning (#4118) * [BCH]: Fix Transaction Planning * [BCH]: Add BitcoinCash transaction signing V2 test in C++ --- rust/chains/tw_bitcoincash/src/context.rs | 1 + rust/chains/tw_bitcoincash/src/entry.rs | 10 +++ .../chains/bitcoincash/bitcoincash_sign.rs | 13 +++- .../chains/solana/solana_transaction_ffi.rs | 7 +- .../chains/BitcoinCash/TWBitcoinCashTests.cpp | 71 +++++++++++++++++++ 5 files changed, 96 insertions(+), 6 deletions(-) diff --git a/rust/chains/tw_bitcoincash/src/context.rs b/rust/chains/tw_bitcoincash/src/context.rs index e4eb7b2036d..9190ff463e6 100644 --- a/rust/chains/tw_bitcoincash/src/context.rs +++ b/rust/chains/tw_bitcoincash/src/context.rs @@ -7,6 +7,7 @@ use tw_coin_entry::error::prelude::*; use tw_utxo::context::{AddressPrefixes, UtxoContext}; use tw_utxo::script::Script; +#[derive(Default)] pub struct BitcoinCashContext; impl UtxoContext for BitcoinCashContext { diff --git a/rust/chains/tw_bitcoincash/src/entry.rs b/rust/chains/tw_bitcoincash/src/entry.rs index bbe4625aa7d..92dee76a81c 100644 --- a/rust/chains/tw_bitcoincash/src/entry.rs +++ b/rust/chains/tw_bitcoincash/src/entry.rs @@ -92,4 +92,14 @@ impl CoinEntry for BitcoinCashEntry { ) -> Self::SigningOutput { BitcoinCompiler::::compile(coin, input, signatures, public_keys) } + + #[inline] + fn plan_builder(&self) -> Option { + Some(BitcoinPlanner::::default()) + } + + #[inline] + fn transaction_util(&self) -> Option { + Some(BitcoinTransactionUtil) + } } diff --git a/rust/tw_tests/tests/chains/bitcoincash/bitcoincash_sign.rs b/rust/tw_tests/tests/chains/bitcoincash/bitcoincash_sign.rs index 8802ca9963d..0282966d3c6 100644 --- a/rust/tw_tests/tests/chains/bitcoincash/bitcoincash_sign.rs +++ b/rust/tw_tests/tests/chains/bitcoincash/bitcoincash_sign.rs @@ -3,7 +3,7 @@ // Copyright © 2017 Trust Wallet. use crate::chains::bitcoincash::test_cases::transfer_96ee20; -use crate::chains::common::bitcoin::{btc_info, sign, TransactionOneof}; +use crate::chains::common::bitcoin::{btc_info, plan, sign, TransactionOneof}; use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::DecodeHex; use tw_proto::BitcoinV2::Proto; @@ -20,6 +20,16 @@ fn test_bitcoincash_sign_input_p2pkh_from_to_address() { ..Default::default() }; + plan::BitcoinPlanHelper::new(&signing) + .coin(CoinType::BitcoinCash) + .plan(plan::Expected { + inputs: vec![5151], + outputs: vec![600, 4325], + vsize_estimate: 227, + fee_estimate: 226, + change: 0, + }); + // Successfully broadcasted: // https://blockchair.com/bitcoin-cash/transaction/96ee20002b34e468f9d3c5ee54f6a8ddaa61c118889c4f35395c2cd93ba5bbb4 sign::BitcoinSignHelper::new(&signing) @@ -29,7 +39,6 @@ fn test_bitcoincash_sign_input_p2pkh_from_to_address() { txid: transfer_96ee20::TX_ID, inputs: vec![5151], outputs: vec![600, 4325], - // `vsize` is different from the estimated value due to the signatures der serialization. vsize: 226, weight: 904, fee: 226, diff --git a/rust/tw_tests/tests/chains/solana/solana_transaction_ffi.rs b/rust/tw_tests/tests/chains/solana/solana_transaction_ffi.rs index c9bdb0a5e4b..a9a31f80da0 100644 --- a/rust/tw_tests/tests/chains/solana/solana_transaction_ffi.rs +++ b/rust/tw_tests/tests/chains/solana/solana_transaction_ffi.rs @@ -2,18 +2,17 @@ // // Copyright © 2017 Trust Wallet. -use tw_any_coin::test_utils::address_utils::test_address_derive; -use tw_any_coin::test_utils::sign_utils::{AnySignerHelper, PreImageHelper}; +use tw_any_coin::test_utils::sign_utils::AnySignerHelper; use tw_any_coin::test_utils::transaction_decode_utils::TransactionDecoderHelper; use tw_coin_registry::coin_type::CoinType; use tw_encoding::base64::STANDARD; -use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_encoding::hex::DecodeHex; use tw_encoding::{base58, base64}; use tw_memory::test_utils::tw_data_helper::TWDataHelper; use tw_memory::test_utils::tw_data_vector_helper::TWDataVectorHelper; use tw_memory::test_utils::tw_string_helper::TWStringHelper; use tw_proto::Common::Proto::SigningError; -use tw_proto::Solana::Proto::{self, mod_SigningInput::OneOftransaction_type as TransactionType}; +use tw_proto::Solana::Proto::{self}; use tw_solana::SOLANA_ALPHABET; use wallet_core_rs::ffi::solana::transaction::{ tw_solana_transaction_get_compute_unit_limit, tw_solana_transaction_get_compute_unit_price, diff --git a/tests/chains/BitcoinCash/TWBitcoinCashTests.cpp b/tests/chains/BitcoinCash/TWBitcoinCashTests.cpp index 734aa7d541c..dc77f5b5642 100644 --- a/tests/chains/BitcoinCash/TWBitcoinCashTests.cpp +++ b/tests/chains/BitcoinCash/TWBitcoinCashTests.cpp @@ -6,6 +6,7 @@ #include "Bitcoin/SigHashType.h" #include "HexCoding.h" #include "proto/Bitcoin.pb.h" +#include "proto/BitcoinV2.pb.h" #include "TestUtilities.h" #include @@ -161,6 +162,76 @@ TEST(BitcoinCash, SignTransaction) { "e510000000000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" "00000000"); } + +TEST(BitcoinCash, SignTransactionV2) { + auto privateKey = parse_hex("7fdafb9db5bc501f2096e7d13d331dc7a75d9594af3d251313ba8b6200f4e384"); + auto txId = parse_hex("050d00e2e18ef13969606f1ceee290d3f49bd940684ce39898159352952b8ce2"); + std::reverse(txId.begin(), txId.end()); + + BitcoinV2::Proto::SigningInput signing; + signing.add_private_keys(privateKey.data(), privateKey.size()); + signing.mutable_chain_info()->set_p2pkh_prefix(0); + signing.mutable_chain_info()->set_p2sh_prefix(5); + signing.mutable_chain_info()->set_hrp("bitcoincash"); + + auto& builder = *signing.mutable_builder(); + builder.set_version(BitcoinV2::Proto::TransactionVersion::V1); + builder.set_input_selector(BitcoinV2::Proto::InputSelector::UseAll); + builder.set_fixed_dust_threshold(546); + + auto& in = *builder.add_inputs(); + auto& inOutPoint = *in.mutable_out_point(); + inOutPoint.set_hash(txId.data(), txId.size()); + inOutPoint.set_vout(2); + in.set_value(5151); + // Cash address without prefix. + in.set_receiver_address("qzhlrcrcne07x94h99thved2pgzdtv8ccujjy73xya"); + in.set_sighash_type(TWBitcoinSigHashTypeAll | TWBitcoinSigHashTypeFork); + + auto& out0 = *builder.add_outputs(); + out0.set_value(600); + // Legacy address. + out0.set_to_address("1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"); + + auto& explicitChangeOutput = *builder.add_outputs(); + explicitChangeOutput.set_value(4325); + // Cash address with an explicit prefix. + explicitChangeOutput.set_to_address("bitcoincash:qz0q3xmg38sr94rw8wg45vujah7kzma3cskxymnw06"); + + Proto::SigningInput legacy; + *legacy.mutable_signing_v2() = signing; + legacy.set_coin_type(TWCoinTypeBitcoinCash); + + Proto::TransactionPlan plan; + ANY_PLAN(legacy, plan, TWCoinTypeBitcoin); + + ASSERT_EQ(plan.error(), Common::Proto::SigningError::OK); + const auto planV2 = plan.planning_result_v2(); + EXPECT_EQ(planV2.error(), Common::Proto::SigningError::OK) << planV2.error_message(); + + EXPECT_EQ(planV2.inputs_size(), 1); + EXPECT_EQ(planV2.outputs_size(), 2); + EXPECT_EQ(planV2.vsize_estimate(), 227); + EXPECT_EQ(planV2.fee_estimate(), 226); + EXPECT_EQ(planV2.change(), 0); + + Proto::SigningOutput output; + ANY_SIGN(legacy, TWCoinTypeBitcoin); + + EXPECT_EQ(output.error(), Common::Proto::OK); + ASSERT_TRUE(output.has_signing_result_v2()); + const auto outputV2 = output.signing_result_v2(); + EXPECT_EQ(outputV2.error(), Common::Proto::SigningError::OK) << outputV2.error_message(); + ASSERT_EQ(hex(outputV2.encoded()), + "01000000" + "01" + "e28c2b955293159898e34c6840d99bf4d390e2ee1c6f606939f18ee1e2000d05" "02000000" "6b483045022100b70d158b43cbcded60e6977e93f9a84966bc0cec6f2dfd1463d1223a90563f0d02207548d081069de570a494d0967ba388ff02641d91cadb060587ead95a98d4e3534121038eab72ec78e639d02758e7860cdec018b49498c307791f785aa3019622f4ea5b" "ffffffff" + "02" + "5802000000000000" "1976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ac" + "e510000000000000" "1976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac" + "00000000"); +} + // clang-format on } // namespace TW::Bitcoin::tests