Skip to content

Commit

Permalink
feat(btc): Add TWBitcoinPsbtSign and TWBitcoinPsbtPlan C interface
Browse files Browse the repository at this point in the history
  • Loading branch information
satoshiotomakan committed Sep 20, 2024
1 parent c7d8407 commit 0af0634
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 10 deletions.
36 changes: 36 additions & 0 deletions include/TrustWalletCore/TWBitcoinPsbt.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// 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
4 changes: 2 additions & 2 deletions rust/tw_tests/tests/chains/common/bitcoin/psbt_plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ 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_plan_psbt;
use wallet_core_rs::ffi::bitcoin::psbt::tw_bitcoin_psbt_plan;

pub struct BitcoinPsbtPlanHelper<'a> {
input: &'a Proto::PsbtSigningInput<'a>,
Expand Down Expand Up @@ -36,7 +36,7 @@ impl<'a> BitcoinPsbtPlanHelper<'a> {
let input = TWDataHelper::create(input);

let output =
TWDataHelper::wrap(unsafe { tw_bitcoin_plan_psbt(coin_type as u32, input.ptr()) });
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();
Expand Down
4 changes: 2 additions & 2 deletions rust/tw_tests/tests/chains/common/bitcoin/psbt_sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ 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_sign_psbt;
use wallet_core_rs::ffi::bitcoin::psbt::tw_bitcoin_psbt_sign;

pub struct Expected {
/// Hex encoded PSBT.
Expand Down Expand Up @@ -48,7 +48,7 @@ impl<'a> BitcoinPsbtSignHelper<'a> {
let input = TWDataHelper::create(input);

let output =
TWDataHelper::wrap(unsafe { tw_bitcoin_sign_psbt(coin_type as u32, input.ptr()) });
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();
Expand Down
12 changes: 6 additions & 6 deletions rust/wallet_core_rs/src/ffi/bitcoin/psbt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ 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 `Proto.PsbtSigningInput` protobuf message.
/// \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` protobuf message.
/// \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_sign_psbt(coin: u32, input: *const TWData) -> *mut TWData {
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);
Expand All @@ -31,11 +31,11 @@ pub unsafe extern "C" fn tw_bitcoin_sign_psbt(coin: u32, input: *const TWData) -
/// 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 `Proto.PsbtSigningInput` protobuf message.
/// \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` protobuf message.
/// \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_plan_psbt(coin: u32, input: *const TWData) -> *mut TWData {
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);
Expand Down
22 changes: 22 additions & 0 deletions src/Bitcoin/Psbt.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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<uint32_t>(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<uint32_t>(coin));
return outputRust.toDataOrDefault();
}

} // namespace TW::Bitcoin
22 changes: 22 additions & 0 deletions src/Bitcoin/Psbt.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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
20 changes: 20 additions & 0 deletions src/interface/TWBitcoinPsbt.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// 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<const Data*>(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<const Data*>(input));
const auto dataOut = Bitcoin::Psbt::plan(dataIn, coin);
return TWDataCreateWithBytes(dataOut.data(), dataOut.size());
}
63 changes: 63 additions & 0 deletions tests/chains/Bitcoin/TWBitcoinPsbtTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

#include "HexCoding.h"
#include "proto/BitcoinV2.pb.h"
#include "PrivateKey.h"
#include "TestUtilities.h"

#include "TrustWalletCore/TWBitcoinPsbt.h"

#include <gtest/gtest.h>

namespace TW::Bitcoin::PsbtTests {

const auto gPrivateKey = PrivateKey(parse_hex("f00ffbe44c5c2838c13d2778854ac66b75e04eb6054f0241989e223223ad5e55"));
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()));

const auto outputPtr = WRAPD(TWBitcoinPsbtSign(inputPtr.get(), TWCoinTypeBitcoin));

BitcoinV2::Proto::PsbtSigningOutput output;
output.ParseFromArray(
TWDataBytes(outputPtr.get()),
static_cast<int>(TWDataSize(outputPtr.get()))
);

EXPECT_EQ(output.error(), Common::Proto::SigningError::OK);
EXPECT_EQ(hex(output.psbt()), "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d01086c02483045022100b1229a008f20691639767bf925d6b8956ea957ccc633ad6b5de3618733a55e6b02205774d3320489b8a57a6f8de07f561de3e660ff8e587f6ac5422c49020cd4dc9101210306d8c664ea8fd2683eebea1d3114d90e0a5429e5783ba49b80ddabce04ff28f300000000");
EXPECT_EQ(hex(output.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());

const auto inputData = data(input.SerializeAsString());
const auto inputPtr = WRAPD(TWDataCreateWithBytes(inputData.data(), inputData.size()));

const auto planPtr = WRAPD(TWBitcoinPsbtPlan(inputPtr.get(), TWCoinTypeBitcoin));

BitcoinV2::Proto::TransactionPlan plan;
plan.ParseFromArray(
TWDataBytes(planPtr.get()),
static_cast<int>(TWDataSize(planPtr.get()))
);

EXPECT_EQ(plan.error(), Common::Proto::SigningError::OK);
EXPECT_EQ(plan.send_amount(), 66'406);
EXPECT_EQ(plan.fee_estimate(), 1'736);
}

}

0 comments on commit 0af0634

Please sign in to comment.