Skip to content

Commit 167b436

Browse files
committed
PSBT signer: modify v2 modifiable flags based on sighash
1 parent bf0cea2 commit 167b436

2 files changed

Lines changed: 40 additions & 8 deletions

File tree

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

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

99
use Bitcoin::Crypto qw(btc_script_tree);
10+
use Bitcoin::Crypto::Constants qw(:sighash);
1011
use Bitcoin::Crypto::Transaction::Flags;
1112

1213
sub _input_has_witness_utxo
@@ -19,14 +20,43 @@ sub _input_has_witness_utxo
1920

2021
sub _add_partial_signature
2122
{
22-
my ($self, $input_index, $key, $signature) = @_;
23+
my ($self, $input_index, $key, $signature, $sighash) = @_;
2324

2425
$self->add_field(
2526
type => 'PSBT_IN_PARTIAL_SIG',
2627
index => $input_index,
2728
key => $key->get_public_key,
2829
value => $signature,
2930
);
31+
32+
if ($self->version == 2) {
33+
34+
# NOTE: SIGHASH_DEFAULT may not be correct for pre-taproot, but it's only
35+
# used to set modifiable flags, which will be the same for SIGHASH_ALL
36+
$sighash = $sighash ? $sighash->value : SIGHASH_ALL;
37+
38+
my $modifiable = $self->get_all_fields('PSBT_GLOBAL_TX_MODIFIABLE');
39+
$modifiable //= $self
40+
->add_field(type => 'PSBT_GLOBAL_TX_MODIFIABLE', value => {})
41+
->get_field('PSBT_GLOBAL_TX_MODIFIABLE');
42+
43+
my $value = $modifiable->value;
44+
45+
if (!($sighash & SIGHASH_ANYONECANPAY)) {
46+
$value->{inputs_modifiable} = !!0;
47+
}
48+
49+
if (!($sighash & SIGHASH_NONE)) {
50+
$value->{outputs_modifiable} = !!0;
51+
}
52+
53+
if (!($sighash & SIGHASH_SINGLE)) {
54+
$value->{has_sighash_single} = !!1;
55+
}
56+
57+
# update the value back in the PSBT
58+
$modifiable->set_value($value);
59+
}
3060
}
3161

3262
sub _do_sign_P2PKH
@@ -43,7 +73,7 @@ sub _do_sign_P2PKH
4373
my $signature = $signer->add_signature($key, sighash => $sighash ? $sighash->value : undef)
4474
->signature->[-1];
4575

46-
$self->_add_partial_signature($input_index, $key, $signature);
76+
$self->_add_partial_signature($input_index, $key, $signature, $sighash);
4777
return 1;
4878
}
4979

@@ -72,7 +102,7 @@ sub _do_sign_P2WPKH
72102
my $signature = $signer->add_signature($key, sighash => $sighash ? $sighash->value : undef)
73103
->signature->[-1];
74104

75-
$self->_add_partial_signature($input_index, $key, $signature);
105+
$self->_add_partial_signature($input_index, $key, $signature, $sighash);
76106
return 1;
77107
}
78108

@@ -153,8 +183,7 @@ sub _do_sign
153183
signature_for sign => (
154184
method => !!1,
155185
positional => [
156-
(InstanceOf ['Bitcoin::Crypto::Key::Private'])
157-
| (InstanceOf ['Bitcoin::Crypto::Key::ExtPrivate']),
186+
InstanceOf ['Bitcoin::Crypto::Key::Private']
158187
],
159188
);
160189

@@ -175,9 +204,6 @@ sub sign
175204
}
176205

177206
return $signed;
178-
179-
# TODO: this note
180-
# For PSBTv2s, a signer must update the PSBT_GLOBAL_TX_MODIFIABLE field after signing inputs so that it accurately reflects the state of the PSBT. If the Signer added a signature that does not use SIGHASH_ANYONECANPAY, the Input Modifiable flag must be set to False. If the Signer added a signature that does not use SIGHASH_NONE, the Outputs Modifiable flag must be set to False. If the Signer added a signature that uses SIGHASH_SINGLE, the Has SIGHASH_SINGLE flag must be set to True.
181207
}
182208

183209
1;

t/PSBT/roles.t

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ subtest 'should sign and set final signatures when finalizing P2PKH input' => su
6161
);
6262

6363
is $psbt->sign($priv), 1, 'an input was signed';
64+
65+
my $modifiable = $psbt->get_field('PSBT_GLOBAL_TX_MODIFIABLE');
66+
ok !$modifiable->{inputs_modifiable}, 'inputs modifiable ok';
67+
ok !$modifiable->{outputs_modifiable}, 'outputs modifiable ok';
68+
ok !$modifiable->{has_sighash_single}, 'sighash single ok';
69+
6470
$psbt->finalize;
6571

6672
ok lives { $psbt->get_transaction->verify }, 'verification passed';

0 commit comments

Comments
 (0)