Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BCH]: Migrate BitcoinCash to BitcoinV2 (Rust) #4049

Merged
merged 15 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.trustwallet.core.app.blockchains.bitcoincash

import com.trustwallet.core.app.utils.Numeric
import com.trustwallet.core.app.utils.toHexBytesInByteString
import org.junit.Assert.assertEquals
import org.junit.Test
import wallet.core.java.AnySigner
import wallet.core.jni.BitcoinSigHashType
import wallet.core.jni.CoinType
import wallet.core.jni.proto.Bitcoin
import wallet.core.jni.proto.BitcoinV2
import wallet.core.jni.proto.Common.SigningError

class TestBitcoinCashSigning {

init {
System.loadLibrary("TrustWalletCore")
}

@Test
fun testSignV2P2PKH() {
val privateKey = "7fdafb9db5bc501f2096e7d13d331dc7a75d9594af3d251313ba8b6200f4e384".toHexBytesInByteString()

val utxo1 = BitcoinV2.Input.newBuilder().apply {
outPoint = BitcoinV2.OutPoint.newBuilder().apply {
hash = "e28c2b955293159898e34c6840d99bf4d390e2ee1c6f606939f18ee1e2000d05".toHexBytesInByteString()
vout = 2
}.build()
value = 5151
sighashType = BitcoinSigHashType.ALL.value() + BitcoinSigHashType.FORK.value()
receiverAddress = "bitcoincash:qzhlrcrcne07x94h99thved2pgzdtv8ccujjy73xya"
}

val out1 = BitcoinV2.Output.newBuilder().apply {
value = 600
// Legacy address
toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"
}
val changeOutputExplicit = BitcoinV2.Output.newBuilder().apply {
value = 4325
// Legacy address
toAddress = "qz0q3xmg38sr94rw8wg45vujah7kzma3cskxymnw06"
}

val builder = BitcoinV2.TransactionBuilder.newBuilder()
.setVersion(BitcoinV2.TransactionVersion.V1)
.addInputs(utxo1)
.addOutputs(out1)
.addOutputs(changeOutputExplicit)
.setInputSelector(BitcoinV2.InputSelector.UseAll)
.setFixedDustThreshold(546)
.build()

val input = BitcoinV2.SigningInput.newBuilder()
.addPrivateKeys(privateKey)
.setBuilder(builder)
.setChainInfo(BitcoinV2.ChainInfo.newBuilder().apply {
p2PkhPrefix = 0
p2ShPrefix = 5
hrp = "bitcoincash"
})
.build()

val legacyInput = Bitcoin.SigningInput.newBuilder()
.setSigningV2(input)
// `CoinType` must be set to be handled correctly.
.setCoinType(CoinType.BITCOINCASH.value())
.build()

val outputLegacy = AnySigner.sign(legacyInput, CoinType.BITCOINCASH, Bitcoin.SigningOutput.parser())
assertEquals(outputLegacy.error, SigningError.OK)
assert(outputLegacy.hasSigningResultV2())

val output = outputLegacy.signingResultV2
assertEquals(output.error, SigningError.OK)
assertEquals(
Numeric.toHexString(output.encoded.toByteArray()),
"0x0100000001e28c2b955293159898e34c6840d99bf4d390e2ee1c6f606939f18ee1e2000d05020000006b483045022100b70d158b43cbcded60e6977e93f9a84966bc0cec6f2dfd1463d1223a90563f0d02207548d081069de570a494d0967ba388ff02641d91cadb060587ead95a98d4e3534121038eab72ec78e639d02758e7860cdec018b49498c307791f785aa3019622f4ea5bffffffff0258020000000000001976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ace5100000000000001976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac00000000"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,7 @@ impl CoinEntry for {BLOCKCHAIN}Entry {
}

#[inline]
fn parse_address_unchecked(
&self,
_coin: &dyn CoinContext,
address: &str,
) -> AddressResult<Self::Address> {
fn parse_address_unchecked(&self, address: &str) -> AddressResult<Self::Address> {
{BLOCKCHAIN}Address::from_str(address)
}

Expand Down
1 change: 1 addition & 0 deletions include/TrustWalletCore/TWBlockchain.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ enum TWBlockchain {
TWBlockchainInternetComputer = 52,
TWBlockchainNativeEvmos = 53, // Cosmos
TWBlockchainNativeInjective = 54, // Cosmos
TWBlockchainBitcoinCash = 55,
};

TW_EXTERN_C_END
4 changes: 2 additions & 2 deletions registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -1453,7 +1453,7 @@
"coinId": 145,
"symbol": "BCH",
"decimals": 8,
"blockchain": "Bitcoin",
"blockchain": "BitcoinCash",
"derivation": [
{
"path": "m/44'/145'/0'/0/0",
Expand Down Expand Up @@ -3667,7 +3667,7 @@
"coinId": 899,
"symbol": "XEC",
"decimals": 2,
"blockchain": "Bitcoin",
"blockchain": "BitcoinCash",
"derivation": [
{
"path": "m/44'/899'/0'/0/0",
Expand Down
16 changes: 16 additions & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ members = [
"chains/tw_aptos",
"chains/tw_binance",
"chains/tw_bitcoin",
"chains/tw_bitcoincash",
"chains/tw_cosmos",
"chains/tw_ethereum",
"chains/tw_greenfield",
Expand Down
6 changes: 1 addition & 5 deletions rust/chains/tw_aptos/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,7 @@ impl CoinEntry for AptosEntry {
}

#[inline]
fn parse_address_unchecked(
&self,
_coin: &dyn CoinContext,
address: &str,
) -> AddressResult<Self::Address> {
fn parse_address_unchecked(&self, address: &str) -> AddressResult<Self::Address> {
Address::from_str(address)
}

Expand Down
6 changes: 1 addition & 5 deletions rust/chains/tw_binance/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,7 @@ impl CoinEntry for BinanceEntry {
}

#[inline]
fn parse_address_unchecked(
&self,
_coin: &dyn CoinContext,
address: &str,
) -> AddressResult<Self::Address> {
fn parse_address_unchecked(&self, address: &str) -> AddressResult<Self::Address> {
BinanceAddress::from_str(address)
}

Expand Down
28 changes: 28 additions & 0 deletions rust/chains/tw_bitcoin/src/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

use tw_coin_entry::error::prelude::SigningResult;
use tw_utxo::address::standard_bitcoin::StandardBitcoinAddress;
use tw_utxo::context::{AddressPrefixes, UtxoContext};
use tw_utxo::script::Script;

#[derive(Default)]
pub struct StandardBitcoinContext;

impl UtxoContext for StandardBitcoinContext {
type Address = StandardBitcoinAddress;

fn addr_to_script_pubkey(
addr: &Self::Address,
prefixes: AddressPrefixes,
) -> SigningResult<Script> {
match addr {
StandardBitcoinAddress::Legacy(legacy) => {
legacy.to_script_pubkey(prefixes.p2pkh_prefix, prefixes.p2sh_prefix)
},
StandardBitcoinAddress::Segwit(segwit) => segwit.to_script_pubkey(),
StandardBitcoinAddress::Taproot(taproot) => taproot.to_script_pubkey(),
}
}
}
17 changes: 7 additions & 10 deletions rust/chains/tw_bitcoin/src/entry.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::context::StandardBitcoinContext;
use crate::modules::compiler::BitcoinCompiler;
use crate::modules::planner::BitcoinPlanner;
use crate::modules::signer::BitcoinSigner;
Expand Down Expand Up @@ -26,7 +27,7 @@ impl CoinEntry for BitcoinEntry {

// Optional modules:
type JsonSigner = NoJsonSigner;
type PlanBuilder = BitcoinPlanner;
type PlanBuilder = BitcoinPlanner<StandardBitcoinContext>;
type MessageSigner = NoMessageSigner;
type WalletConnector = NoWalletConnector;
type TransactionDecoder = NoTransactionDecoder;
Expand All @@ -43,11 +44,7 @@ impl CoinEntry for BitcoinEntry {
}

#[inline]
fn parse_address_unchecked(
&self,
_coin: &dyn CoinContext,
address: &str,
) -> AddressResult<Self::Address> {
fn parse_address_unchecked(&self, address: &str) -> AddressResult<Self::Address> {
StandardBitcoinAddress::from_str(address)
}

Expand All @@ -64,7 +61,7 @@ impl CoinEntry for BitcoinEntry {

#[inline]
fn sign(&self, coin: &dyn CoinContext, proto: Self::SigningInput<'_>) -> Self::SigningOutput {
BitcoinSigner::sign(coin, &proto)
BitcoinSigner::<StandardBitcoinContext>::sign(coin, &proto)
}

#[inline]
Expand All @@ -73,7 +70,7 @@ impl CoinEntry for BitcoinEntry {
coin: &dyn CoinContext,
proto: Proto::SigningInput<'_>,
) -> Self::PreSigningOutput {
BitcoinCompiler::preimage_hashes(coin, proto)
BitcoinCompiler::<StandardBitcoinContext>::preimage_hashes(coin, proto)
}

#[inline]
Expand All @@ -84,12 +81,12 @@ impl CoinEntry for BitcoinEntry {
signatures: Vec<SignatureBytes>,
public_keys: Vec<PublicKeyBytes>,
) -> Self::SigningOutput {
BitcoinCompiler::compile(coin, proto, signatures, public_keys)
BitcoinCompiler::<StandardBitcoinContext>::compile(coin, proto, signatures, public_keys)
}

#[inline]
fn plan_builder(&self) -> Option<Self::PlanBuilder> {
Some(BitcoinPlanner)
Some(BitcoinPlanner::<StandardBitcoinContext>::default())
}

#[inline]
Expand Down
1 change: 1 addition & 0 deletions rust/chains/tw_bitcoin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
//
// Copyright © 2017 Trust Wallet.

pub mod context;
pub mod entry;
pub mod modules;
18 changes: 12 additions & 6 deletions rust/chains/tw_bitcoin/src/modules/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::modules::protobuf_builder::ProtobufBuilder;
use crate::modules::psbt_request::PsbtRequest;
use crate::modules::signing_request::SigningRequestBuilder;
use std::borrow::Cow;
use std::marker::PhantomData;
use tw_coin_entry::coin_context::CoinContext;
use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes};
use tw_coin_entry::error::prelude::*;
Expand All @@ -15,6 +16,7 @@ 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::context::UtxoContext;
use tw_utxo::modules::sighash_computer::{SighashComputer, TaprootTweak, TxPreimage};
use tw_utxo::modules::sighash_verifier::SighashVerifier;
use tw_utxo::modules::tx_compiler::TxCompiler;
Expand All @@ -23,9 +25,11 @@ use tw_utxo::modules::utxo_selector::SelectResult;
use tw_utxo::signing_mode::SigningMethod;
use tw_utxo::transaction::transaction_interface::TransactionInterface;

pub struct BitcoinCompiler;
pub struct BitcoinCompiler<Context: UtxoContext> {
_phantom: PhantomData<Context>,
}

impl BitcoinCompiler {
impl<Context: UtxoContext> BitcoinCompiler<Context> {
/// Please note that [`Proto::SigningInput::public_key`] must be set.
/// If the public key should be derived from a private key, please do it before this method is called.
#[inline]
Expand All @@ -43,10 +47,12 @@ impl BitcoinCompiler {
) -> SigningResult<Proto::PreSigningOutput<'static>> {
let unsigned_tx = match input.transaction {
TransactionType::builder(ref tx_builder) => {
let request = SigningRequestBuilder::build(coin, &input, tx_builder)?;
let request = SigningRequestBuilder::<Context>::build(coin, &input, tx_builder)?;
TxPlanner::plan(request)?.unsigned_tx
},
TransactionType::psbt(ref psbt) => PsbtRequest::build(&input, psbt)?.unsigned_tx,
TransactionType::psbt(ref psbt) => {
PsbtRequest::<Context>::build(&input, psbt)?.unsigned_tx
},
TransactionType::None => {
return SigningError::err(SigningErrorType::Error_invalid_params)
.context("Either `TransactionBuilder` or `Psbt` should be set")
Expand Down Expand Up @@ -104,7 +110,7 @@ impl BitcoinCompiler {
tx_builder_input: &Proto::TransactionBuilder,
signatures: Vec<SignatureBytes>,
) -> SigningResult<Proto::SigningOutput<'static>> {
let request = SigningRequestBuilder::build(coin, input, tx_builder_input)?;
let request = SigningRequestBuilder::<Context>::build(coin, input, tx_builder_input)?;
let SelectResult { unsigned_tx, plan } = TxPlanner::plan(request)?;

SighashVerifier::verify_signatures(&unsigned_tx, &signatures)?;
Expand All @@ -130,7 +136,7 @@ impl BitcoinCompiler {
psbt: &Proto::Psbt,
signatures: Vec<SignatureBytes>,
) -> SigningResult<Proto::SigningOutput<'static>> {
let PsbtRequest { unsigned_tx, .. } = PsbtRequest::build(input, psbt)?;
let PsbtRequest { unsigned_tx, .. } = PsbtRequest::<Context>::build(input, psbt)?;
let fee = unsigned_tx.fee()?;

SighashVerifier::verify_signatures(&unsigned_tx, &signatures)?;
Expand Down
15 changes: 10 additions & 5 deletions rust/chains/tw_bitcoin/src/modules/planner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,24 @@ use crate::modules::signing_request::SigningRequestBuilder;
use crate::modules::tx_builder::utxo_protobuf::parse_out_point;
use std::borrow::Cow;
use std::collections::HashMap;
use std::marker::PhantomData;
use tw_coin_entry::coin_context::CoinContext;
use tw_coin_entry::error::prelude::*;
use tw_coin_entry::modules::plan_builder::PlanBuilder;
use tw_coin_entry::signing_output_error;
use tw_proto::BitcoinV2::Proto;
use tw_utxo::context::UtxoContext;
use tw_utxo::modules::tx_planner::TxPlanner;
use tw_utxo::modules::utxo_selector::SelectResult;

pub mod psbt_planner;

pub struct BitcoinPlanner;
#[derive(Default)]
pub struct BitcoinPlanner<Context: UtxoContext> {
_phantom: PhantomData<Context>,
}

impl BitcoinPlanner {
impl<Context: UtxoContext> BitcoinPlanner<Context> {
pub fn plan_impl<'a>(
coin: &dyn CoinContext,
input: &Proto::SigningInput<'a>,
Expand All @@ -28,7 +33,7 @@ impl BitcoinPlanner {
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)
psbt_planner::PsbtPlanner::<Context>::plan_psbt(coin, input, psbt)
},
TransactionType::None => SigningError::err(SigningErrorType::Error_invalid_params)
.context("Either `TransactionBuilder` or `Psbt` should be set"),
Expand All @@ -40,7 +45,7 @@ impl BitcoinPlanner {
input: &Proto::SigningInput<'a>,
tx_builder: &Proto::TransactionBuilder<'a>,
) -> SigningResult<Proto::TransactionPlan<'a>> {
let request = SigningRequestBuilder::build(coin, input, tx_builder)?;
let request = SigningRequestBuilder::<Context>::build(coin, input, tx_builder)?;
let SelectResult { unsigned_tx, plan } = TxPlanner::plan(request)?;

// Prepare a map of source Inputs Proto `{ OutPoint -> Input }`.
Expand Down Expand Up @@ -93,7 +98,7 @@ impl BitcoinPlanner {
}
}

impl PlanBuilder for BitcoinPlanner {
impl<Context: UtxoContext> PlanBuilder for BitcoinPlanner<Context> {
type SigningInput<'a> = Proto::SigningInput<'a>;
type Plan<'a> = Proto::TransactionPlan<'a>;

Expand Down
Loading
Loading