Skip to content

Commit 1fe9c73

Browse files
feat(pczt): Add implementation of PCZT signing (#4576)
* feat(pczt): Add implementation of PCZT signing * feat(pczt): Add a successfully broadcasted tx test * feat(pczt): Address PR comments
1 parent ecfa2bb commit 1fe9c73

File tree

33 files changed

+1245
-111
lines changed

33 files changed

+1245
-111
lines changed

rust/Cargo.lock

Lines changed: 389 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/chains/tw_bitcoin/src/context.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
use crate::modules::protobuf_builder::standard_protobuf_builder::StandardProtobufBuilder;
66
use crate::modules::protobuf_builder::ProtobufBuilder;
7-
use crate::modules::psbt_request::standard_psbt_request_builder::StandardPsbtRequestBuilder;
8-
use crate::modules::psbt_request::PsbtRequestBuilder;
7+
use crate::modules::psbt_request::standard_psbt_request_handler::StandardPsbtRequestHandler;
8+
use crate::modules::psbt_request::PsbtRequestHandler;
99
use crate::modules::signing_request::standard_signing_request::StandardSigningRequestBuilder;
1010
use crate::modules::signing_request::SigningRequestBuilder;
1111
use tw_coin_entry::error::prelude::SigningResult;
@@ -21,7 +21,7 @@ use tw_utxo::transaction::standard_transaction::Transaction;
2121
pub trait BitcoinSigningContext: UtxoContext + Sized {
2222
type SigningRequestBuilder: SigningRequestBuilder<Self>;
2323
type ProtobufBuilder: ProtobufBuilder<Self>;
24-
type PsbtRequestBuilder: PsbtRequestBuilder<Self>;
24+
type PsbtRequestHandler: PsbtRequestHandler<Self>;
2525
}
2626

2727
#[derive(Default)]
@@ -31,6 +31,7 @@ impl UtxoContext for StandardBitcoinContext {
3131
type Address = StandardBitcoinAddress;
3232
type Transaction = Transaction;
3333
type FeeEstimator = StandardFeeEstimator<Self::Transaction>;
34+
type Psbt = bitcoin::psbt::Psbt;
3435

3536
fn addr_to_script_pubkey(
3637
addr: &Self::Address,
@@ -49,5 +50,5 @@ impl UtxoContext for StandardBitcoinContext {
4950
impl BitcoinSigningContext for StandardBitcoinContext {
5051
type SigningRequestBuilder = StandardSigningRequestBuilder;
5152
type ProtobufBuilder = StandardProtobufBuilder;
52-
type PsbtRequestBuilder = StandardPsbtRequestBuilder;
53+
type PsbtRequestHandler = StandardPsbtRequestHandler;
5354
}

rust/chains/tw_bitcoin/src/modules/compiler.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use crate::context::BitcoinSigningContext;
66
use crate::modules::protobuf_builder::ProtobufBuilder;
7-
use crate::modules::psbt_request::{PsbtRequest, PsbtRequestBuilder};
7+
use crate::modules::psbt_request::{PsbtRequest, PsbtRequestHandler};
88
use crate::modules::signing_request::SigningRequestBuilder;
99
use std::borrow::Cow;
1010
use std::marker::PhantomData;
@@ -53,7 +53,7 @@ impl<Context: BitcoinSigningContext> BitcoinCompiler<Context> {
5353
TxPlanner::plan(request)?.unsigned_tx
5454
},
5555
TransactionType::psbt(ref psbt) => {
56-
Context::PsbtRequestBuilder::build(&input, psbt)?.unsigned_tx
56+
Context::PsbtRequestHandler::parse_request(&input, psbt)?.unsigned_tx
5757
},
5858
TransactionType::None => {
5959
return SigningError::err(SigningErrorType::Error_invalid_params)
@@ -137,7 +137,8 @@ impl<Context: BitcoinSigningContext> BitcoinCompiler<Context> {
137137
psbt: &Proto::Psbt,
138138
signatures: Vec<SignatureBytes>,
139139
) -> SigningResult<Proto::SigningOutput<'static>> {
140-
let PsbtRequest { unsigned_tx, .. } = Context::PsbtRequestBuilder::build(input, psbt)?;
140+
let PsbtRequest { unsigned_tx, .. } =
141+
Context::PsbtRequestHandler::parse_request(input, psbt)?;
141142
let fee = unsigned_tx.fee()?;
142143

143144
SighashVerifier::verify_signatures(&unsigned_tx, &signatures)?;

rust/chains/tw_bitcoin/src/modules/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
pub mod compiler;
66
pub mod planner;
77
pub mod protobuf_builder;
8-
pub mod psbt;
98
pub mod psbt_request;
109
pub mod signer;
1110
pub mod signing_request;

rust/chains/tw_bitcoin/src/modules/planner/psbt_planner.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// Copyright © 2017 Trust Wallet.
44

55
use crate::context::BitcoinSigningContext;
6-
use crate::modules::psbt_request::{PsbtRequest, PsbtRequestBuilder};
6+
use crate::modules::psbt_request::{PsbtRequest, PsbtRequestHandler};
77
use crate::modules::signing_request::standard_signing_request::chain_info;
88
use crate::modules::tx_builder::script_parser::StandardScriptParser;
99
use crate::modules::tx_builder::BitcoinChainInfo;
@@ -32,7 +32,7 @@ impl<Context: BitcoinSigningContext> PsbtPlanner<Context> {
3232
) -> SigningResult<Proto::TransactionPlan<'static>> {
3333
let chain_info = chain_info(coin, &input.chain_info)?;
3434
let PsbtRequest { unsigned_tx, .. } =
35-
Context::PsbtRequestBuilder::build(input, psbt_input)?;
35+
Context::PsbtRequestHandler::parse_request(input, psbt_input)?;
3636

3737
let total_input = unsigned_tx.total_input()?;
3838
let fee_estimate = unsigned_tx.fee()?;

rust/chains/tw_bitcoin/src/modules/psbt.rs

Lines changed: 0 additions & 33 deletions
This file was deleted.

rust/chains/tw_bitcoin/src/modules/psbt_request/mod.rs

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,62 @@
22
//
33
// Copyright © 2017 Trust Wallet.
44

5-
use bitcoin::psbt::Psbt;
65
use std::marker::PhantomData;
76
use tw_coin_entry::error::prelude::*;
7+
use tw_memory::Data;
88
use tw_proto::BitcoinV2::Proto;
99
use tw_utxo::context::UtxoContext;
1010
use tw_utxo::transaction::unsigned_transaction::UnsignedTransaction;
1111

1212
pub mod output_psbt;
13-
pub mod standard_psbt_request_builder;
13+
pub mod standard_psbt_request_handler;
1414
pub mod utxo_psbt;
1515

16-
pub trait PsbtRequestBuilder<Context: UtxoContext> {
17-
fn build(
16+
pub trait PsbtRequestHandler<Context: UtxoContext> {
17+
/// Parses a PSBT request from Protobuf.
18+
fn parse_request(
1819
input: &Proto::SigningInput,
1920
psbt_input: &Proto::Psbt,
2021
) -> SigningResult<PsbtRequest<Context>>;
22+
23+
/// Finalizes the [Partially Signed Bitcoin Transaction](Psbt)
24+
/// by updating the final `script_sig` and/or `witness`.
25+
fn update_signed(
26+
psbt: &mut Context::Psbt,
27+
signed_tx: &Context::Transaction,
28+
) -> SigningResult<()>;
29+
30+
/// Serializes the PSBT into bytes.
31+
fn serialize_psbt(psbt: &Context::Psbt) -> SigningResult<Data>;
2132
}
2233

2334
pub struct PsbtRequest<Context: UtxoContext> {
24-
pub psbt: Psbt,
35+
pub psbt: Context::Psbt,
2536
pub unsigned_tx: UnsignedTransaction<Context::Transaction>,
26-
_phantom: PhantomData<Context>,
37+
pub _phantom: PhantomData<Context>,
2738
}
2839

2940
pub struct NoPsbtRequestBuilder;
3041

31-
impl<Context: UtxoContext> PsbtRequestBuilder<Context> for NoPsbtRequestBuilder {
32-
fn build(
42+
impl<Context: UtxoContext> PsbtRequestHandler<Context> for NoPsbtRequestBuilder {
43+
fn parse_request(
3344
_input: &Proto::SigningInput,
3445
_psbt_input: &Proto::Psbt,
3546
) -> SigningResult<PsbtRequest<Context>> {
3647
SigningError::err(SigningErrorType::Error_not_supported)
3748
.context("PSBT signing is not supported")
3849
}
50+
51+
fn update_signed(
52+
_psbt: &mut Context::Psbt,
53+
_signed_tx: &Context::Transaction,
54+
) -> SigningResult<()> {
55+
SigningError::err(SigningErrorType::Error_not_supported)
56+
.context("PSBT signing is not supported")
57+
}
58+
59+
fn serialize_psbt(_psbt: &Context::Psbt) -> SigningResult<Data> {
60+
SigningError::err(SigningErrorType::Error_not_supported)
61+
.context("PSBT signing is not supported")
62+
}
3963
}

rust/chains/tw_bitcoin/src/modules/psbt_request/standard_psbt_request_builder.rs renamed to rust/chains/tw_bitcoin/src/modules/psbt_request/standard_psbt_request_handler.rs

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,26 @@
44

55
use crate::modules::psbt_request::output_psbt::OutputPsbt;
66
use crate::modules::psbt_request::utxo_psbt::UtxoPsbt;
7-
use crate::modules::psbt_request::{PsbtRequest, PsbtRequestBuilder};
7+
use crate::modules::psbt_request::{PsbtRequest, PsbtRequestHandler};
88
use crate::modules::signing_request::standard_signing_request::StandardSigningRequestBuilder;
9-
use bitcoin::psbt::Psbt;
109
use std::marker::PhantomData;
1110
use tw_coin_entry::error::prelude::*;
11+
use tw_memory::Data;
1212
use tw_proto::BitcoinV2::Proto;
1313
use tw_utxo::context::UtxoContext;
1414
use tw_utxo::transaction::standard_transaction::builder::TransactionBuilder;
1515
use tw_utxo::transaction::standard_transaction::Transaction;
16+
use tw_utxo::transaction::transaction_interface::{TransactionInterface, TxInputInterface};
1617

17-
pub struct StandardPsbtRequestBuilder;
18+
pub use bitcoin::psbt::Psbt;
1819

19-
impl<Context> PsbtRequestBuilder<Context> for StandardPsbtRequestBuilder
20+
pub struct StandardPsbtRequestHandler;
21+
22+
impl<Context> PsbtRequestHandler<Context> for StandardPsbtRequestHandler
2023
where
21-
Context: UtxoContext<Transaction = Transaction>,
24+
Context: UtxoContext<Transaction = Transaction, Psbt = Psbt>,
2225
{
23-
fn build(
26+
fn parse_request(
2427
input: &Proto::SigningInput,
2528
psbt_input: &Proto::Psbt,
2629
) -> SigningResult<PsbtRequest<Context>> {
@@ -66,4 +69,34 @@ where
6669
_phantom: PhantomData,
6770
})
6871
}
72+
73+
fn update_signed(
74+
psbt: &mut Context::Psbt,
75+
signed_tx: &Context::Transaction,
76+
) -> SigningResult<()> {
77+
for (signed_txin, utxo_psbt) in signed_tx.inputs().iter().zip(psbt.inputs.iter_mut()) {
78+
if signed_txin.has_script_sig() {
79+
utxo_psbt.final_script_sig = Some(bitcoin::ScriptBuf::from_bytes(
80+
signed_txin.script_sig().to_vec(),
81+
));
82+
}
83+
84+
if let Some(witness) = signed_txin.witness() {
85+
if witness.is_empty() {
86+
continue;
87+
}
88+
89+
let mut final_witness = bitcoin::Witness::new();
90+
for witness_item in witness.as_items() {
91+
final_witness.push(bitcoin::ScriptBuf::from_bytes(witness_item.to_vec()));
92+
}
93+
utxo_psbt.final_script_witness = Some(final_witness);
94+
}
95+
}
96+
Ok(())
97+
}
98+
99+
fn serialize_psbt(psbt: &Context::Psbt) -> SigningResult<Data> {
100+
Ok(psbt.serialize())
101+
}
69102
}

rust/chains/tw_bitcoin/src/modules/psbt_request/utxo_psbt.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ impl<'a> UtxoPsbt<'a> {
9696
},
9797
StandardScript::P2SH(_) | StandardScript::P2WSH(_) => {
9898
SigningError::err(SigningErrorType::Error_not_supported)
99-
.context("P2SH and P2WSH scriptPubkey's are not supported yet")
99+
.context("P2SH and P2WSH scriptPubkeys are not supported yet")
100100
},
101101
StandardScript::OpReturn(_) => SigningError::err(SigningErrorType::Error_invalid_utxo)
102102
.context("Cannot spend an OP_RETURN output"),

rust/chains/tw_bitcoin/src/modules/signer.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44

55
use crate::context::BitcoinSigningContext;
66
use crate::modules::protobuf_builder::ProtobufBuilder;
7-
use crate::modules::psbt::update_psbt_signed;
8-
use crate::modules::psbt_request::{PsbtRequest, PsbtRequestBuilder};
7+
use crate::modules::psbt_request::{PsbtRequest, PsbtRequestHandler};
98
use crate::modules::signing_request::SigningRequestBuilder;
109
use std::borrow::Cow;
1110
use std::marker::PhantomData;
@@ -89,7 +88,7 @@ impl<Context: BitcoinSigningContext> BitcoinSigner<Context> {
8988
mut psbt,
9089
unsigned_tx,
9190
..
92-
} = Context::PsbtRequestBuilder::build(input, psbt_input)?;
91+
} = Context::PsbtRequestHandler::parse_request(input, psbt_input)?;
9392

9493
let fee = unsigned_tx.fee()?;
9594

@@ -102,7 +101,7 @@ impl<Context: BitcoinSigningContext> BitcoinSigner<Context> {
102101
let signed_tx =
103102
TxSigner::sign_tx(unsigned_tx, &keys_manager).context("Error signing transaction")?;
104103

105-
update_psbt_signed(&mut psbt, &signed_tx);
104+
Context::PsbtRequestHandler::update_signed(&mut psbt, &signed_tx)?;
106105

107106
Ok(Proto::SigningOutput {
108107
transaction: Context::ProtobufBuilder::tx_to_proto(&signed_tx),
@@ -113,7 +112,7 @@ impl<Context: BitcoinSigningContext> BitcoinSigner<Context> {
113112
fee,
114113
weight: signed_tx.weight() as u64,
115114
psbt: Some(Proto::Psbt {
116-
psbt: Cow::from(psbt.serialize()),
115+
psbt: Cow::from(Context::PsbtRequestHandler::serialize_psbt(&psbt)?),
117116
}),
118117
..Proto::SigningOutput::default()
119118
})

0 commit comments

Comments
 (0)