Skip to content

Commit a4e121b

Browse files
committed
Implement signing PSBT taproot key spends
1 parent 0f0d49f commit a4e121b

3 files changed

Lines changed: 87 additions & 4 deletions

File tree

lib/Bitcoin/Crypto/Role/PSBT/Finalizer.pm

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ sub _should_finalize_P2TR_keypath
104104
return () unless $sig;
105105

106106
return (
107-
{type => 'witness', sigs => [$sig]},
107+
{type => 'witness', sigs => [$sig->value]},
108108
);
109109
}
110110

@@ -113,6 +113,7 @@ sub _should_finalize_P2TR_scriptpath
113113
my ($self, $input, $input_index) = @_;
114114

115115
# TODO: custom script tree - check public keys and signatures for leaves
116+
return ();
116117
}
117118

118119
sub _should_finalize_P2TR

lib/Bitcoin/Crypto/Role/PSBT/Signer.pm

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use warnings;
66
use Mooish::Base -standard, -role;
77
use Types::Common -sigs;
88

9+
use Bitcoin::Crypto qw(btc_script_tree);
10+
911
sub _add_partial_signature
1012
{
1113
my ($self, $input_index, $key, $signature) = @_;
@@ -57,12 +59,56 @@ sub _do_sign_P2WSH
5759
return 0;
5860
}
5961

62+
sub _do_sign_P2TR_keypath
63+
{
64+
my ($self, $key, $tx, $input, $input_index) = @_;
65+
66+
my $tree_root = $self->get_all_fields('PSBT_IN_TAP_MERKLE_ROOT', $input_index);
67+
my $tree = $tree_root ? btc_script_tree->new(tree => [{hash => $tree_root->value}]) : undef;
68+
69+
my $output_key = $key->get_public_key->get_taproot_output_key($tree ? $tree->get_merkle_root : ());
70+
return 0 unless $output_key->get_xonly_key eq $input->utxo->output->locking_script->get_raw_address;
71+
72+
my $signer = $tx->sign(
73+
signing_index => $input_index,
74+
($tree ? (script_tree => $tree) : ()),
75+
);
76+
my $sighash = $self->get_all_fields('PSBT_IN_SIGHASH_TYPE', $input_index);
77+
78+
# use transaction signer's ability to give us the signature
79+
my $signature = $signer->add_signature($key, sighash => $sighash ? $sighash->value : undef)
80+
->signature->[-1];
81+
82+
$self->add_field(
83+
type => 'PSBT_IN_TAP_KEY_SIG',
84+
index => $input_index,
85+
value => $signature,
86+
);
87+
88+
return 1;
89+
}
90+
91+
sub _do_sign_P2TR_scriptpath
92+
{
93+
my ($self, $key, $tx, $input, $input_index) = @_;
94+
95+
my $script = $self->get_all_fields('PSBT_IN_TAP_LEAF_SCRIPT', $input_index);
96+
return 0 unless $script;
97+
98+
# TODO: detect simple P2MS scripts with checksigadd
99+
100+
return 1;
101+
}
102+
60103
sub _do_sign_P2TR
61104
{
62105
my ($self, $key, $tx, $input, $input_index) = @_;
63106

64-
# TODO
65-
return 0;
107+
my $res = $self->_do_sign_P2TR_scriptpath($key, $tx, $input, $input_index);
108+
$res = $self->_do_sign_P2TR_keypath($key, $tx, $input, $input_index)
109+
unless $res;
110+
111+
return $res;
66112
}
67113

68114
sub _do_sign

t/PSBT/roles.t

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use Test2::V0;
2-
use Bitcoin::Crypto qw(btc_transaction btc_prv btc_psbt);
2+
use Bitcoin::Crypto qw(btc_transaction btc_prv btc_psbt btc_script_tree);
33
use Bitcoin::Crypto::Constants qw(:transaction);
44
use Bitcoin::Crypto::Util qw(to_format);
55
use Bitcoin::Crypto::Transaction::Output;
@@ -86,6 +86,42 @@ subtest 'should sign and set final signatures when finalizing P2WPKH input' => s
8686
ok lives { $psbt->get_transaction->verify }, 'verification passed';
8787
};
8888

89+
subtest 'should sign and set final signatures when finalizing P2TR key input' => sub {
90+
my $fake_merkle_root = [hex => "1d358125e4839360ade708a32c64548b0ffde92421bc3348eaf07a55ad775651"];
91+
my $tree = btc_script_tree->new(tree => [{hash => $fake_merkle_root}]);
92+
93+
my $psbt = build_psbt(
94+
[[$fake_txid, 0], [$fake_txid, 1]],
95+
@fields,
96+
{
97+
type => 'PSBT_IN_WITNESS_UTXO',
98+
index => 0,
99+
value => Bitcoin::Crypto::Transaction::Output->new(
100+
value => 5000,
101+
locking_script => [address => $priv->get_public_key->get_taproot_address],
102+
),
103+
},
104+
{
105+
type => 'PSBT_IN_WITNESS_UTXO',
106+
index => 1,
107+
value => Bitcoin::Crypto::Transaction::Output->new(
108+
value => 5000,
109+
locking_script => [address => $priv->get_public_key->get_taproot_address($tree)],
110+
),
111+
},
112+
{
113+
type => 'PSBT_IN_TAP_MERKLE_ROOT',
114+
index => 1,
115+
value => $fake_merkle_root,
116+
},
117+
);
118+
119+
is $psbt->sign($priv), 2, 'both inputs were signed';
120+
$psbt->finalize;
121+
122+
ok lives { $psbt->get_transaction->verify }, 'verification passed';
123+
};
124+
89125
done_testing;
90126

91127
sub build_psbt

0 commit comments

Comments
 (0)