From 4af0ee33be559941fbda7d3519a9dd032006ab52 Mon Sep 17 00:00:00 2001 From: satoshiotomakan <127754187+satoshiotomakan@users.noreply.github.com> Date: Thu, 26 Sep 2024 01:26:57 +0700 Subject: [PATCH] [Bitcoin]: Refactor PSBT protocol (#4038) * feat(btc): Refactor BitcoinV2.proto * Move PSBT input to `BitcoinV2.SigningInput.transaction.psbt` * Add `BitcoinV2.TransactionBuilder` * feat(btc): Refactor Signer, Planner and Compiler to the refactored Protobuf * Remove `tw_bitcoin_psbt_sign` and `tw_bitcoin_psbt_plan` * feat(btc): Adopt integration tests * feat(btc): Remove `TWBitcoinPsbt` module * Adopt C++ integration tests * feat(btc): Adopt iOS tests * feat(btc): Adopt Android tests * feat(btc): Adopt WASM tests --- .../blockchains/bitcoin/TestBitcoinPsbt.kt | 51 +++++---- .../blockchains/bitcoin/TestBitcoinSigning.kt | 24 ++-- include/TrustWalletCore/TWBitcoinPsbt.h | 36 ------ rust/chains/tw_bitcoin/src/entry.rs | 26 ----- .../chains/tw_bitcoin/src/modules/compiler.rs | 58 +++++++++- rust/chains/tw_bitcoin/src/modules/mod.rs | 1 - .../modules/{planner.rs => planner/mod.rs} | 25 +++- .../src/modules/{ => planner}/psbt_planner.rs | 20 +--- .../src/modules/psbt_request/mod.rs | 24 +--- rust/chains/tw_bitcoin/src/modules/signer.rs | 48 ++++---- .../src/modules/signing_request/mod.rs | 25 ++-- rust/frameworks/tw_utxo/src/lib.rs | 1 - .../src/transaction/unsigned_transaction.rs | 9 ++ rust/frameworks/tw_utxo/src/utxo_entry.rs | 45 -------- rust/tw_coin_registry/src/dispatcher.rs | 10 -- .../chains/bitcoin/bitcoin_compile/brc20.rs | 24 ++-- .../bitcoin/bitcoin_compile/compile_error.rs | 14 ++- .../chains/bitcoin/bitcoin_compile/p2pkh.rs | 15 ++- .../chains/bitcoin/bitcoin_plan/plan_psbt.rs | 10 +- .../chains/bitcoin/bitcoin_sign/brc20.rs | 36 ++++-- .../chains/bitcoin/bitcoin_sign/op_return.rs | 13 ++- .../chains/bitcoin/bitcoin_sign/p2pkh.rs | 26 +++-- .../tests/chains/bitcoin/bitcoin_sign/p2sh.rs | 15 ++- .../bitcoin/bitcoin_sign/p2tr_key_path.rs | 65 +++++++---- .../bitcoin/bitcoin_sign/p2tr_script_path.rs | 13 ++- .../chains/bitcoin/bitcoin_sign/p2wpkh.rs | 13 ++- .../chains/bitcoin/bitcoin_sign/p2wsh.rs | 15 ++- .../tests/chains/bitcoin/bitcoin_sign/psbt.rs | 17 +-- .../bitcoin/bitcoin_sign/send_to_address.rs | 47 ++++++-- .../bitcoin/bitcoin_sign/sighash_single.rs | 19 ++-- .../tests/chains/common/bitcoin/mod.rs | 13 ++- .../tests/chains/common/bitcoin/plan.rs | 9 +- .../tests/chains/common/bitcoin/psbt_plan.rs | 20 +--- .../tests/chains/common/bitcoin/psbt_sign.rs | 22 ++-- .../tests/chains/common/bitcoin/sign.rs | 16 ++- rust/wallet_core_rs/src/ffi/bitcoin/mod.rs | 2 - rust/wallet_core_rs/src/ffi/bitcoin/psbt.rs | 48 -------- src/Bitcoin/Psbt.cpp | 22 ---- src/Bitcoin/Psbt.h | 22 ---- src/interface/TWBitcoinPsbt.cpp | 20 ---- src/proto/BitcoinV2.proto | 98 +++++++--------- swift/Tests/Blockchains/BitcoinTests.swift | 107 ++++++++++-------- tests/chains/Bitcoin/TWBitcoinPsbtTests.cpp | 56 +++++---- .../chains/Bitcoin/TWBitcoinSigningTests.cpp | 14 ++- .../Bitcoin/TransactionCompilerTests.cpp | 19 ++-- wasm/tests/Blockchain/Bitcoin.test.ts | 52 +++++---- 46 files changed, 637 insertions(+), 648 deletions(-) delete mode 100644 include/TrustWalletCore/TWBitcoinPsbt.h rename rust/chains/tw_bitcoin/src/modules/{planner.rs => planner/mod.rs} (77%) rename rust/chains/tw_bitcoin/src/modules/{ => planner}/psbt_planner.rs (85%) delete mode 100644 rust/frameworks/tw_utxo/src/utxo_entry.rs delete mode 100644 rust/wallet_core_rs/src/ffi/bitcoin/psbt.rs delete mode 100644 src/Bitcoin/Psbt.cpp delete mode 100644 src/Bitcoin/Psbt.h delete mode 100644 src/interface/TWBitcoinPsbt.cpp diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinPsbt.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinPsbt.kt index 3f8a083b7de..884120800ca 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinPsbt.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinPsbt.kt @@ -7,7 +7,7 @@ import com.trustwallet.core.app.utils.toHexBytes import com.trustwallet.core.app.utils.toHexBytesInByteString import org.junit.Assert.assertEquals import org.junit.Test -import wallet.core.jni.BitcoinPsbt +import wallet.core.java.AnySigner import wallet.core.jni.BitcoinScript import wallet.core.jni.BitcoinSigHashType import wallet.core.jni.CoinType @@ -17,7 +17,6 @@ import wallet.core.jni.PrivateKey import wallet.core.jni.PublicKey import wallet.core.jni.PublicKeyType import wallet.core.jni.proto.Bitcoin -import wallet.core.jni.proto.Bitcoin.SigningOutput import wallet.core.jni.proto.BitcoinV2 import wallet.core.jni.proto.Common.SigningError @@ -34,17 +33,21 @@ class TestBitcoinPsbt { val privateKey = "f00ffbe44c5c2838c13d2778854ac66b75e04eb6054f0241989e223223ad5e55".toHexBytesInByteString() val psbt = "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d00000000".toHexBytesInByteString() - val input = BitcoinV2.PsbtSigningInput.newBuilder() - .setPsbt(psbt) + val input = BitcoinV2.SigningInput.newBuilder() + .setPsbt(BitcoinV2.Psbt.newBuilder().setPsbt(psbt)) .addPrivateKeys(privateKey) .build() - val outputData = BitcoinPsbt.sign(input.toByteArray(), BITCOIN) - val output = BitcoinV2.PsbtSigningOutput.parseFrom(outputData) + val legacyInput = Bitcoin.SigningInput.newBuilder() + .setSigningV2(input) + .build() + + val legacyOutput = AnySigner.sign(legacyInput, BITCOIN, Bitcoin.SigningOutput.parser()) + val output = legacyOutput.signingResultV2 assertEquals(output.error, SigningError.OK) assertEquals( - output.psbt.toByteArray().toHex(), + output.psbt.psbt.toByteArray().toHex(), "0x70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d01086c02483045022100b1229a008f20691639767bf925d6b8956ea957ccc633ad6b5de3618733a55e6b02205774d3320489b8a57a6f8de07f561de3e660ff8e587f6ac5422c49020cd4dc9101210306d8c664ea8fd2683eebea1d3114d90e0a5429e5783ba49b80ddabce04ff28f300000000" ) assertEquals( @@ -65,37 +68,41 @@ class TestBitcoinPsbt { val publicKey = privateKey.getPublicKeySecp256k1(true) val psbt = "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d00000000".toHexBytesInByteString() - val input = BitcoinV2.PsbtSigningInput.newBuilder() - .setPsbt(psbt) + val input = BitcoinV2.SigningInput.newBuilder() + .setPsbt(BitcoinV2.Psbt.newBuilder().setPsbt(psbt)) .addPublicKeys(ByteString.copyFrom(publicKey.data())) .build() - val outputData = BitcoinPsbt.plan(input.toByteArray(), BITCOIN) - val output = BitcoinV2.TransactionPlan.parseFrom(outputData) + val legacyInput = Bitcoin.SigningInput.newBuilder() + .setSigningV2(input) + .build() - assertEquals(output.error, SigningError.OK) + val legacyPlan = AnySigner.plan(legacyInput, BITCOIN, Bitcoin.TransactionPlan.parser()) + val plan = legacyPlan.planningResultV2 + + assertEquals(plan.error, SigningError.OK) - assertEquals(output.getInputs(0).receiverAddress, "bc1qkyu3n8k8jmekl3pwvdl59k5w8enjp25akz2r3z") - assertEquals(output.getInputs(0).value, 66_406) + assertEquals(plan.getInputs(0).receiverAddress, "bc1qkyu3n8k8jmekl3pwvdl59k5w8enjp25akz2r3z") + assertEquals(plan.getInputs(0).value, 66_406) // Vault transfer - assertEquals(output.getOutputs(0).toAddress, "bc1q7g48qdshqd000aysws74pun2uzxrp598gcfum0") - assertEquals(output.getOutputs(0).value, 60_000) + assertEquals(plan.getOutputs(0).toAddress, "bc1q7g48qdshqd000aysws74pun2uzxrp598gcfum0") + assertEquals(plan.getOutputs(0).value, 60_000) // OP_RETURN assertEquals( - output.getOutputs(1).customScriptPubkey.toByteArray().toHex(), + plan.getOutputs(1).customScriptPubkey.toByteArray().toHex(), "0x6a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a3530" ) - assertEquals(output.getOutputs(1).value, 0) + assertEquals(plan.getOutputs(1).value, 0) // Change output - assertEquals(output.getOutputs(2).toAddress, "bc1qkyu3n8k8jmekl3pwvdl59k5w8enjp25akz2r3z") - assertEquals(output.getOutputs(2).value, 4_670) + assertEquals(plan.getOutputs(2).toAddress, "bc1qkyu3n8k8jmekl3pwvdl59k5w8enjp25akz2r3z") + assertEquals(plan.getOutputs(2).value, 4_670) - assertEquals(output.feeEstimate, 1736) + assertEquals(plan.feeEstimate, 1736) // Please note that `change` in PSBT planning is always 0. // That's because we aren't able to determine which output is an actual change from PSBT. - assertEquals(output.change, 0) + assertEquals(plan.change, 0) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt index 9d6bcdab802..abd187f8150 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt @@ -198,19 +198,21 @@ class TestBitcoinSigning { p2Wpkh = BitcoinV2.PublicKeyOrHash.newBuilder().setPubkey(publicKey).build() }) - val signingInput = BitcoinV2.SigningInput.newBuilder() + val builder = BitcoinV2.TransactionBuilder.newBuilder() .setVersion(BitcoinV2.TransactionVersion.V2) - .addPrivateKeys(ByteString.copyFrom(privateKeyData)) .addInputs(utxo0) .addOutputs(out0) .addOutputs(changeOutput) .setInputSelector(BitcoinV2.InputSelector.UseAll) + .setFixedDustThreshold(dustSatoshis) + val signingInput = BitcoinV2.SigningInput.newBuilder() + .setBuilder(builder) + .addPrivateKeys(ByteString.copyFrom(privateKeyData)) .setChainInfo(BitcoinV2.ChainInfo.newBuilder().apply { p2PkhPrefix = 0 p2ShPrefix = 5 }) .setDangerousUseFixedSchnorrRng(true) - .setFixedDustThreshold(dustSatoshis) .build() val legacySigningInput = Bitcoin.SigningInput.newBuilder().apply { @@ -256,18 +258,20 @@ class TestBitcoinSigning { p2Wpkh = BitcoinV2.PublicKeyOrHash.newBuilder().setPubkey(publicKey).build() }) - val signingInput = BitcoinV2.SigningInput.newBuilder() + val builder = BitcoinV2.TransactionBuilder.newBuilder() .setVersion(BitcoinV2.TransactionVersion.V2) - .addPrivateKeys(ByteString.copyFrom(privateKeyData)) .addInputs(utxo0) .addOutputs(out0) .setInputSelector(BitcoinV2.InputSelector.UseAll) + .setFixedDustThreshold(dustSatoshis) + val signingInput = BitcoinV2.SigningInput.newBuilder() + .setBuilder(builder) + .addPrivateKeys(ByteString.copyFrom(privateKeyData)) .setChainInfo(BitcoinV2.ChainInfo.newBuilder().apply { p2PkhPrefix = 0 p2ShPrefix = 5 }) .setDangerousUseFixedSchnorrRng(true) - .setFixedDustThreshold(dustSatoshis) .build() val legacySigningInput = Bitcoin.SigningInput.newBuilder().apply { @@ -326,20 +330,22 @@ class TestBitcoinSigning { p2Wpkh = BitcoinV2.PublicKeyOrHash.newBuilder().setPubkey(publicKey).build() }) - val signingInput = BitcoinV2.SigningInput.newBuilder() + val builder = BitcoinV2.TransactionBuilder.newBuilder() .setVersion(BitcoinV2.TransactionVersion.V2) - .addPrivateKeys(ByteString.copyFrom(privateKeyData)) .addInputs(utxo0) .addInputs(utxo1) .addOutputs(out0) .addOutputs(changeOutput) .setInputSelector(BitcoinV2.InputSelector.UseAll) + .setFixedDustThreshold(dustSatoshis) + val signingInput = BitcoinV2.SigningInput.newBuilder() + .setBuilder(builder) + .addPrivateKeys(ByteString.copyFrom(privateKeyData)) .setChainInfo(BitcoinV2.ChainInfo.newBuilder().apply { p2PkhPrefix = 0 p2ShPrefix = 5 }) .setDangerousUseFixedSchnorrRng(true) - .setFixedDustThreshold(dustSatoshis) .build() val legacySigningInput = Bitcoin.SigningInput.newBuilder().apply { diff --git a/include/TrustWalletCore/TWBitcoinPsbt.h b/include/TrustWalletCore/TWBitcoinPsbt.h deleted file mode 100644 index 5860e3332b7..00000000000 --- a/include/TrustWalletCore/TWBitcoinPsbt.h +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#pragma once - -#include "TWBase.h" -#include "TWBitcoinSigHashType.h" -#include "TWCoinType.h" -#include "TWData.h" -#include "TWPublicKey.h" - -TW_EXTERN_C_BEGIN - -/// Represents a signer to sign/plan PSBT for Bitcoin blockchains. -TW_EXPORT_CLASS -struct TWBitcoinPsbt; - -/// Signs a PSBT (Partially Signed Bitcoin Transaction) specified by the signing input and coin type. -/// -/// \param input The serialized data of a signing input (e.g. `TW.BitcoinV2.Proto.PsbtSigningInput`) -/// \param coin The given coin type to sign the PSBT for. -/// \return The serialized data of a `Proto.PsbtSigningOutput` proto object (e.g. `TW.BitcoinV2.Proto.PsbtSigningOutput`). -TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWBitcoinPsbtSign(TWData* _Nonnull input, enum TWCoinType coin); - -/// Plans a PSBT (Partially Signed Bitcoin Transaction). -/// Can be used to get the transaction detailed decoded from PSBT. -/// -/// \param input The serialized data of a signing input (e.g. `TW.BitcoinV2.Proto.PsbtSigningInput`) -/// \param coin The given coin type to sign the PSBT for. -/// \return The serialized data of a `Proto.TransactionPlan` proto object (e.g. `TW.BitcoinV2.Proto.TransactionPlan`). -TW_EXPORT_STATIC_METHOD -TWData* _Nonnull TWBitcoinPsbtPlan(TWData* _Nonnull input, enum TWCoinType coin); - -TW_EXTERN_C_END diff --git a/rust/chains/tw_bitcoin/src/entry.rs b/rust/chains/tw_bitcoin/src/entry.rs index 67d597814b4..5d6b81508ec 100644 --- a/rust/chains/tw_bitcoin/src/entry.rs +++ b/rust/chains/tw_bitcoin/src/entry.rs @@ -1,6 +1,5 @@ use crate::modules::compiler::BitcoinCompiler; use crate::modules::planner::BitcoinPlanner; -use crate::modules::psbt_planner::PsbtPlanner; use crate::modules::signer::BitcoinSigner; use crate::modules::transaction_util::BitcoinTransactionUtil; use std::str::FromStr; @@ -15,7 +14,6 @@ use tw_coin_entry::modules::wallet_connector::NoWalletConnector; use tw_keypair::tw::PublicKey; use tw_proto::BitcoinV2::Proto; use tw_utxo::address::standard_bitcoin::{StandardBitcoinAddress, StandardBitcoinPrefix}; -use tw_utxo::utxo_entry::UtxoEntry; pub struct BitcoinEntry; @@ -99,27 +97,3 @@ impl CoinEntry for BitcoinEntry { Some(BitcoinTransactionUtil) } } - -impl UtxoEntry for BitcoinEntry { - type PsbtSigningInput<'a> = Proto::PsbtSigningInput<'a>; - type PsbtSigningOutput = Proto::PsbtSigningOutput<'static>; - type PsbtTransactionPlan = Proto::TransactionPlan<'static>; - - #[inline] - fn sign_psbt( - &self, - coin: &dyn CoinContext, - input: Self::PsbtSigningInput<'_>, - ) -> Self::PsbtSigningOutput { - BitcoinSigner::sign_psbt(coin, &input) - } - - #[inline] - fn plan_psbt( - &self, - coin: &dyn CoinContext, - input: Self::PsbtSigningInput<'_>, - ) -> Self::PsbtTransactionPlan { - PsbtPlanner::plan_psbt(coin, &input) - } -} diff --git a/rust/chains/tw_bitcoin/src/modules/compiler.rs b/rust/chains/tw_bitcoin/src/modules/compiler.rs index ca4a4e745b8..398940ec3e7 100644 --- a/rust/chains/tw_bitcoin/src/modules/compiler.rs +++ b/rust/chains/tw_bitcoin/src/modules/compiler.rs @@ -3,6 +3,7 @@ // Copyright © 2017 Trust Wallet. use crate::modules::protobuf_builder::ProtobufBuilder; +use crate::modules::psbt_request::PsbtRequest; use crate::modules::signing_request::SigningRequestBuilder; use std::borrow::Cow; use tw_coin_entry::coin_context::CoinContext; @@ -13,6 +14,7 @@ use tw_proto::BitcoinV2::Proto; use tw_proto::BitcoinV2::Proto::mod_PreSigningOutput::{ SigningMethod as ProtoSigningMethod, TaprootTweak as ProtoTaprootTweak, }; +use tw_proto::BitcoinV2::Proto::mod_SigningInput::OneOftransaction as TransactionType; use tw_utxo::modules::sighash_computer::{SighashComputer, TaprootTweak, TxPreimage}; use tw_utxo::modules::sighash_verifier::SighashVerifier; use tw_utxo::modules::tx_compiler::TxCompiler; @@ -39,8 +41,17 @@ impl BitcoinCompiler { coin: &dyn CoinContext, input: Proto::SigningInput<'_>, ) -> SigningResult> { - let request = SigningRequestBuilder::build(coin, &input)?; - let SelectResult { unsigned_tx, .. } = TxPlanner::plan(request)?; + let unsigned_tx = match input.transaction { + TransactionType::builder(ref tx_builder) => { + let request = SigningRequestBuilder::build(coin, &input, tx_builder)?; + TxPlanner::plan(request)?.unsigned_tx + }, + TransactionType::psbt(ref psbt) => PsbtRequest::build(&input, psbt)?.unsigned_tx, + TransactionType::None => { + return SigningError::err(SigningErrorType::Error_invalid_params) + .context("Either `TransactionBuilder` or `Psbt` should be set") + }, + }; let TxPreimage { sighashes } = SighashComputer::preimage_tx(&unsigned_tx)?; @@ -77,7 +88,23 @@ impl BitcoinCompiler { signatures: Vec, _public_keys: Vec, ) -> SigningResult> { - let request = SigningRequestBuilder::build(coin, &input)?; + match input.transaction { + TransactionType::builder(ref tx) => { + Self::compile_with_tx_builder(coin, &input, tx, signatures) + }, + TransactionType::psbt(ref psbt) => Self::compile_psbt(coin, &input, psbt, signatures), + TransactionType::None => SigningError::err(SigningErrorType::Error_invalid_params) + .context("No transaction type specified"), + } + } + + fn compile_with_tx_builder( + coin: &dyn CoinContext, + input: &Proto::SigningInput, + tx_builder_input: &Proto::TransactionBuilder, + signatures: Vec, + ) -> SigningResult> { + let request = SigningRequestBuilder::build(coin, input, tx_builder_input)?; let SelectResult { unsigned_tx, plan } = TxPlanner::plan(request)?; SighashVerifier::verify_signatures(&unsigned_tx, &signatures)?; @@ -96,6 +123,31 @@ impl BitcoinCompiler { ..Proto::SigningOutput::default() }) } + + fn compile_psbt( + _coin: &dyn CoinContext, + input: &Proto::SigningInput, + psbt: &Proto::Psbt, + signatures: Vec, + ) -> SigningResult> { + let PsbtRequest { unsigned_tx, .. } = PsbtRequest::build(input, psbt)?; + let fee = unsigned_tx.fee()?; + + SighashVerifier::verify_signatures(&unsigned_tx, &signatures)?; + let signed_tx = TxCompiler::compile(unsigned_tx, &signatures)?; + let tx_proto = ProtobufBuilder::tx_to_proto(&signed_tx); + + Ok(Proto::SigningOutput { + transaction: Some(tx_proto), + encoded: Cow::from(signed_tx.encode_out()), + txid: Cow::from(signed_tx.txid()), + // `vsize` could have been changed after the transaction being signed. + vsize: signed_tx.vsize() as u64, + weight: signed_tx.weight() as u64, + fee, + ..Proto::SigningOutput::default() + }) + } } pub fn signing_method(s: SigningMethod) -> ProtoSigningMethod { diff --git a/rust/chains/tw_bitcoin/src/modules/mod.rs b/rust/chains/tw_bitcoin/src/modules/mod.rs index d275e69e9de..fc470aafecb 100644 --- a/rust/chains/tw_bitcoin/src/modules/mod.rs +++ b/rust/chains/tw_bitcoin/src/modules/mod.rs @@ -6,7 +6,6 @@ pub mod compiler; pub mod planner; pub mod protobuf_builder; pub mod psbt; -pub mod psbt_planner; pub mod psbt_request; pub mod signer; pub mod signing_request; diff --git a/rust/chains/tw_bitcoin/src/modules/planner.rs b/rust/chains/tw_bitcoin/src/modules/planner/mod.rs similarity index 77% rename from rust/chains/tw_bitcoin/src/modules/planner.rs rename to rust/chains/tw_bitcoin/src/modules/planner/mod.rs index a109ccd958f..21d437f8149 100644 --- a/rust/chains/tw_bitcoin/src/modules/planner.rs +++ b/rust/chains/tw_bitcoin/src/modules/planner/mod.rs @@ -14,6 +14,8 @@ use tw_proto::BitcoinV2::Proto; use tw_utxo::modules::tx_planner::TxPlanner; use tw_utxo::modules::utxo_selector::SelectResult; +pub mod psbt_planner; + pub struct BitcoinPlanner; impl BitcoinPlanner { @@ -21,13 +23,30 @@ impl BitcoinPlanner { coin: &dyn CoinContext, input: &Proto::SigningInput<'a>, ) -> SigningResult> { - let request = SigningRequestBuilder::build(coin, input)?; + use Proto::mod_SigningInput::OneOftransaction as TransactionType; + + match input.transaction { + TransactionType::builder(ref tx) => Self::plan_with_tx_builder(coin, input, tx), + TransactionType::psbt(ref psbt) => { + psbt_planner::PsbtPlanner::plan_psbt(coin, input, psbt) + }, + TransactionType::None => SigningError::err(SigningErrorType::Error_invalid_params) + .context("Either `TransactionBuilder` or `Psbt` should be set"), + } + } + + pub fn plan_with_tx_builder<'a>( + coin: &dyn CoinContext, + input: &Proto::SigningInput<'a>, + tx_builder: &Proto::TransactionBuilder<'a>, + ) -> SigningResult> { + let request = SigningRequestBuilder::build(coin, input, tx_builder)?; let SelectResult { unsigned_tx, plan } = TxPlanner::plan(request)?; // Prepare a map of source Inputs Proto `{ OutPoint -> Input }`. // It will be used to find a Input Proto by its `OutPoint`. - let mut inputs_map = HashMap::with_capacity(input.inputs.len()); - for utxo in input.inputs.iter() { + let mut inputs_map = HashMap::with_capacity(tx_builder.inputs.len()); + for utxo in tx_builder.inputs.iter() { let key = parse_out_point(&utxo.out_point)?; if inputs_map.insert(key, utxo).is_some() { // Found a duplicate UTXO. Return an error. diff --git a/rust/chains/tw_bitcoin/src/modules/psbt_planner.rs b/rust/chains/tw_bitcoin/src/modules/planner/psbt_planner.rs similarity index 85% rename from rust/chains/tw_bitcoin/src/modules/psbt_planner.rs rename to rust/chains/tw_bitcoin/src/modules/planner/psbt_planner.rs index a57945daeef..0967e85b75a 100644 --- a/rust/chains/tw_bitcoin/src/modules/psbt_planner.rs +++ b/rust/chains/tw_bitcoin/src/modules/planner/psbt_planner.rs @@ -8,7 +8,6 @@ use crate::modules::tx_builder::script_parser::StandardScriptParser; use crate::modules::tx_builder::BitcoinChainInfo; use tw_coin_entry::coin_context::CoinContext; use tw_coin_entry::error::prelude::*; -use tw_coin_entry::signing_output_error; use tw_proto::BitcoinV2::Proto; use tw_proto::BitcoinV2::Proto::mod_Input::OneOfclaiming_script as ClaimingScriptProto; use tw_proto::BitcoinV2::Proto::mod_Output::OneOfto_recipient as ToRecipientProto; @@ -21,25 +20,14 @@ pub struct PsbtPlanner; impl PsbtPlanner { pub fn plan_psbt( coin: &dyn CoinContext, - input: &Proto::PsbtSigningInput, - ) -> Proto::TransactionPlan<'static> { - Self::plan_psbt_impl(coin, input) - .unwrap_or_else(|e| signing_output_error!(Proto::TransactionPlan, e)) - } - - pub fn plan_psbt_impl( - coin: &dyn CoinContext, - input: &Proto::PsbtSigningInput, + input: &Proto::SigningInput, + psbt_input: &Proto::Psbt, ) -> SigningResult> { let chain_info = SigningRequestBuilder::chain_info(coin, &input.chain_info)?; - let PsbtRequest { unsigned_tx, .. } = PsbtRequest::build(input)?; + let PsbtRequest { unsigned_tx, .. } = PsbtRequest::build(input, psbt_input)?; let total_input = unsigned_tx.total_input()?; - let total_output = unsigned_tx.total_output()?; - let fee_estimate = total_input - .checked_sub(total_output) - .or_tw_err(SigningErrorType::Error_not_enough_utxos) - .context("PSBT sum(input) < sum(output)")?; + let fee_estimate = unsigned_tx.fee()?; let vsize_estimate = unsigned_tx.estimate_transaction().vsize() as u64; diff --git a/rust/chains/tw_bitcoin/src/modules/psbt_request/mod.rs b/rust/chains/tw_bitcoin/src/modules/psbt_request/mod.rs index dcf92cbe366..f3d63e4c6ca 100644 --- a/rust/chains/tw_bitcoin/src/modules/psbt_request/mod.rs +++ b/rust/chains/tw_bitcoin/src/modules/psbt_request/mod.rs @@ -4,7 +4,7 @@ use crate::modules::psbt_request::output_psbt::OutputPsbt; use crate::modules::psbt_request::utxo_psbt::UtxoPsbt; -use crate::modules::tx_builder::public_keys::PublicKeys; +use crate::modules::signing_request::SigningRequestBuilder; use bitcoin::psbt::Psbt; use tw_coin_entry::error::prelude::*; use tw_proto::BitcoinV2::Proto; @@ -21,8 +21,8 @@ pub struct PsbtRequest { } impl PsbtRequest { - pub fn build(input: &Proto::PsbtSigningInput) -> SigningResult { - let psbt = Psbt::deserialize(input.psbt.as_ref()) + pub fn build(input: &Proto::SigningInput, psbt_input: &Proto::Psbt) -> SigningResult { + let psbt = Psbt::deserialize(&psbt_input.psbt) .tw_err(|_| SigningErrorType::Error_input_parse) .context("Error deserializing PSBT")?; @@ -34,7 +34,7 @@ impl PsbtRequest { .context("Invalid PSBT transaction version")?; let lock_time = psbt.unsigned_tx.lock_time.to_consensus_u32(); - let public_keys = Self::get_public_keys(input)?; + let public_keys = SigningRequestBuilder::get_public_keys(input)?; let mut builder = TransactionBuilder::default(); builder.version(version).lock_time(lock_time); @@ -60,20 +60,4 @@ impl PsbtRequest { let unsigned_tx = builder.build()?; Ok(PsbtRequest { psbt, unsigned_tx }) } - - fn get_public_keys(input: &Proto::PsbtSigningInput) -> SigningResult { - let mut public_keys = PublicKeys::default(); - - if input.private_keys.is_empty() { - for public in input.public_keys.iter() { - public_keys.add_public_key(public.to_vec()); - } - } else { - for private in input.private_keys.iter() { - public_keys.add_public_with_ecdsa_private(private)?; - } - } - - Ok(public_keys) - } } diff --git a/rust/chains/tw_bitcoin/src/modules/signer.rs b/rust/chains/tw_bitcoin/src/modules/signer.rs index 7cd2e7d4749..1bed884f053 100644 --- a/rust/chains/tw_bitcoin/src/modules/signer.rs +++ b/rust/chains/tw_bitcoin/src/modules/signer.rs @@ -34,9 +34,24 @@ impl BitcoinSigner { pub fn sign_impl( coin: &dyn CoinContext, - input: &Proto::SigningInput<'_>, + input: &Proto::SigningInput, + ) -> SigningResult> { + use Proto::mod_SigningInput::OneOftransaction as TransactionType; + + match input.transaction { + TransactionType::builder(ref tx) => Self::sign_with_tx_builder(coin, input, tx), + TransactionType::psbt(ref psbt) => Self::sign_psbt(coin, input, psbt), + TransactionType::None => SigningError::err(SigningErrorType::Error_invalid_params) + .context("Either `TransactionBuilder` or `Psbt` should be set"), + } + } + + pub fn sign_with_tx_builder( + coin: &dyn CoinContext, + input: &Proto::SigningInput, + tx_builder_input: &Proto::TransactionBuilder, ) -> SigningResult> { - let request = SigningRequestBuilder::build(coin, input)?; + let request = SigningRequestBuilder::build(coin, input, tx_builder_input)?; let SelectResult { unsigned_tx, plan } = TxPlanner::plan(request)?; let keys_manager = Self::keys_manager_for_tx( @@ -62,27 +77,16 @@ impl BitcoinSigner { } pub fn sign_psbt( - coin: &dyn CoinContext, - input: &Proto::PsbtSigningInput<'_>, - ) -> Proto::PsbtSigningOutput<'static> { - Self::sign_psbt_impl(coin, input) - .unwrap_or_else(|e| signing_output_error!(Proto::PsbtSigningOutput, e)) - } - - pub fn sign_psbt_impl( _coin: &dyn CoinContext, - input: &Proto::PsbtSigningInput<'_>, - ) -> SigningResult> { + input: &Proto::SigningInput, + psbt_input: &Proto::Psbt, + ) -> SigningResult> { let PsbtRequest { mut psbt, unsigned_tx, - } = PsbtRequest::build(input)?; + } = PsbtRequest::build(input, psbt_input)?; - let fee = unsigned_tx - .total_input()? - .checked_sub(unsigned_tx.total_output()?) - .or_tw_err(SigningErrorType::Error_not_enough_utxos) - .context("PSBT sum(input) < sum(output)")?; + let fee = unsigned_tx.fee()?; let keys_manager = Self::keys_manager_for_tx( &input.private_keys, @@ -95,7 +99,7 @@ impl BitcoinSigner { update_psbt_signed(&mut psbt, &signed_tx); - Ok(Proto::PsbtSigningOutput { + Ok(Proto::SigningOutput { transaction: Some(ProtobufBuilder::tx_to_proto(&signed_tx)), encoded: Cow::from(signed_tx.encode_out()), txid: Cow::from(signed_tx.txid()), @@ -103,8 +107,10 @@ impl BitcoinSigner { vsize: signed_tx.vsize() as u64, fee, weight: signed_tx.weight() as u64, - psbt: Cow::from(psbt.serialize()), - ..Proto::PsbtSigningOutput::default() + psbt: Some(Proto::Psbt { + psbt: Cow::from(psbt.serialize()), + }), + ..Proto::SigningOutput::default() }) } diff --git a/rust/chains/tw_bitcoin/src/modules/signing_request/mod.rs b/rust/chains/tw_bitcoin/src/modules/signing_request/mod.rs index 81548a754fb..bddbcac4b0c 100644 --- a/rust/chains/tw_bitcoin/src/modules/signing_request/mod.rs +++ b/rust/chains/tw_bitcoin/src/modules/signing_request/mod.rs @@ -15,7 +15,7 @@ use tw_utxo::modules::tx_planner::{PlanRequest, RequestType}; use tw_utxo::modules::utxo_selector::InputSelector; use tw_utxo::transaction::standard_transaction::builder::TransactionBuilder; use tw_utxo::transaction::standard_transaction::Transaction; -use Proto::mod_SigningInput::OneOfdust_policy as ProtoDustPolicy; +use Proto::mod_TransactionBuilder::OneOfdust_policy as ProtoDustPolicy; const DEFAULT_TX_VERSION: u32 = 1; @@ -27,19 +27,22 @@ impl SigningRequestBuilder { pub fn build( coin: &dyn CoinContext, input: &Proto::SigningInput, + transaction_builder: &Proto::TransactionBuilder, ) -> SigningResult { let chain_info = Self::chain_info(coin, &input.chain_info)?; - let dust_policy = Self::dust_policy(&input.dust_policy)?; - let fee_per_vbyte = input.fee_per_vb; - let version = Self::transaction_version(&input.version); + let dust_policy = Self::dust_policy(&transaction_builder.dust_policy)?; + let fee_per_vbyte = transaction_builder.fee_per_vb; + let version = Self::transaction_version(&transaction_builder.version); let public_keys = Self::get_public_keys(input)?; let mut builder = TransactionBuilder::default(); - builder.version(version).lock_time(input.lock_time); + builder + .version(version) + .lock_time(transaction_builder.lock_time); // Parse all UTXOs. - for utxo_proto in input.inputs.iter() { + for utxo_proto in transaction_builder.inputs.iter() { let utxo_builder = UtxoProtobuf::new(&chain_info, utxo_proto, &public_keys); let (utxo, utxo_args) = utxo_builder @@ -49,7 +52,7 @@ impl SigningRequestBuilder { } // If `max_amount_output` is set, construct a transaction with only one output. - if let Some(max_output_proto) = input.max_amount_output.as_ref() { + if let Some(max_output_proto) = transaction_builder.max_amount_output.as_ref() { let output_builder = OutputProtobuf::new(&chain_info, max_output_proto); let max_output = output_builder @@ -66,7 +69,7 @@ impl SigningRequestBuilder { } // `max_amount_output` isn't set, parse all Outputs. - for output_proto in input.outputs.iter() { + for output_proto in transaction_builder.outputs.iter() { let output = OutputProtobuf::new(&chain_info, output_proto) .output_from_proto() .context("Error creating Output from Proto")?; @@ -74,7 +77,7 @@ impl SigningRequestBuilder { } // Parse change output if it was provided. - let change_output = input + let change_output = transaction_builder .change_output .as_ref() .map(|change_output_proto| { @@ -84,7 +87,7 @@ impl SigningRequestBuilder { }) .transpose()?; - let input_selector = Self::input_selector(&input.input_selector); + let input_selector = Self::input_selector(&transaction_builder.input_selector); let unsigned_tx = builder.build()?; Ok(StandardSigningRequest { @@ -98,7 +101,7 @@ impl SigningRequestBuilder { }) } - fn get_public_keys(input: &Proto::SigningInput) -> SigningResult { + pub fn get_public_keys(input: &Proto::SigningInput) -> SigningResult { let mut public_keys = PublicKeys::default(); if input.private_keys.is_empty() { diff --git a/rust/frameworks/tw_utxo/src/lib.rs b/rust/frameworks/tw_utxo/src/lib.rs index cdfe6be6642..84748580ded 100644 --- a/rust/frameworks/tw_utxo/src/lib.rs +++ b/rust/frameworks/tw_utxo/src/lib.rs @@ -13,4 +13,3 @@ pub mod signature; pub mod signing_mode; pub mod spending_data; pub mod transaction; -pub mod utxo_entry; diff --git a/rust/frameworks/tw_utxo/src/transaction/unsigned_transaction.rs b/rust/frameworks/tw_utxo/src/transaction/unsigned_transaction.rs index 75f43f780ea..d001f6b81ec 100644 --- a/rust/frameworks/tw_utxo/src/transaction/unsigned_transaction.rs +++ b/rust/frameworks/tw_utxo/src/transaction/unsigned_transaction.rs @@ -140,6 +140,15 @@ where .or_tw_err(SigningErrorType::Error_tx_too_big) .context("Sum of Transaction output amounts is too big") } + + /// Calculates the unsigned transaction fee by the formula: + /// `sum(input) - sum(output)` + pub fn fee(&self) -> SigningResult { + self.total_input()? + .checked_sub(self.total_output()?) + .or_tw_err(SigningErrorType::Error_not_enough_utxos) + .context("sum(input) < sum(output)") + } } fn check_utxo_args_number(inputs_len: usize, utxo_args: usize) -> SigningResult<()> { diff --git a/rust/frameworks/tw_utxo/src/utxo_entry.rs b/rust/frameworks/tw_utxo/src/utxo_entry.rs deleted file mode 100644 index 8d8321b299c..00000000000 --- a/rust/frameworks/tw_utxo/src/utxo_entry.rs +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -use tw_coin_entry::coin_context::CoinContext; -use tw_memory::Data; -use tw_proto::{deserialize, serialize, MessageRead, MessageWrite, ProtoResult}; - -pub trait UtxoEntry { - type PsbtSigningInput<'a>: MessageRead<'a>; - type PsbtSigningOutput: MessageWrite; - type PsbtTransactionPlan: MessageWrite; - - fn sign_psbt( - &self, - coin: &dyn CoinContext, - input: Self::PsbtSigningInput<'_>, - ) -> Self::PsbtSigningOutput; - - fn plan_psbt( - &self, - coin: &dyn CoinContext, - input: Self::PsbtSigningInput<'_>, - ) -> Self::PsbtTransactionPlan; -} - -pub trait UtxoEntryExt { - fn sign_psbt(&self, coin: &dyn CoinContext, input: &[u8]) -> ProtoResult; - - fn plan_psbt(&self, coin: &dyn CoinContext, input: &[u8]) -> ProtoResult; -} - -impl UtxoEntryExt for T { - fn sign_psbt(&self, coin: &dyn CoinContext, input: &[u8]) -> ProtoResult { - let input: T::PsbtSigningInput<'_> = deserialize(input)?; - let output = ::sign_psbt(self, coin, input); - serialize(&output) - } - - fn plan_psbt(&self, coin: &dyn CoinContext, input: &[u8]) -> ProtoResult { - let input: T::PsbtSigningInput<'_> = deserialize(input)?; - let output = ::plan_psbt(self, coin, input); - serialize(&output) - } -} diff --git a/rust/tw_coin_registry/src/dispatcher.rs b/rust/tw_coin_registry/src/dispatcher.rs index ec28beb651a..bda3fdba931 100644 --- a/rust/tw_coin_registry/src/dispatcher.rs +++ b/rust/tw_coin_registry/src/dispatcher.rs @@ -23,11 +23,9 @@ use tw_solana::entry::SolanaEntry; use tw_sui::entry::SuiEntry; use tw_thorchain::entry::ThorchainEntry; use tw_ton::entry::TheOpenNetworkEntry; -use tw_utxo::utxo_entry::UtxoEntryExt; pub type CoinEntryExtStaticRef = &'static dyn CoinEntryExt; pub type EvmEntryExtStaticRef = &'static dyn EvmEntryExt; -pub type UtxoEntryExtStaticRef = &'static dyn UtxoEntryExt; // start_of_blockchain_entries - USED TO GENERATE CODE const APTOS: AptosEntry = AptosEntry; @@ -85,11 +83,3 @@ pub fn evm_dispatcher(coin: CoinType) -> RegistryResult { _ => Err(RegistryError::Unsupported), } } - -pub fn utxo_dispatcher(coin: CoinType) -> RegistryResult { - let item = get_coin_item(coin)?; - match item.blockchain { - BlockchainType::Bitcoin => Ok(&BITCOIN), - _ => Err(RegistryError::Unsupported), - } -} diff --git a/rust/tw_tests/tests/chains/bitcoin/bitcoin_compile/brc20.rs b/rust/tw_tests/tests/chains/bitcoin/bitcoin_compile/brc20.rs index bb631cc34b4..40f6a48aaec 100644 --- a/rust/tw_tests/tests/chains/bitcoin/bitcoin_compile/brc20.rs +++ b/rust/tw_tests/tests/chains/bitcoin/bitcoin_compile/brc20.rs @@ -3,7 +3,7 @@ // Copyright © 2017 Trust Wallet. use crate::chains::common::bitcoin::{ - btc_info, compile, dust_threshold, input, output, preimage, DUST, SIGHASH_ALL, + btc_info, compile, dust_threshold, input, output, preimage, TransactionOneof, DUST, SIGHASH_ALL, }; use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::ToHex; @@ -51,17 +51,22 @@ fn test_bitcoin_compile_brc20_transfer_commit() { to_recipient: output::p2wpkh(my_pubkey.to_vec()), }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V2, - public_keys: vec![my_pubkey.to_vec().into()], inputs: vec![utxo_0], outputs: vec![out_0, explicit_change_out], input_selector: Proto::InputSelector::UseAll, - chain_info: btc_info(), dust_policy: dust_threshold(DUST), ..Default::default() }; + let signing = Proto::SigningInput { + public_keys: vec![my_pubkey.to_vec().into()], + chain_info: btc_info(), + transaction: TransactionOneof::builder(builder), + ..Default::default() + }; + let sighash_0 = H256::from("36dea9e58307a81b4adfa31f2cb8938270b5b16e345ccd3580c4451499260667"); preimage::BitcoinPreImageHelper::new(&signing) .coin(CoinType::Bitcoin) @@ -124,17 +129,22 @@ fn test_bitcoin_compile_brc20_transfer_reveal() { to_recipient: output::p2wpkh(my_pubkey.to_vec()), }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V2, - public_keys: vec![my_pubkey.to_vec().into()], inputs: vec![utxo_0], outputs: vec![out_0], input_selector: Proto::InputSelector::UseAll, - chain_info: btc_info(), dust_policy: dust_threshold(DUST), ..Default::default() }; + let signing = Proto::SigningInput { + public_keys: vec![my_pubkey.to_vec().into()], + chain_info: btc_info(), + transaction: TransactionOneof::builder(builder), + ..Default::default() + }; + let sighash_0 = H256::from("3fd1a453f68dadba110a5b18798a06458e5f3d84dbb6f4f33334fa440a4a2530"); preimage::BitcoinPreImageHelper::new(&signing) .coin(CoinType::Bitcoin) diff --git a/rust/tw_tests/tests/chains/bitcoin/bitcoin_compile/compile_error.rs b/rust/tw_tests/tests/chains/bitcoin/bitcoin_compile/compile_error.rs index dfd15a29148..bcb36dffa1d 100644 --- a/rust/tw_tests/tests/chains/bitcoin/bitcoin_compile/compile_error.rs +++ b/rust/tw_tests/tests/chains/bitcoin/bitcoin_compile/compile_error.rs @@ -3,7 +3,8 @@ // Copyright © 2017 Trust Wallet. use crate::chains::common::bitcoin::{ - btc_info, dust_threshold, input, output, DUST, MINER_FEE, ONE_BTC, SIGHASH_ALL, + btc_info, dust_threshold, input, output, TransactionOneof, DUST, MINER_FEE, ONE_BTC, + SIGHASH_ALL, }; use tw_any_coin::test_utils::sign_utils::CompilerHelper; use tw_coin_registry::coin_type::CoinType; @@ -35,16 +36,21 @@ fn test_bitcoin_compile_p2pkh_error() { to_recipient: output::p2pkh(bob_pubkey.clone()), }; - let signing = Proto::SigningInput { - public_keys: vec![alice_pubkey.clone().into()], + let builder = Proto::TransactionBuilder { inputs: vec![tx1], outputs: vec![out1], input_selector: Proto::InputSelector::UseAll, - chain_info: btc_info(), dust_policy: dust_threshold(DUST), ..Default::default() }; + let signing = Proto::SigningInput { + public_keys: vec![alice_pubkey.clone().into()], + chain_info: btc_info(), + transaction: TransactionOneof::builder(builder), + ..Default::default() + }; + let invalid_signature = "360a84fb41ad07f07c845fedc34cde728421803ebbaae392fc39c116b29fc07b53bd9d1376e15a191d844db458893b928f3efbfee90c9febf51ab84c9796677900" .decode_hex() .unwrap(); diff --git a/rust/tw_tests/tests/chains/bitcoin/bitcoin_compile/p2pkh.rs b/rust/tw_tests/tests/chains/bitcoin/bitcoin_compile/p2pkh.rs index 0b0d66d9254..68dc9cea741 100644 --- a/rust/tw_tests/tests/chains/bitcoin/bitcoin_compile/p2pkh.rs +++ b/rust/tw_tests/tests/chains/bitcoin/bitcoin_compile/p2pkh.rs @@ -3,8 +3,8 @@ // Copyright © 2017 Trust Wallet. use crate::chains::common::bitcoin::{ - btc_info, compile, dust_threshold, input, output, preimage, DUST, MINER_FEE, ONE_BTC, - SIGHASH_ALL, + btc_info, compile, dust_threshold, input, output, preimage, TransactionOneof, DUST, MINER_FEE, + ONE_BTC, SIGHASH_ALL, }; use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::{DecodeHex, ToHex}; @@ -42,17 +42,22 @@ fn test_bitcoin_compile_p2pkh() { to_recipient: output::p2pkh(bob_pubkey.clone()), }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V2, - public_keys: vec![alice_pubkey.to_vec().into()], inputs: vec![tx1], outputs: vec![out1], input_selector: Proto::InputSelector::UseAll, - chain_info: btc_info(), dust_policy: dust_threshold(DUST), ..Default::default() }; + let signing = Proto::SigningInput { + public_keys: vec![alice_pubkey.to_vec().into()], + chain_info: btc_info(), + transaction: TransactionOneof::builder(builder), + ..Default::default() + }; + let sighash_1 = "6a0e072da66b141fdb448323d54765cafcaf084a06d2fa13c8aed0c694e50d18" .decode_hex() .unwrap(); diff --git a/rust/tw_tests/tests/chains/bitcoin/bitcoin_plan/plan_psbt.rs b/rust/tw_tests/tests/chains/bitcoin/bitcoin_plan/plan_psbt.rs index 70adc5b626a..28c55e13b9d 100644 --- a/rust/tw_tests/tests/chains/bitcoin/bitcoin_plan/plan_psbt.rs +++ b/rust/tw_tests/tests/chains/bitcoin/bitcoin_plan/plan_psbt.rs @@ -4,7 +4,7 @@ use crate::chains::common::bitcoin::input::out_point; use crate::chains::common::bitcoin::psbt_plan::BitcoinPsbtPlanHelper; -use crate::chains::common::bitcoin::{btc_info, input, output, RecipientType}; +use crate::chains::common::bitcoin::{btc_info, input, output, transaction_psbt, RecipientType}; use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::DecodeHex; use tw_keypair::ecdsa; @@ -17,12 +17,12 @@ fn test_bitcoin_plan_psbt_thorchain_swap_witness() { ) .unwrap(); - let psbt = "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d00000000".decode_hex().unwrap(); - let input = Proto::PsbtSigningInput { - psbt: psbt.into(), + let psbt = "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d00000000"; + let input = Proto::SigningInput { public_keys: vec![private_key.public().compressed().to_vec().into()], chain_info: btc_info(), - ..Proto::PsbtSigningInput::default() + transaction: transaction_psbt(psbt), + ..Proto::SigningInput::default() }; let utxo_0 = Proto::Input { diff --git a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/brc20.rs b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/brc20.rs index 8bf93d1c9c2..5836744205a 100644 --- a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/brc20.rs +++ b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/brc20.rs @@ -1,5 +1,5 @@ use crate::chains::common::bitcoin::{ - btc_info, dust_threshold, input, output, plan, sign, DUST, SIGHASH_ALL, + btc_info, dust_threshold, input, output, plan, sign, TransactionOneof, DUST, SIGHASH_ALL, }; use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::DecodeHex; @@ -34,17 +34,22 @@ fn test_bitcoin_sign_brc20_commit() { to_recipient: output::p2wpkh(alice_pubkey.to_vec()), }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V2, - private_keys: vec![ALICE_PRIVATE_KEY.decode_hex().unwrap().into()], inputs: vec![tx1], outputs: vec![out1, out2], input_selector: Proto::InputSelector::UseAll, - chain_info: btc_info(), dust_policy: dust_threshold(DUST), ..Default::default() }; + let signing = Proto::SigningInput { + private_keys: vec![ALICE_PRIVATE_KEY.decode_hex().unwrap().into()], + chain_info: btc_info(), + transaction: TransactionOneof::builder(builder), + ..Default::default() + }; + // https://www.blockchain.com/explorer/transactions/btc/797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1 let txid = "797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1"; sign::BitcoinSignHelper::new(&signing) @@ -80,16 +85,21 @@ fn test_bitcoin_sign_brc20_reveal() { to_recipient: output::p2wpkh(alice_pubkey.to_vec()), }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V2, - private_keys: vec![ALICE_PRIVATE_KEY.decode_hex().unwrap().into()], inputs: vec![tx1], outputs: vec![out1], input_selector: Proto::InputSelector::UseAll, + dust_policy: dust_threshold(DUST), + ..Default::default() + }; + + let signing = Proto::SigningInput { + private_keys: vec![ALICE_PRIVATE_KEY.decode_hex().unwrap().into()], chain_info: btc_info(), // We enable deterministic Schnorr signatures here dangerous_use_fixed_schnorr_rng: true, - dust_policy: dust_threshold(DUST), + transaction: TransactionOneof::builder(builder), ..Default::default() }; @@ -154,16 +164,22 @@ fn test_bitcoin_sign_brc20_transfer() { to_recipient: output::p2wpkh(alice_pubkey.to_vec()), }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V2, - private_keys: vec![ALICE_PRIVATE_KEY.decode_hex().unwrap().into()], inputs: vec![tx1, tx2], outputs: vec![out1, change_output], input_selector: Proto::InputSelector::UseAll, + // We enable deterministic Schnorr signatures here + dust_policy: dust_threshold(DUST), + ..Default::default() + }; + + let signing = Proto::SigningInput { + private_keys: vec![ALICE_PRIVATE_KEY.decode_hex().unwrap().into()], chain_info: btc_info(), // We enable deterministic Schnorr signatures here dangerous_use_fixed_schnorr_rng: true, - dust_policy: dust_threshold(DUST), + transaction: TransactionOneof::builder(builder), ..Default::default() }; diff --git a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/op_return.rs b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/op_return.rs index 457bf5729b6..10101742107 100644 --- a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/op_return.rs +++ b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/op_return.rs @@ -2,7 +2,9 @@ // // Copyright © 2017 Trust Wallet. -use crate::chains::common::bitcoin::{dust_threshold, input, output, sign, DUST, SIGHASH_ALL}; +use crate::chains::common::bitcoin::{ + dust_threshold, input, output, sign, TransactionOneof, DUST, SIGHASH_ALL, +}; use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::DecodeHex; use tw_proto::BitcoinV2::Proto; @@ -48,8 +50,7 @@ fn test_bitcoin_deposit_to_zetachain() { to_recipient: output::to_address(my_address), }; - let signing = Proto::SigningInput { - private_keys: vec![my_private_key.into()], + let builder = Proto::TransactionBuilder { inputs: vec![utxo_0], outputs: vec![out_0, out_1, explicit_change_output], input_selector: Proto::InputSelector::UseAll, @@ -58,6 +59,12 @@ fn test_bitcoin_deposit_to_zetachain() { ..Default::default() }; + let signing = Proto::SigningInput { + private_keys: vec![my_private_key.into()], + transaction: TransactionOneof::builder(builder), + ..Default::default() + }; + // Successfully broadcasted: // https://mempool.space/tx/2b871b6c1112ad0a777f6db1f7a7709154c4d9af8e771ba4eca148915f830e9d // https://explorer.zetachain.com/cc/tx/0x269e319478f8849247abb28b33a7b8e0a849dab4551bab328bf58bf67b02a807 diff --git a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2pkh.rs b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2pkh.rs index 0cfbcbd74aa..410761a2410 100644 --- a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2pkh.rs +++ b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2pkh.rs @@ -1,6 +1,6 @@ use crate::chains::common::bitcoin::{ - btc_info, dust_threshold, input, output, plan, sign, BITCOIN_P2PKH_PREFIX, DUST, MINER_FEE, - ONE_BTC, SIGHASH_ALL, + btc_info, dust_threshold, input, output, plan, sign, TransactionOneof, BITCOIN_P2PKH_PREFIX, + DUST, MINER_FEE, ONE_BTC, SIGHASH_ALL, }; use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::DecodeHex; @@ -55,17 +55,22 @@ fn test_bitcoin_sign_input_p2pkh(utxo_owner: P2PKHClaimingScriptType) { to_recipient: output::p2pkh(bob_pubkey.clone()), }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V2, - private_keys: vec![ALICE_PRIVATE_KEY.decode_hex().unwrap().into()], inputs: vec![tx1], outputs: vec![out1], input_selector: Proto::InputSelector::UseAll, - chain_info: btc_info(), dust_policy: dust_threshold(DUST), ..Default::default() }; + let signing = Proto::SigningInput { + private_keys: vec![ALICE_PRIVATE_KEY.decode_hex().unwrap().into()], + chain_info: btc_info(), + transaction: TransactionOneof::builder(builder), + ..Default::default() + }; + plan::BitcoinPlanHelper::new(&signing) .coin(CoinType::Bitcoin) .plan(plan::Expected { @@ -144,19 +149,24 @@ fn test_bitcoin_cash_sign_input_p2pkh_custom_script() { to_recipient: output::to_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"), }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V1, - private_keys: vec![alice_private_key.into()], inputs: vec![utxo_1], outputs: vec![out_1, explicit_change_out], change_output: None, // No matter which selector to use. input_selector: Proto::InputSelector::SelectInOrder, - chain_info: btc_info(), dust_policy: dust_threshold(DUST), ..Default::default() }; + let signing = Proto::SigningInput { + private_keys: vec![alice_private_key.into()], + chain_info: btc_info(), + transaction: TransactionOneof::builder(builder), + ..Default::default() + }; + let expected_encoded = concat!( "01000000", "01", diff --git a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2sh.rs b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2sh.rs index 204c86b119d..3e5e5854a27 100644 --- a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2sh.rs +++ b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2sh.rs @@ -1,6 +1,6 @@ use crate::chains::common::bitcoin::{ - btc_info, dust_threshold, input, output, sign, BITCOIN_P2SH_PREFIX, DUST, MINER_FEE, ONE_BTC, - SIGHASH_ALL, + btc_info, dust_threshold, input, output, sign, TransactionOneof, BITCOIN_P2SH_PREFIX, DUST, + MINER_FEE, ONE_BTC, SIGHASH_ALL, }; use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::DecodeHex; @@ -64,17 +64,22 @@ fn test_bitcoin_sign_input_p2pkh_output_p2sh(recipient_type: P2SHRecipientType) to_recipient, }; - let input = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V2, - private_keys: vec![alice_privkey_data.into()], inputs: vec![tx0], outputs: vec![out0], input_selector: Proto::InputSelector::UseAll, - chain_info: btc_info(), dust_policy: dust_threshold(DUST), ..Default::default() }; + let input = Proto::SigningInput { + private_keys: vec![alice_privkey_data.into()], + chain_info: btc_info(), + transaction: TransactionOneof::builder(builder), + ..Default::default() + }; + sign::BitcoinSignHelper::new(&input) .coin(CoinType::Bitcoin) .sign(sign::Expected { diff --git a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2tr_key_path.rs b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2tr_key_path.rs index 2b4583fc177..2f4eadf0cec 100644 --- a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2tr_key_path.rs +++ b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2tr_key_path.rs @@ -1,6 +1,6 @@ use crate::chains::common::bitcoin::{ - btc_info, dust_threshold, input, output, sign, BITCOIN_HRP, DUST, MINER_FEE, ONE_BTC, - SIGHASH_ALL, + btc_info, dust_threshold, input, output, sign, TransactionOneof, BITCOIN_HRP, DUST, MINER_FEE, + ONE_BTC, SIGHASH_ALL, }; use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::DecodeHex; @@ -57,17 +57,22 @@ fn test_bitcoin_sign_input_p2pkh_output_p2tr_key_path(send_to: P2TRRecipientType to_recipient, }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V2, - private_keys: vec![ALICE_PRIVATE_KEY.decode_hex().unwrap().into()], inputs: vec![tx1], outputs: vec![out1], input_selector: Proto::InputSelector::UseAll, - chain_info: btc_info(), dust_policy: dust_threshold(DUST), ..Default::default() }; + let signing = Proto::SigningInput { + private_keys: vec![ALICE_PRIVATE_KEY.decode_hex().unwrap().into()], + chain_info: btc_info(), + transaction: TransactionOneof::builder(builder), + ..Default::default() + }; + let txid = "9a582032f6a50cedaff77d3d5604b33adf8bc31bdaef8de977c2187e395860ac"; sign::BitcoinSignHelper::new(&signing) .coin(CoinType::Bitcoin) @@ -138,16 +143,21 @@ fn test_bitcoin_sign_output_p2tr(utxo_owner: P2TRClaimingScriptType) { to_recipient: output::p2tr_key_path(alice_pubkey.to_vec()), }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V2, - private_keys: vec![BOB_PRIVATE_KEY.decode_hex().unwrap().into()], inputs: vec![tx1], outputs: vec![out1], input_selector: Proto::InputSelector::UseAll, + dust_policy: dust_threshold(DUST), + ..Default::default() + }; + + let signing = Proto::SigningInput { + private_keys: vec![BOB_PRIVATE_KEY.decode_hex().unwrap().into()], chain_info: btc_info(), // We enable deterministic Schnorr signatures here dangerous_use_fixed_schnorr_rng: true, - dust_policy: dust_threshold(DUST), + transaction: TransactionOneof::builder(builder), ..Default::default() }; @@ -208,17 +218,22 @@ fn test_bitcoin_sign_input_p2tr_key_path_with_change_output_a9c63d() { ..Proto::Output::default() }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V2, - private_keys: vec![PRIVATE_KEY.decode_hex().unwrap().into()], inputs: vec![utxo0], outputs: vec![out0], input_selector: Proto::InputSelector::SelectDescending, fee_per_vb: 8, change_output: Some(change_output), + dust_policy: dust_threshold(DUST), + ..Default::default() + }; + + let signing = Proto::SigningInput { + private_keys: vec![PRIVATE_KEY.decode_hex().unwrap().into()], chain_info: btc_info(), dangerous_use_fixed_schnorr_rng: true, - dust_policy: dust_threshold(DUST), + transaction: TransactionOneof::builder(builder), ..Default::default() }; @@ -260,16 +275,21 @@ fn test_bitcoin_sign_input_p2tr_key_path_with_max_amount_89c5d1() { ..Proto::Output::default() }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V2, - private_keys: vec![PRIVATE_KEY.decode_hex().unwrap().into()], inputs: vec![utxo0], input_selector: Proto::InputSelector::SelectDescending, fee_per_vb: 6, max_amount_output: Some(max_output), + dust_policy: dust_threshold(DUST), + ..Default::default() + }; + + let signing = Proto::SigningInput { + private_keys: vec![PRIVATE_KEY.decode_hex().unwrap().into()], chain_info: btc_info(), dangerous_use_fixed_schnorr_rng: true, - dust_policy: dust_threshold(DUST), + transaction: TransactionOneof::builder(builder), ..Default::default() }; @@ -325,19 +345,24 @@ fn test_bitcoin_sign_input_p2tr_and_p2wpkh() { ..Proto::Output::default() }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V2, - private_keys: vec![ - P2TR_PRIVATE_KEY.decode_hex().unwrap().into(), - P2WPKH_PRIVATE_KEY.decode_hex().unwrap().into(), - ], inputs: vec![p2tr_utxo, p2wpkh_utxo], input_selector: Proto::InputSelector::UseAll, fee_per_vb: 4, max_amount_output: Some(max_output), + dust_policy: dust_threshold(DUST), + ..Default::default() + }; + + let signing = Proto::SigningInput { + private_keys: vec![ + P2TR_PRIVATE_KEY.decode_hex().unwrap().into(), + P2WPKH_PRIVATE_KEY.decode_hex().unwrap().into(), + ], chain_info: btc_info(), dangerous_use_fixed_schnorr_rng: true, - dust_policy: dust_threshold(DUST), + transaction: TransactionOneof::builder(builder), ..Default::default() }; diff --git a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2tr_script_path.rs b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2tr_script_path.rs index 45f145a37b8..5b4f67bb338 100644 --- a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2tr_script_path.rs +++ b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2tr_script_path.rs @@ -1,5 +1,5 @@ use crate::chains::common::bitcoin::{ - btc_info, dust_threshold, input, output, sign, DUST, SIGHASH_ALL, + btc_info, dust_threshold, input, output, sign, TransactionOneof, DUST, SIGHASH_ALL, }; use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::DecodeHex; @@ -47,16 +47,21 @@ fn test_bitcoin_sign_output_p2tr_custom_script_path() { to_recipient: output::p2wpkh(alice_pubkey.to_vec()), }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V2, - private_keys: vec![ALICE_PRIVATE_KEY.decode_hex().unwrap().into()], inputs: vec![tx1], outputs: vec![out1, out2], input_selector: Proto::InputSelector::UseAll, + dust_policy: dust_threshold(DUST), + ..Default::default() + }; + + let signing = Proto::SigningInput { + private_keys: vec![ALICE_PRIVATE_KEY.decode_hex().unwrap().into()], chain_info: btc_info(), // We enable deterministic Schnorr signatures here dangerous_use_fixed_schnorr_rng: true, - dust_policy: dust_threshold(DUST), + transaction: TransactionOneof::builder(builder), ..Default::default() }; diff --git a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2wpkh.rs b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2wpkh.rs index 56db03c92cb..9506375debb 100644 --- a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2wpkh.rs +++ b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2wpkh.rs @@ -3,7 +3,7 @@ // Copyright © 2017 Trust Wallet. use crate::chains::common::bitcoin::{ - btc_info, dust_threshold, input, output, sign, DUST, SIGHASH_ALL, + btc_info, dust_threshold, input, output, sign, TransactionOneof, DUST, SIGHASH_ALL, }; use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::DecodeHex; @@ -55,20 +55,25 @@ fn test_bitcoin_sign_p2wpkh_input_different_builders() { to_recipient: output::to_address("bc1q2dsdlq3343vk29runkgv4yc292hmq53jedfjmp"), }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V1, - private_keys: vec![my_private_key.to_zeroizing_vec().to_vec().into()], inputs: vec![utxo_0, utxo_1, utxo_2], outputs: vec![out_0], change_output: None, // No matter which selector to use. input_selector: Proto::InputSelector::SelectAscending, - chain_info: btc_info(), dust_policy: dust_threshold(DUST), fee_per_vb: 33, ..Default::default() }; + let signing = Proto::SigningInput { + private_keys: vec![my_private_key.to_zeroizing_vec().to_vec().into()], + chain_info: btc_info(), + transaction: TransactionOneof::builder(builder), + ..Default::default() + }; + // Successfully broadcasted: https://mempool.space/tx/5d6bf53576a54be4d92cd8abf58d28ecc9ea7956eaf970d24d6bfcb9fcfe9855 sign::BitcoinSignHelper::new(&signing) .coin(CoinType::Bitcoin) diff --git a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2wsh.rs b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2wsh.rs index 4c47d28ac51..ffca582c171 100644 --- a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2wsh.rs +++ b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/p2wsh.rs @@ -1,6 +1,6 @@ use crate::chains::common::bitcoin::{ - btc_info, dust_threshold, input, output, sign, BITCOIN_HRP, DUST, MINER_FEE, ONE_BTC, - SIGHASH_ALL, + btc_info, dust_threshold, input, output, sign, TransactionOneof, BITCOIN_HRP, DUST, MINER_FEE, + ONE_BTC, SIGHASH_ALL, }; use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::DecodeHex; @@ -67,17 +67,22 @@ fn test_bitcoin_sign_output_p2wsh(recipient_type: P2WSHRecipientType) { to_recipient, }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V2, - private_keys: vec![ALICE_PRIVATE_KEY.decode_hex().unwrap().into()], inputs: vec![tx1], outputs: vec![out1], input_selector: Proto::InputSelector::UseAll, - chain_info: btc_info(), dust_policy: dust_threshold(DUST), ..Default::default() }; + let signing = Proto::SigningInput { + private_keys: vec![ALICE_PRIVATE_KEY.decode_hex().unwrap().into()], + chain_info: btc_info(), + transaction: TransactionOneof::builder(builder), + ..Default::default() + }; + sign::BitcoinSignHelper::new(&signing) .coin(CoinType::Bitcoin) .sign(sign::Expected { diff --git a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/psbt.rs b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/psbt.rs index ae660fd50c7..fc957c289d8 100644 --- a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/psbt.rs +++ b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/psbt.rs @@ -3,6 +3,7 @@ // Copyright © 2017 Trust Wallet. use crate::chains::common::bitcoin::psbt_sign::{BitcoinPsbtSignHelper, Expected}; +use crate::chains::common::bitcoin::transaction_psbt; use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::DecodeHex; use tw_proto::BitcoinV2::Proto; @@ -13,11 +14,11 @@ fn test_bitcoin_sign_psbt_thorchain_swap_witness() { .decode_hex() .unwrap(); - let psbt = "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d00000000".decode_hex().unwrap(); - let input = Proto::PsbtSigningInput { - psbt: psbt.into(), + let psbt = "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d00000000"; + let input = Proto::SigningInput { private_keys: vec![private_key.into()], - ..Proto::PsbtSigningInput::default() + transaction: transaction_psbt(psbt), + ..Proto::SigningInput::default() }; // Successfully broadcasted: https://mempool.space/tx/634a416e82ac710166725f6a4090ac7b5db69687e86b2d2e38dcb3d91c956c32 @@ -40,11 +41,11 @@ fn test_bitcoin_sign_psbt_thorchain_swap_non_witness() { .decode_hex() .unwrap(); - let psbt = "70736274ff01008202000000015c37bcf049b7e62dd5bfd707e0998ce86163b786e3cd45db2336cb794a8d8aa10000000000ffffffff03f82a000000000000160014bf5a13a26791a5db6406304a46952e264c2b28910000000000000000056a032b3a6291950000000000001976a9147c2c0ac72afbde13ecf52fca54368e7883b538b188ac000000000001007e0200000002714916920be4dbc87cbb8697ca9b1420d6b1e47e7d732e2d2e0e7a935087788d0000000000ffffffff326c951cd9b3dc382e2d6be88796b65d7bac90406a5f72660171ac826e414a630200000000ffffffff01efca0000000000001976a9147c2c0ac72afbde13ecf52fca54368e7883b538b188ac0000000000000000".decode_hex().unwrap(); - let input = Proto::PsbtSigningInput { - psbt: psbt.into(), + let psbt = "70736274ff01008202000000015c37bcf049b7e62dd5bfd707e0998ce86163b786e3cd45db2336cb794a8d8aa10000000000ffffffff03f82a000000000000160014bf5a13a26791a5db6406304a46952e264c2b28910000000000000000056a032b3a6291950000000000001976a9147c2c0ac72afbde13ecf52fca54368e7883b538b188ac000000000001007e0200000002714916920be4dbc87cbb8697ca9b1420d6b1e47e7d732e2d2e0e7a935087788d0000000000ffffffff326c951cd9b3dc382e2d6be88796b65d7bac90406a5f72660171ac826e414a630200000000ffffffff01efca0000000000001976a9147c2c0ac72afbde13ecf52fca54368e7883b538b188ac0000000000000000"; + let input = Proto::SigningInput { private_keys: vec![private_key.into()], - ..Proto::PsbtSigningInput::default() + transaction: transaction_psbt(psbt), + ..Proto::SigningInput::default() }; // Successfully broadcasted: https://mempool.space/tx/710e9270b57720f567ada156c6ac72177aa00a36789e2c6526fd80040fae3ce4 diff --git a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/send_to_address.rs b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/send_to_address.rs index 3b92f6288e1..7cf0c9eeb90 100644 --- a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/send_to_address.rs +++ b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/send_to_address.rs @@ -1,5 +1,5 @@ use crate::chains::common::bitcoin::{ - btc_info, dust_threshold, input, output, sign, DUST, SIGHASH_ALL, + btc_info, dust_threshold, input, output, sign, TransactionOneof, DUST, SIGHASH_ALL, }; use std::str::FromStr; use tw_coin_registry::coin_type::CoinType; @@ -46,9 +46,8 @@ fn test_bitcoin_send_to_p2sh_address() { to_recipient: output::to_address(&p2sh_address.to_string()), }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V2, - private_keys: vec![alice_private_key.to_zeroizing_vec().to_vec().into()], inputs: vec![utxo_0], outputs: vec![out_0], input_selector: Proto::InputSelector::UseAll, @@ -57,6 +56,12 @@ fn test_bitcoin_send_to_p2sh_address() { ..Default::default() }; + let signing = Proto::SigningInput { + private_keys: vec![alice_private_key.to_zeroizing_vec().to_vec().into()], + transaction: TransactionOneof::builder(builder), + ..Default::default() + }; + sign::BitcoinSignHelper::new(&signing) .coin(CoinType::Bitcoin) .sign(sign::Expected { @@ -96,9 +101,8 @@ fn test_bitcoin_send_to_p2pkh_address() { to_recipient: output::to_address(bob_address), }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V2, - private_keys: vec![alice_private_key.into()], inputs: vec![tx1], outputs: vec![out1], input_selector: Proto::InputSelector::UseAll, @@ -106,6 +110,12 @@ fn test_bitcoin_send_to_p2pkh_address() { ..Default::default() }; + let signing = Proto::SigningInput { + private_keys: vec![alice_private_key.into()], + transaction: TransactionOneof::builder(builder), + ..Default::default() + }; + sign::BitcoinSignHelper::new(&signing) .coin(CoinType::Bitcoin) .sign(sign::Expected { @@ -152,9 +162,8 @@ fn test_bitcoin_send_to_p2wsh_address() { to_recipient: output::to_address(&p2wsh_address.to_string()), }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V2, - private_keys: vec![alice_private_key.to_zeroizing_vec().to_vec().into()], inputs: vec![utxo_0], outputs: vec![out_0], input_selector: Proto::InputSelector::UseAll, @@ -162,6 +171,12 @@ fn test_bitcoin_send_to_p2wsh_address() { ..Default::default() }; + let signing = Proto::SigningInput { + private_keys: vec![alice_private_key.to_zeroizing_vec().to_vec().into()], + transaction: TransactionOneof::builder(builder), + ..Default::default() + }; + sign::BitcoinSignHelper::new(&signing) .coin(CoinType::Bitcoin) .sign(sign::Expected { @@ -200,9 +215,8 @@ fn test_bitcoin_send_to_p2wpkh_address() { to_recipient: output::to_address(bob_address), }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V2, - private_keys: vec![alice_private_key.into()], inputs: vec![tx1], outputs: vec![out1], input_selector: Proto::InputSelector::UseAll, @@ -210,6 +224,12 @@ fn test_bitcoin_send_to_p2wpkh_address() { ..Default::default() }; + let signing = Proto::SigningInput { + private_keys: vec![alice_private_key.into()], + transaction: TransactionOneof::builder(builder), + ..Default::default() + }; + sign::BitcoinSignHelper::new(&signing) .coin(CoinType::Bitcoin) .sign(sign::Expected { @@ -248,9 +268,8 @@ fn test_bitcoin_send_to_p2tr_key_path_address() { to_recipient: output::to_address(bob_address), }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V2, - private_keys: vec![alice_private_key.into()], inputs: vec![tx1], outputs: vec![out1], input_selector: Proto::InputSelector::UseAll, @@ -258,6 +277,12 @@ fn test_bitcoin_send_to_p2tr_key_path_address() { ..Default::default() }; + let signing = Proto::SigningInput { + private_keys: vec![alice_private_key.into()], + transaction: TransactionOneof::builder(builder), + ..Default::default() + }; + sign::BitcoinSignHelper::new(&signing) .coin(CoinType::Bitcoin) .sign(sign::Expected { diff --git a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/sighash_single.rs b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/sighash_single.rs index e859e780b11..cfde76bbda3 100644 --- a/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/sighash_single.rs +++ b/rust/tw_tests/tests/chains/bitcoin/bitcoin_sign/sighash_single.rs @@ -3,7 +3,7 @@ // Copyright © 2017 Trust Wallet. use crate::chains::common::bitcoin::{ - btc_info, dust_threshold, input, output, sign, DUST, SIGHASH_SINGLE, + btc_info, dust_threshold, input, output, sign, TransactionOneof, DUST, SIGHASH_SINGLE, }; use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::DecodeHex; @@ -51,18 +51,13 @@ fn test_bitcoin_sign_sighash_single() { to_recipient: output::to_address("1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"), }; - let signing = Proto::SigningInput { + let builder = Proto::TransactionBuilder { version: Proto::TransactionVersion::V1, - private_keys: vec![ - priv_key_1.decode_hex().unwrap().into(), - priv_key_2.decode_hex().unwrap().into(), - ], inputs: vec![utxo_0, utxo_1], outputs: vec![out_0, explicit_change_output], change_output: None, // No matter which selector to use. input_selector: Proto::InputSelector::UseAll, - chain_info: btc_info(), dust_policy: dust_threshold(DUST), // Disable transaction fee calculation. // This transaction was generated via legacy Bitcoin implementation with an error in weight calculation. @@ -70,6 +65,16 @@ fn test_bitcoin_sign_sighash_single() { ..Default::default() }; + let signing = Proto::SigningInput { + private_keys: vec![ + priv_key_1.decode_hex().unwrap().into(), + priv_key_2.decode_hex().unwrap().into(), + ], + chain_info: btc_info(), + transaction: TransactionOneof::builder(builder), + ..Default::default() + }; + sign::BitcoinSignHelper::new(&signing) .coin(CoinType::Bitcoin) .sign(sign::Expected { diff --git a/rust/tw_tests/tests/chains/common/bitcoin/mod.rs b/rust/tw_tests/tests/chains/common/bitcoin/mod.rs index 1c9c1ecd2f0..337c48c590e 100644 --- a/rust/tw_tests/tests/chains/common/bitcoin/mod.rs +++ b/rust/tw_tests/tests/chains/common/bitcoin/mod.rs @@ -6,6 +6,8 @@ // functions/constants. #![allow(dead_code)] +use tw_encoding::hex::DecodeHex; + pub mod compile; pub mod data; pub mod plan; @@ -37,6 +39,7 @@ pub use tw_proto::BitcoinV2::Proto::mod_Output::{ OneOfto_recipient as RecipientType, OutputBuilder, }; pub use tw_proto::BitcoinV2::Proto::mod_PublicKeyOrHash::OneOfvariant as PublicKeyOrHashType; +pub use tw_proto::BitcoinV2::Proto::mod_SigningInput::OneOftransaction as TransactionOneof; use tw_proto::BitcoinV2::Proto; @@ -48,8 +51,14 @@ pub fn btc_info() -> Option> { }) } -pub fn dust_threshold(threshold: i64) -> Proto::mod_SigningInput::OneOfdust_policy { - Proto::mod_SigningInput::OneOfdust_policy::fixed_dust_threshold(threshold) +pub fn dust_threshold(threshold: i64) -> Proto::mod_TransactionBuilder::OneOfdust_policy { + Proto::mod_TransactionBuilder::OneOfdust_policy::fixed_dust_threshold(threshold) +} + +pub fn transaction_psbt(hex: &str) -> TransactionOneof { + TransactionOneof::psbt(Proto::Psbt { + psbt: hex.decode_hex().unwrap().into(), + }) } pub mod input { diff --git a/rust/tw_tests/tests/chains/common/bitcoin/plan.rs b/rust/tw_tests/tests/chains/common/bitcoin/plan.rs index 8ca4c959c85..eaa2cf285a5 100644 --- a/rust/tw_tests/tests/chains/common/bitcoin/plan.rs +++ b/rust/tw_tests/tests/chains/common/bitcoin/plan.rs @@ -134,16 +134,21 @@ pub fn make_planning_input(args: PlanArgs) -> Proto::SigningInput<'static> { None }; - Proto::SigningInput { + let builder = Proto::TransactionBuilder { inputs, outputs, input_selector: args.order, fee_per_vb: args.fee_per_vb, change_output, max_amount_output, + dust_policy: dust_threshold(args.dust_threshold), + ..Default::default() + }; + + Proto::SigningInput { // Chain info does not matter in this case, set to the default BTC. chain_info: btc_info(), - dust_policy: dust_threshold(args.dust_threshold), + transaction: TransactionOneof::builder(builder), ..Proto::SigningInput::default() } } diff --git a/rust/tw_tests/tests/chains/common/bitcoin/psbt_plan.rs b/rust/tw_tests/tests/chains/common/bitcoin/psbt_plan.rs index d754b7b6c9a..3f258771632 100644 --- a/rust/tw_tests/tests/chains/common/bitcoin/psbt_plan.rs +++ b/rust/tw_tests/tests/chains/common/bitcoin/psbt_plan.rs @@ -2,19 +2,17 @@ // // Copyright © 2017 Trust Wallet. +use tw_any_coin::test_utils::plan_utils::AnyPlannerHelper; use tw_coin_registry::coin_type::CoinType; -use tw_memory::test_utils::tw_data_helper::TWDataHelper; use tw_proto::BitcoinV2::Proto; -use tw_proto::{deserialize, serialize}; -use wallet_core_rs::ffi::bitcoin::psbt::tw_bitcoin_psbt_plan; pub struct BitcoinPsbtPlanHelper<'a> { - input: &'a Proto::PsbtSigningInput<'a>, + input: &'a Proto::SigningInput<'a>, coin_type: Option, } impl<'a> BitcoinPsbtPlanHelper<'a> { - pub fn new(input: &'a Proto::PsbtSigningInput<'a>) -> Self { + pub fn new(input: &'a Proto::SigningInput<'a>) -> Self { BitcoinPsbtPlanHelper { input, coin_type: None, @@ -30,16 +28,10 @@ impl<'a> BitcoinPsbtPlanHelper<'a> { pub fn plan_psbt(self, expected: Proto::TransactionPlan) { let coin_type = self .coin_type - .expect("'BitcoinSignHelper::coin_type' is not set"); + .expect("'BitcoinPsbtPlanHelper::coin_type' is not set"); - let input = serialize(self.input).unwrap(); - let input = TWDataHelper::create(input); - - let output = - TWDataHelper::wrap(unsafe { tw_bitcoin_psbt_plan(input.ptr(), coin_type as u32) }); - let output_bytes = output.to_vec().unwrap(); - - let output: Proto::TransactionPlan = deserialize(&output_bytes).unwrap(); + let mut planner = AnyPlannerHelper::::default(); + let output = planner.plan(coin_type, self.input.clone()); assert_eq!(output.error, expected.error, "{}", output.error_message); diff --git a/rust/tw_tests/tests/chains/common/bitcoin/psbt_sign.rs b/rust/tw_tests/tests/chains/common/bitcoin/psbt_sign.rs index 5cd7e72ff99..dbbc0ae5c6c 100644 --- a/rust/tw_tests/tests/chains/common/bitcoin/psbt_sign.rs +++ b/rust/tw_tests/tests/chains/common/bitcoin/psbt_sign.rs @@ -2,13 +2,11 @@ // // Copyright © 2017 Trust Wallet. +use tw_any_coin::test_utils::sign_utils::AnySignerHelper; use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::ToHex; -use tw_memory::test_utils::tw_data_helper::TWDataHelper; use tw_proto::BitcoinV2::Proto; use tw_proto::Common::Proto::SigningError; -use tw_proto::{deserialize, serialize}; -use wallet_core_rs::ffi::bitcoin::psbt::tw_bitcoin_psbt_sign; pub struct Expected { /// Hex encoded PSBT. @@ -21,12 +19,12 @@ pub struct Expected { } pub struct BitcoinPsbtSignHelper<'a> { - input: &'a Proto::PsbtSigningInput<'a>, + input: &'a Proto::SigningInput<'a>, coin_type: Option, } impl<'a> BitcoinPsbtSignHelper<'a> { - pub fn new(input: &'a Proto::PsbtSigningInput<'a>) -> Self { + pub fn new(input: &'a Proto::SigningInput<'a>) -> Self { BitcoinPsbtSignHelper { input, coin_type: None, @@ -44,17 +42,13 @@ impl<'a> BitcoinPsbtSignHelper<'a> { .coin_type .expect("'BitcoinSignHelper::coin_type' is not set"); - let input = serialize(self.input).unwrap(); - let input = TWDataHelper::create(input); - - let output = - TWDataHelper::wrap(unsafe { tw_bitcoin_psbt_sign(input.ptr(), coin_type as u32) }); - let output_bytes = output.to_vec().unwrap(); - - let output: Proto::PsbtSigningOutput = deserialize(&output_bytes).unwrap(); + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(coin_type, self.input.clone()); assert_eq!(output.error, SigningError::OK, "{}", output.error_message); - assert_eq!(output.psbt.to_hex(), expected.psbt, "Wrong PSBT hex"); + + let psbt = output.psbt.expect("No PSBT in the SigningOutput").psbt; + assert_eq!(psbt.to_hex(), expected.psbt, "Wrong PSBT hex"); assert_eq!( output.encoded.to_hex(), expected.encoded, diff --git a/rust/tw_tests/tests/chains/common/bitcoin/sign.rs b/rust/tw_tests/tests/chains/common/bitcoin/sign.rs index af8bbbec314..83917d568c9 100644 --- a/rust/tw_tests/tests/chains/common/bitcoin/sign.rs +++ b/rust/tw_tests/tests/chains/common/bitcoin/sign.rs @@ -77,8 +77,10 @@ impl<'a> BitcoinSignHelper<'a> { /// Gets the map of `{ OutPoint -> Amount }`. fn utxo_map(&self) -> UtxoMap { - let mut utxo_map = HashMap::with_capacity(self.input.inputs.len()); - for utxo in self.input.inputs.iter() { + let builder_input = self.transaction_builder(); + + let mut utxo_map = HashMap::with_capacity(builder_input.inputs.len()); + for utxo in builder_input.inputs.iter() { utxo_map.insert(OutPoint::from_proto(&utxo.out_point), utxo.value); } utxo_map @@ -99,6 +101,16 @@ impl<'a> BitcoinSignHelper<'a> { output_inputs } + + fn transaction_builder(&self) -> &Proto::TransactionBuilder<'_> { + match self.input.transaction { + TransactionOneof::builder(ref builder) => builder, + TransactionOneof::psbt(_) => panic!( + "`BitcoinSignHelper` doesn't support PSBT. Consider using `BitcoinPsbtSignHelper`" + ), + TransactionOneof::None => unreachable!(), + } + } } #[derive(Debug, Eq, Hash, PartialEq)] diff --git a/rust/wallet_core_rs/src/ffi/bitcoin/mod.rs b/rust/wallet_core_rs/src/ffi/bitcoin/mod.rs index dd6df1c9eda..7632dd9561b 100644 --- a/rust/wallet_core_rs/src/ffi/bitcoin/mod.rs +++ b/rust/wallet_core_rs/src/ffi/bitcoin/mod.rs @@ -1,5 +1,3 @@ // SPDX-License-Identifier: Apache-2.0 // // Copyright © 2017 Trust Wallet. - -pub mod psbt; diff --git a/rust/wallet_core_rs/src/ffi/bitcoin/psbt.rs b/rust/wallet_core_rs/src/ffi/bitcoin/psbt.rs deleted file mode 100644 index ceec4d34ab9..00000000000 --- a/rust/wallet_core_rs/src/ffi/bitcoin/psbt.rs +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#![allow(clippy::missing_safety_doc)] - -use tw_coin_registry::coin_type::CoinType; -use tw_coin_registry::dispatcher::{coin_dispatcher, utxo_dispatcher}; -use tw_memory::ffi::tw_data::TWData; -use tw_memory::ffi::RawPtrTrait; -use tw_misc::try_or_else; - -/// Signs a PSBT (Partially Signed Bitcoin Transaction) specified by the signing input and coin type. -/// -/// \param input The serialized data of a signing input (e.g. `TW.BitcoinV2.Proto.PsbtSigningInput`) -/// \param coin The given coin type to sign the PSBT for. -/// \return The serialized data of a `Proto.PsbtSigningOutput` proto object (e.g. `TW.BitcoinV2.Proto.PsbtSigningOutput`). -#[no_mangle] -pub unsafe extern "C" fn tw_bitcoin_psbt_sign(input: *const TWData, coin: u32) -> *mut TWData { - let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); - let input_data = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); - let utxo_dispatcher = try_or_else!(utxo_dispatcher(coin), std::ptr::null_mut); - let (coin_context, _) = try_or_else!(coin_dispatcher(coin), std::ptr::null_mut); - - utxo_dispatcher - .sign_psbt(&coin_context, input_data.as_slice()) - .map(|data| TWData::from(data).into_ptr()) - .unwrap_or_else(|_| std::ptr::null_mut()) -} - -/// Plans a PSBT (Partially Signed Bitcoin Transaction). -/// Can be used to get the transaction detailed decoded from PSBT. -/// -/// \param input The serialized data of a signing input (e.g. `TW.BitcoinV2.Proto.PsbtSigningInput`) -/// \param coin The given coin type to sign the PSBT for. -/// \return The serialized data of a `Proto.TransactionPlan` proto object (e.g. `TW.BitcoinV2.Proto.TransactionPlan`). -#[no_mangle] -pub unsafe extern "C" fn tw_bitcoin_psbt_plan(input: *const TWData, coin: u32) -> *mut TWData { - let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); - let input_data = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); - let utxo_dispatcher = try_or_else!(utxo_dispatcher(coin), std::ptr::null_mut); - let (coin_context, _) = try_or_else!(coin_dispatcher(coin), std::ptr::null_mut); - - utxo_dispatcher - .plan_psbt(&coin_context, input_data.as_slice()) - .map(|data| TWData::from(data).into_ptr()) - .unwrap_or_else(|_| std::ptr::null_mut()) -} diff --git a/src/Bitcoin/Psbt.cpp b/src/Bitcoin/Psbt.cpp deleted file mode 100644 index 99484238ea1..00000000000 --- a/src/Bitcoin/Psbt.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#include "Psbt.h" -#include "rust/Wrapper.h" - -namespace TW::Bitcoin { - -Data Psbt::sign(const Data &input, TWCoinType coin) { - const Rust::TWDataWrapper inputRust = input; - const Rust::TWDataWrapper outputRust = Rust::tw_bitcoin_psbt_sign(inputRust.get(), static_cast(coin)); - return outputRust.toDataOrDefault(); -} - -Data Psbt::plan(const Data& input, TWCoinType coin) { - const Rust::TWDataWrapper inputRust = input; - const Rust::TWDataWrapper outputRust = Rust::tw_bitcoin_psbt_plan(inputRust.get(), static_cast(coin)); - return outputRust.toDataOrDefault(); -} - -} // namespace TW::Bitcoin diff --git a/src/Bitcoin/Psbt.h b/src/Bitcoin/Psbt.h deleted file mode 100644 index 7ee128ea129..00000000000 --- a/src/Bitcoin/Psbt.h +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#pragma once - -#include "Data.h" -#include "TrustWalletCore/TWCoinType.h" - -namespace TW::Bitcoin { - -class Psbt { -public: - /// Signs a PSBT (Partially Signed Bitcoin Transaction) specified by the signing input and coin type. - static Data sign(const Data& input, TWCoinType coin); - - /// Plans a PSBT (Partially Signed Bitcoin Transaction). - /// Can be used to get the transaction detailed decoded from PSBT. - static Data plan(const Data& input, TWCoinType coin); -}; - -} // namespace TW::Bitcoin diff --git a/src/interface/TWBitcoinPsbt.cpp b/src/interface/TWBitcoinPsbt.cpp deleted file mode 100644 index 54c52a515f9..00000000000 --- a/src/interface/TWBitcoinPsbt.cpp +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#include "TrustWalletCore/TWBitcoinPsbt.h" -#include "Bitcoin/Psbt.h" - -using namespace TW; - -TWData* _Nonnull TWBitcoinPsbtSign(TWData* _Nonnull input, enum TWCoinType coin) { - const Data& dataIn = *(reinterpret_cast(input)); - const auto dataOut = Bitcoin::Psbt::sign(dataIn, coin); - return TWDataCreateWithBytes(dataOut.data(), dataOut.size()); -} - -TWData* _Nonnull TWBitcoinPsbtPlan(TWData* _Nonnull input, enum TWCoinType coin) { - const Data& dataIn = *(reinterpret_cast(input)); - const auto dataOut = Bitcoin::Psbt::plan(dataIn, coin); - return TWDataCreateWithBytes(dataOut.data(), dataOut.size()); -} diff --git a/src/proto/BitcoinV2.proto b/src/proto/BitcoinV2.proto index 5f58e161174..6129b39b40c 100644 --- a/src/proto/BitcoinV2.proto +++ b/src/proto/BitcoinV2.proto @@ -197,49 +197,67 @@ enum TransactionVersion { V2 = 2; } -message SigningInput { +// Transaction builder used in `SigningInput`. +message TransactionBuilder { // Transaction version. TransactionVersion version = 1; - // User private keys. - // Only required if the `sign` method is called. - repeated bytes private_keys = 2; - // User public keys. - // Only required if the `plan`, `preImageHash` methods are called. - repeated bytes public_keys = 3; // (optional) Block height or timestamp indicating at what point transactions can be included in a block. // Zero by default. - uint32 lock_time = 4; + uint32 lock_time = 2; // The inputs to spend. - repeated Input inputs = 5; + repeated Input inputs = 3; // The output of the transaction. Note that the change output is specified // in the `change_output` field. - repeated Output outputs = 6; + repeated Output outputs = 4; // How the inputs should be selected. - InputSelector input_selector = 7; + InputSelector input_selector = 5; // The amount of satoshis per vbyte ("satVb"), used for fee calculation. // Can be satoshis per byte ("satB") **ONLY** when transaction does not contain segwit UTXOs. - int64 fee_per_vb = 8; + int64 fee_per_vb = 6; // (optional) The change output to be added (return to sender) at the end of the outputs list. // The `Output.value` will be overwritten, leave default. // Note there can be no change output if the change amount is less than dust threshold. // Leave empty to explicitly disable change output creation. - Output change_output = 9; + Output change_output = 7; // The only output with a max available amount to be send. // If set, `SigningInput.outputs` and `SigningInput.change` will be ignored. // The `Output.value` will be overwritten, leave default. - Output max_amount_output = 10; - // Chain info includes p2pkh, p2sh address prefixes. - // The parameter needs to be set if an input/output has a receiver address pattern. - ChainInfo chain_info = 13; + Output max_amount_output = 8; // One of the "Dust" amount policies. // Later, we plan to add support for `DynamicDust` policy with a `min_relay_fee` amount. oneof dust_policy { // Use a constant "Dust" threshold. int64 fixed_dust_threshold = 14; } +} + +// Partially Signed Bitcoin Transaction. +message Psbt { + // Partially Signed Bitcoin Transaction binary encoded. + bytes psbt = 1; +} + +message SigningInput { + // User private keys. + // Only required if the `sign` method is called. + repeated bytes private_keys = 1; + // User public keys. + // Only required if the `plan`, `preImageHash` methods are called. + repeated bytes public_keys = 2; + // Chain info includes p2pkh, p2sh address prefixes. + // The parameter needs to be set if an input/output has a receiver address pattern. + ChainInfo chain_info = 3; // Whether disable auxiliary random data when signing. // Use for testing **ONLY**. - bool dangerous_use_fixed_schnorr_rng = 20; + bool dangerous_use_fixed_schnorr_rng = 4; + + // The transaction signing type. + oneof transaction { + // Build a transaction to be signed. + TransactionBuilder builder = 10; + // Finalize a Partially Signed Bitcoin Transaction by signing the rest of UTXOs. + Psbt psbt = 11; + } } message Transaction { @@ -355,45 +373,7 @@ message SigningOutput { uint64 weight = 7; // The total and final fee of the transaction in satoshis. int64 fee = 8; -} - -message PsbtSigningInput { - // Partly signed transaction to be signed. - bytes psbt = 1; - // User private keys. - // Only required if the `signPSBT` method is called. - repeated bytes private_keys = 2; - // User public keys. - // Only required if the `planPSBT` method is called. - repeated bytes public_keys = 3; - // Chain info includes p2pkh, p2sh, hrp address prefixes. - // The parameter needs to be set when `planPSBT` is called. - ChainInfo chain_info = 4; - // Whether disable auxiliary random data when signing. - // Use for testing **ONLY**. - bool dangerous_use_fixed_schnorr_rng = 5; -} - -message PsbtSigningOutput { - // A possible error, `OK` if none. - Common.Proto.SigningError error = 1; - // Error description. - string error_message = 2; - // Resulting transaction. - Transaction transaction = 3; - // The encoded transaction that can be submitted to the network. - bytes encoded = 4; - // The transaction ID (hash). - bytes txid = 5; - // The total `vsize` in `vbytes`. - // It is used to compare how much blockweight needs to be allocated to confirm a transaction. - // For non-segwit transactions, `vsize` = `size`. - uint64 vsize = 6; - // Transaction weight is defined as Base transaction size * 3 + Total transaction size - // (ie. the same method as calculating Block weight from Base size and Total size). - uint64 weight = 7; - // The total and final fee of the transaction in satoshis. - int64 fee = 8; - // Signed transaction serialized as PSBT. - bytes psbt = 9; + // Optional. Signed transaction serialized as PSBT. + // Set if `SigningInput.psbt` is used. + Psbt psbt = 9; } diff --git a/swift/Tests/Blockchains/BitcoinTests.swift b/swift/Tests/Blockchains/BitcoinTests.swift index c33fc2a27f8..28d0eb39094 100644 --- a/swift/Tests/Blockchains/BitcoinTests.swift +++ b/swift/Tests/Blockchains/BitcoinTests.swift @@ -55,16 +55,18 @@ class BitcoinTransactionSignerTests: XCTestCase { } let signingInput = BitcoinV2SigningInput.with { - $0.version = .v2 + $0.builder = BitcoinV2TransactionBuilder.with { + $0.version = .v2 + $0.inputs = [utxo0] + $0.outputs = [out0, changeOut] + $0.inputSelector = .useAll + $0.fixedDustThreshold = dustAmount + } $0.privateKeys = [privateKeyData] - $0.inputs = [utxo0] - $0.outputs = [out0, changeOut] - $0.inputSelector = .useAll $0.chainInfo = BitcoinV2ChainInfo.with { $0.p2PkhPrefix = 0 $0.p2ShPrefix = 5 } - $0.fixedDustThreshold = dustAmount } let legacySigningInput = BitcoinSigningInput.with { @@ -115,17 +117,19 @@ class BitcoinTransactionSignerTests: XCTestCase { } let signingInput = BitcoinV2SigningInput.with { - $0.version = .v2 + $0.builder = BitcoinV2TransactionBuilder.with { + $0.version = .v2 + $0.inputs = [utxo0] + $0.outputs = [out0] + $0.inputSelector = .useAll + $0.fixedDustThreshold = dustAmount + } $0.privateKeys = [privateKeyData] - $0.inputs = [utxo0] - $0.outputs = [out0] - $0.inputSelector = .useAll $0.chainInfo = BitcoinV2ChainInfo.with { $0.p2PkhPrefix = 0 $0.p2ShPrefix = 5 } $0.dangerousUseFixedSchnorrRng = true - $0.fixedDustThreshold = dustAmount } let legacySigningInput = BitcoinSigningInput.with { @@ -197,16 +201,18 @@ class BitcoinTransactionSignerTests: XCTestCase { } let signingInput = BitcoinV2SigningInput.with { - $0.version = .v2 + $0.builder = BitcoinV2TransactionBuilder.with { + $0.version = .v2 + $0.inputs = [utxo0, utxo1] + $0.outputs = [out0, changeOut] + $0.inputSelector = .useAll + $0.fixedDustThreshold = dustAmount + } $0.privateKeys = [privateKeyData] - $0.inputs = [utxo0, utxo1] - $0.outputs = [out0, changeOut] - $0.inputSelector = .useAll $0.chainInfo = BitcoinV2ChainInfo.with { $0.p2PkhPrefix = 0 $0.p2ShPrefix = 5 } - $0.fixedDustThreshold = dustAmount } let legacySigningInput = BitcoinSigningInput.with { @@ -391,19 +397,25 @@ class BitcoinTransactionSignerTests: XCTestCase { func testSignPsbtThorSwap() throws { let privateKey = Data(hexString: "f00ffbe44c5c2838c13d2778854ac66b75e04eb6054f0241989e223223ad5e55")! let psbt = Data(hexString: "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d00000000")! - - let input = BitcoinV2PsbtSigningInput.with { - $0.psbt = psbt + + let input = BitcoinV2SigningInput.with { + $0.psbt = BitcoinV2Psbt.with { + $0.psbt = psbt + } $0.privateKeys = [privateKey] } - - let outputData = try BitcoinPsbt.sign(input: input.serializedData(), coin: .bitcoin) - let output = try BitcoinV2PsbtSigningOutput(serializedData: outputData) - + + let legacy = BitcoinSigningInput.with { + $0.signingV2 = input + } + let output: BitcoinSigningOutput = AnySigner.sign(input: legacy, coin: .bitcoin) + XCTAssertEqual(output.error, .ok) - XCTAssertEqual(output.psbt.hexString, "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d01086c02483045022100b1229a008f20691639767bf925d6b8956ea957ccc633ad6b5de3618733a55e6b02205774d3320489b8a57a6f8de07f561de3e660ff8e587f6ac5422c49020cd4dc9101210306d8c664ea8fd2683eebea1d3114d90e0a5429e5783ba49b80ddabce04ff28f300000000") - XCTAssertEqual(output.encoded.hexString, "02000000000101147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d02483045022100b1229a008f20691639767bf925d6b8956ea957ccc633ad6b5de3618733a55e6b02205774d3320489b8a57a6f8de07f561de3e660ff8e587f6ac5422c49020cd4dc9101210306d8c664ea8fd2683eebea1d3114d90e0a5429e5783ba49b80ddabce04ff28f300000000") - XCTAssertEqual(output.txid.hexString, "634a416e82ac710166725f6a4090ac7b5db69687e86b2d2e38dcb3d91c956c32") + XCTAssert(output.hasSigningResultV2) + let outputV2 = output.signingResultV2 + XCTAssertEqual(outputV2.psbt.psbt.hexString, "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d01086c02483045022100b1229a008f20691639767bf925d6b8956ea957ccc633ad6b5de3618733a55e6b02205774d3320489b8a57a6f8de07f561de3e660ff8e587f6ac5422c49020cd4dc9101210306d8c664ea8fd2683eebea1d3114d90e0a5429e5783ba49b80ddabce04ff28f300000000") + XCTAssertEqual(outputV2.encoded.hexString, "02000000000101147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d02483045022100b1229a008f20691639767bf925d6b8956ea957ccc633ad6b5de3618733a55e6b02205774d3320489b8a57a6f8de07f561de3e660ff8e587f6ac5422c49020cd4dc9101210306d8c664ea8fd2683eebea1d3114d90e0a5429e5783ba49b80ddabce04ff28f300000000") + XCTAssertEqual(outputV2.txid.hexString, "634a416e82ac710166725f6a4090ac7b5db69687e86b2d2e38dcb3d91c956c32") } func testPlanPsbtThorSwap() throws { @@ -413,38 +425,43 @@ class BitcoinTransactionSignerTests: XCTestCase { let psbt = Data(hexString: "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d00000000")! - let input = BitcoinV2PsbtSigningInput.with { - $0.psbt = psbt + let input = BitcoinV2SigningInput.with { + // TODO check if it works + $0.psbt.psbt = psbt $0.publicKeys = [publicKey.data] } - let outputData = try BitcoinPsbt.plan(input: input.serializedData(), coin: .bitcoin) - let output = try BitcoinV2TransactionPlan(serializedData: outputData) - - XCTAssertEqual(output.error, .ok) - - XCTAssertEqual(output.inputs[0].receiverAddress, "bc1qkyu3n8k8jmekl3pwvdl59k5w8enjp25akz2r3z") - XCTAssertEqual(output.inputs[0].value, 66_406) + let legacy = BitcoinSigningInput.with { + $0.signingV2 = input + } + let legacyPlan: BitcoinTransactionPlan = AnySigner.plan(input: legacy, coin: .bitcoin) + + XCTAssertEqual(legacyPlan.error, .ok) + XCTAssert(legacyPlan.hasPlanningResultV2) + let plan = legacyPlan.planningResultV2 + XCTAssertEqual(plan.inputs[0].receiverAddress, "bc1qkyu3n8k8jmekl3pwvdl59k5w8enjp25akz2r3z") + XCTAssertEqual(plan.inputs[0].value, 66_406) + // Vault transfer - XCTAssertEqual(output.outputs[0].toAddress, "bc1q7g48qdshqd000aysws74pun2uzxrp598gcfum0") - XCTAssertEqual(output.outputs[0].value, 60_000) - + XCTAssertEqual(plan.outputs[0].toAddress, "bc1q7g48qdshqd000aysws74pun2uzxrp598gcfum0") + XCTAssertEqual(plan.outputs[0].value, 60_000) + // OP_RETURN XCTAssertEqual( - output.outputs[1].customScriptPubkey.hexString, + plan.outputs[1].customScriptPubkey.hexString, "6a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a3530" ) - XCTAssertEqual(output.outputs[1].value, 0) - + XCTAssertEqual(plan.outputs[1].value, 0) + // Change output - XCTAssertEqual(output.outputs[2].toAddress, "bc1qkyu3n8k8jmekl3pwvdl59k5w8enjp25akz2r3z") - XCTAssertEqual(output.outputs[2].value, 4_670) - - XCTAssertEqual(output.feeEstimate, 1736) + XCTAssertEqual(plan.outputs[2].toAddress, "bc1qkyu3n8k8jmekl3pwvdl59k5w8enjp25akz2r3z") + XCTAssertEqual(plan.outputs[2].value, 4_670) + + XCTAssertEqual(plan.feeEstimate, 1736) // Please note that `change` in PSBT planning is always 0. // That's because we aren't able to determine which output is an actual change from PSBT. - XCTAssertEqual(output.change, 0) + XCTAssertEqual(plan.change, 0) } func testBitcoinMessageSigner() { diff --git a/tests/chains/Bitcoin/TWBitcoinPsbtTests.cpp b/tests/chains/Bitcoin/TWBitcoinPsbtTests.cpp index 616c9913f2a..83ef3a6c6ba 100644 --- a/tests/chains/Bitcoin/TWBitcoinPsbtTests.cpp +++ b/tests/chains/Bitcoin/TWBitcoinPsbtTests.cpp @@ -3,12 +3,11 @@ // Copyright © 2017 Trust Wallet. #include "HexCoding.h" +#include "proto/Bitcoin.pb.h" #include "proto/BitcoinV2.pb.h" #include "PrivateKey.h" #include "TestUtilities.h" -#include "TrustWalletCore/TWBitcoinPsbt.h" - #include namespace TW::Bitcoin::PsbtTests { @@ -17,47 +16,44 @@ const auto gPrivateKey = PrivateKey(parse_hex("f00ffbe44c5c2838c13d2778854ac66b7 const auto gPsbt = parse_hex("70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d00000000"); TEST(TWBitcoinPsbt, SignThorSwap) { - BitcoinV2::Proto::PsbtSigningInput input; - input.set_psbt(gPsbt.data(), gPsbt.size()); - input.add_private_keys(gPrivateKey.bytes.data(), gPrivateKey.bytes.size()); - - const auto inputData = data(input.SerializeAsString()); - const auto inputPtr = WRAPD(TWDataCreateWithBytes(inputData.data(), inputData.size())); + BitcoinV2::Proto::SigningInput signing; + signing.mutable_psbt()->set_psbt(gPsbt.data(), gPsbt.size()); + signing.add_private_keys(gPrivateKey.bytes.data(), gPrivateKey.bytes.size()); - const auto outputPtr = WRAPD(TWBitcoinPsbtSign(inputPtr.get(), TWCoinTypeBitcoin)); + Proto::SigningInput legacy; + *legacy.mutable_signing_v2() = signing; - BitcoinV2::Proto::PsbtSigningOutput output; - output.ParseFromArray( - TWDataBytes(outputPtr.get()), - static_cast(TWDataSize(outputPtr.get())) - ); + Bitcoin::Proto::SigningOutput output; + ANY_SIGN(legacy, TWCoinTypeBitcoin); EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); - EXPECT_EQ(hex(output.psbt()), "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d01086c02483045022100b1229a008f20691639767bf925d6b8956ea957ccc633ad6b5de3618733a55e6b02205774d3320489b8a57a6f8de07f561de3e660ff8e587f6ac5422c49020cd4dc9101210306d8c664ea8fd2683eebea1d3114d90e0a5429e5783ba49b80ddabce04ff28f300000000"); - EXPECT_EQ(hex(output.encoded()), "02000000000101147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d02483045022100b1229a008f20691639767bf925d6b8956ea957ccc633ad6b5de3618733a55e6b02205774d3320489b8a57a6f8de07f561de3e660ff8e587f6ac5422c49020cd4dc9101210306d8c664ea8fd2683eebea1d3114d90e0a5429e5783ba49b80ddabce04ff28f300000000"); + EXPECT_TRUE(output.has_signing_result_v2()); + + const auto& outputV2 = output.signing_result_v2(); + EXPECT_TRUE(outputV2.has_psbt()); + EXPECT_EQ(hex(outputV2.psbt().psbt()), "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d01086c02483045022100b1229a008f20691639767bf925d6b8956ea957ccc633ad6b5de3618733a55e6b02205774d3320489b8a57a6f8de07f561de3e660ff8e587f6ac5422c49020cd4dc9101210306d8c664ea8fd2683eebea1d3114d90e0a5429e5783ba49b80ddabce04ff28f300000000"); + EXPECT_EQ(hex(outputV2.encoded()), "02000000000101147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d02483045022100b1229a008f20691639767bf925d6b8956ea957ccc633ad6b5de3618733a55e6b02205774d3320489b8a57a6f8de07f561de3e660ff8e587f6ac5422c49020cd4dc9101210306d8c664ea8fd2683eebea1d3114d90e0a5429e5783ba49b80ddabce04ff28f300000000"); } TEST(TWBitcoinPsbt, PlanThorSwap) { const auto publicKey = gPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - BitcoinV2::Proto::PsbtSigningInput input; - input.set_psbt(gPsbt.data(), gPsbt.size()); - input.add_public_keys(publicKey.bytes.data(), publicKey.bytes.size()); + BitcoinV2::Proto::SigningInput inputV2; + inputV2.mutable_psbt()->set_psbt(gPsbt.data(), gPsbt.size()); + inputV2.add_public_keys(publicKey.bytes.data(), publicKey.bytes.size()); - const auto inputData = data(input.SerializeAsString()); - const auto inputPtr = WRAPD(TWDataCreateWithBytes(inputData.data(), inputData.size())); + Proto::SigningInput legacy; + *legacy.mutable_signing_v2() = inputV2; - const auto planPtr = WRAPD(TWBitcoinPsbtPlan(inputPtr.get(), TWCoinTypeBitcoin)); - - BitcoinV2::Proto::TransactionPlan plan; - plan.ParseFromArray( - TWDataBytes(planPtr.get()), - static_cast(TWDataSize(planPtr.get())) - ); + Bitcoin::Proto::TransactionPlan plan; + ANY_PLAN(legacy, plan, TWCoinTypeBitcoin); EXPECT_EQ(plan.error(), Common::Proto::SigningError::OK); - EXPECT_EQ(plan.send_amount(), 66'406); - EXPECT_EQ(plan.fee_estimate(), 1'736); + EXPECT_TRUE(plan.has_planning_result_v2()); + + const auto& planV2 = plan.planning_result_v2(); + EXPECT_EQ(planV2.send_amount(), 66'406); + EXPECT_EQ(planV2.fee_estimate(), 1'736); } } diff --git a/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp b/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp index 3150758df09..711950650d4 100644 --- a/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp +++ b/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp @@ -382,17 +382,19 @@ TEST(BitcoinSigning, SignBRC20TransferCommitV2) { auto pubKey = key.getPublicKey(TWPublicKeyTypeSECP256k1); TW::BitcoinV2::Proto::SigningInput signing; - signing.set_version(BitcoinV2::Proto::TransactionVersion::V2); signing.add_private_keys(key.bytes.data(), key.bytes.size()); - signing.set_input_selector(BitcoinV2::Proto::InputSelector::UseAll); signing.set_dangerous_use_fixed_schnorr_rng(true); - signing.set_fixed_dust_threshold(546); + + auto& builder = *signing.mutable_builder(); + builder.set_version(BitcoinV2::Proto::TransactionVersion::V2); + builder.set_input_selector(BitcoinV2::Proto::InputSelector::UseAll); + builder.set_fixed_dust_threshold(546); auto& chainInfo = *signing.mutable_chain_info(); chainInfo.set_p2pkh_prefix(0); chainInfo.set_p2sh_prefix(5); - auto& in = *signing.add_inputs(); + auto& in = *builder.add_inputs(); auto& inOutPoint = *in.mutable_out_point(); inOutPoint.set_hash(txId.data(), txId.size()); inOutPoint.set_vout(1); @@ -400,14 +402,14 @@ TEST(BitcoinSigning, SignBRC20TransferCommitV2) { in.mutable_script_builder()->mutable_p2wpkh()->set_pubkey(pubKey.bytes.data(), pubKey.bytes.size()); in.set_sighash_type(TWBitcoinSigHashTypeAll); - auto& out = *signing.add_outputs(); + auto& out = *builder.add_outputs(); out.set_value(brcInscribeAmount); auto& brc20 = *out.mutable_builder()->mutable_brc20_inscribe(); brc20.set_ticker("oadf"); brc20.set_transfer_amount("20"); brc20.set_inscribe_to(pubKey.bytes.data(), pubKey.bytes.size()); - auto& changeOut = *signing.add_outputs(); + auto& changeOut = *builder.add_outputs(); changeOut.set_value(forFeeAmount); changeOut.mutable_builder()->mutable_p2wpkh()->set_pubkey(pubKey.bytes.data(), pubKey.bytes.size()); diff --git a/tests/chains/Bitcoin/TransactionCompilerTests.cpp b/tests/chains/Bitcoin/TransactionCompilerTests.cpp index f28813fd594..fd1aeb639c6 100644 --- a/tests/chains/Bitcoin/TransactionCompilerTests.cpp +++ b/tests/chains/Bitcoin/TransactionCompilerTests.cpp @@ -303,12 +303,18 @@ TEST(BitcoinCompiler, CompileWithSignaturesV2) { const auto alicePublicKey = alicePrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); const auto bobPublicKey = parse_hex("037ed9a436e11ec4947ac4b7823787e24ba73180f1edd2857bff19c9f4d62b65bf"); + input.mutable_chain_info()->set_p2pkh_prefix(0); + input.mutable_chain_info()->set_p2sh_prefix(5); + input.add_public_keys(alicePublicKey.bytes.data(), alicePublicKey.bytes.size()); + auto txid0 = parse_hex("1e1cdc48aa990d7e154a161d5b5f1cad737742e97d2712ab188027bb42e6e47b"); std::reverse(txid0.begin(), txid0.end()); // Step 1: Prepare transaction input (protobuf) - auto& utxo0 = *input.add_inputs(); + auto& builder = *input.mutable_builder(); + + auto& utxo0 = *builder.add_inputs(); utxo0.mutable_out_point()->set_hash(txid0.data(), txid0.size()); utxo0.mutable_out_point()->set_vout(0); utxo0.set_value(ONE_BTC * 50); @@ -316,17 +322,14 @@ TEST(BitcoinCompiler, CompileWithSignaturesV2) { // Set the Alice public key as the owner of the P2PKH input. utxo0.mutable_script_builder()->mutable_p2pkh()->set_pubkey(alicePublicKey.bytes.data(), alicePublicKey.bytes.size()); - auto& out0 = *input.add_outputs(); + auto& out0 = *builder.add_outputs(); out0.set_value(ONE_BTC * 50 - 1'000'000); // Set the Bob public key as the receiver of the P2PKH output. out0.mutable_builder()->mutable_p2pkh()->set_pubkey(bobPublicKey.data(), bobPublicKey.size()); - input.set_version(BitcoinV2::Proto::TransactionVersion::V2); - input.add_public_keys(alicePublicKey.bytes.data(), alicePublicKey.bytes.size()); - input.set_input_selector(BitcoinV2::Proto::InputSelector::UseAll); - input.mutable_chain_info()->set_p2pkh_prefix(0); - input.mutable_chain_info()->set_p2sh_prefix(5); - input.set_fixed_dust_threshold(546); + builder.set_version(BitcoinV2::Proto::TransactionVersion::V2); + builder.set_input_selector(BitcoinV2::Proto::InputSelector::UseAll); + builder.set_fixed_dust_threshold(546); const auto inputLegacyData = data(inputLegacy.SerializeAsString()); diff --git a/wasm/tests/Blockchain/Bitcoin.test.ts b/wasm/tests/Blockchain/Bitcoin.test.ts index c55ebeb0db8..f839f31b8f3 100644 --- a/wasm/tests/Blockchain/Bitcoin.test.ts +++ b/wasm/tests/Blockchain/Bitcoin.test.ts @@ -54,20 +54,22 @@ describe("Bitcoin", () => { }); const signingInput = Proto.SigningInput.create({ - version: Proto.TransactionVersion.V2, + builder: { + version: Proto.TransactionVersion.V2, + inputs: [utxo0], + outputs: [out0], + inputSelector: Proto.InputSelector.SelectDescending, + feePerVb: new Long(8), + changeOutput: changeOut, + fixedDustThreshold: dustAmount, + }, privateKeys: [privateKeyData], - inputs: [utxo0], - outputs: [out0], - inputSelector: Proto.InputSelector.SelectDescending, - feePerVb: new Long(8), chainInfo: { p2pkhPrefix: 0, p2shPrefix: 5, }, - changeOutput: changeOut, // WARNING Do not use in production! dangerousUseFixedSchnorrRng: true, - fixedDustThreshold: dustAmount, }); const legacySigningInput = TW.Bitcoin.Proto.SigningInput.create({ @@ -150,16 +152,18 @@ describe("Bitcoin", () => { }); const signingInput = Proto.SigningInput.create({ - version: Proto.TransactionVersion.V2, + builder: { + version: Proto.TransactionVersion.V2, + inputs: [utxo0, utxo1], + outputs: [out0, changeOut], + inputSelector: Proto.InputSelector.UseAll, + fixedDustThreshold: dustSatoshis, + }, privateKeys: [privateKeyData], - inputs: [utxo0, utxo1], - outputs: [out0, changeOut], - inputSelector: Proto.InputSelector.UseAll, chainInfo: { p2pkhPrefix: 0, p2shPrefix: 5, }, - fixedDustThreshold: dustSatoshis, }); const legacySigningInput = TW.Bitcoin.Proto.SigningInput.create({ @@ -230,16 +234,18 @@ describe("Bitcoin", () => { }); const signingInput = Proto.SigningInput.create({ - version: Proto.TransactionVersion.V2, + builder: { + version: Proto.TransactionVersion.V2, + inputs: [utxo0], + outputs: [out0, changeOut], + inputSelector: Proto.InputSelector.UseAll, + fixedDustThreshold: dustAmount, + }, privateKeys: [privateKeyData], - inputs: [utxo0], - outputs: [out0, changeOut], - inputSelector: Proto.InputSelector.UseAll, chainInfo: { p2pkhPrefix: 0, p2shPrefix: 5, }, - fixedDustThreshold: dustAmount, }); const legacySigningInput = TW.Bitcoin.Proto.SigningInput.create({ @@ -301,18 +307,20 @@ describe("Bitcoin", () => { }); const signingInput = Proto.SigningInput.create({ - version: Proto.TransactionVersion.V2, + builder: { + version: Proto.TransactionVersion.V2, + inputs: [utxo0], + outputs: [out0], + inputSelector: Proto.InputSelector.UseAll, + fixedDustThreshold: dustAmount, + }, privateKeys: [privateKeyData], - inputs: [utxo0], - outputs: [out0], - inputSelector: Proto.InputSelector.UseAll, chainInfo: { p2pkhPrefix: 0, p2shPrefix: 5, }, // WARNING Do not use in production! dangerousUseFixedSchnorrRng: true, - fixedDustThreshold: dustAmount, }); const legacySigningInput = TW.Bitcoin.Proto.SigningInput.create({