File Coverage

blib/lib/Bitcoin/Crypto/Transaction/Sign.pm
Criterion Covered Total %
statement 112 113 99.1
branch 31 44 70.4
condition 9 16 56.2
subroutine 23 23 100.0
pod 0 1 0.0
total 175 197 88.8


line stmt bran cond sub pod time code
1             package Bitcoin::Crypto::Transaction::Sign;
2             $Bitcoin::Crypto::Transaction::Sign::VERSION = '2.000_01'; # TRIAL
3             $Bitcoin::Crypto::Transaction::Sign::VERSION = '2.00001';
4 16     16   206 use v5.10;
  16         59  
5 16     16   88 use strict;
  16         40  
  16         336  
6 16     16   96 use warnings;
  16         71  
  16         395  
7              
8 16     16   84 use Moo;
  16         34  
  16         146  
9 16     16   7853 use Mooish::AttributeBuilder -standard;
  16         41  
  16         103  
10              
11 16     16   1929 use Bitcoin::Crypto qw(btc_script);
  16         55  
  16         731  
12 16     16   117 use Bitcoin::Crypto::Exception;
  16         42  
  16         308  
13 16     16   80 use Bitcoin::Crypto::Constants;
  16         34  
  16         394  
14 16     16   95 use Bitcoin::Crypto::Types qw(InstanceOf ByteStr PositiveInt PositiveOrZeroInt BitcoinScript Tuple Bool);
  16         42  
  16         105  
15              
16             has param 'transaction' => (
17             isa => InstanceOf ['Bitcoin::Crypto::Transaction'],
18             );
19              
20             has param 'key' => (
21             isa => InstanceOf ['Bitcoin::Crypto::Key::Private'],
22             );
23              
24             has param 'signing_index' => (
25             isa => PositiveOrZeroInt,
26             );
27              
28             has option 'redeem_script' => (
29             coerce => BitcoinScript,
30             );
31              
32             has option 'multisig' => (
33             coerce => Tuple [PositiveInt, PositiveInt],
34             );
35              
36             has param 'sighash' => (
37             isa => PositiveInt,
38             default => Bitcoin::Crypto::Constants::sighash_all,
39             );
40              
41             has field 'input' => (
42             lazy => sub {
43             my $self = shift;
44             return $self->transaction->inputs->[$self->signing_index];
45             },
46             );
47              
48             has field 'segwit' => (
49             isa => Bool,
50             writer => 1,
51             lazy => sub {
52             my $self = shift;
53             return $self->input->utxo->output->locking_script->is_native_segwit;
54             },
55             );
56              
57             sub _get_signature
58             {
59 16     16   51 my ($self, $subscript) = @_;
60              
61 16 100       249 my $digest = $self->transaction->get_digest(
62             signing_index => $self->signing_index,
63             sighash => $self->sighash,
64             ($subscript ? (signing_subscript => $subscript) : ()),
65             );
66              
67 16         118 my $signature = $self->key->sign_message($digest);
68 16         159 $signature .= pack 'C', $self->sighash;
69              
70 16         125 return $signature;
71             }
72              
73             sub _get_old_signature
74             {
75 7     7   19 my ($self) = @_;
76              
77 7 100       192 if ($self->segwit) {
78 5   100     130 return [@{$self->input->witness // []}];
  5         90  
79             }
80             else {
81 2         96 my $old_script = $self->input->signature_script->operations;
82 2         6 my @result;
83 2         7 foreach my $part (@$old_script) {
84 4 100       31 if ($part->[0]->name =~ /^OP_PUSHDATA/) {
    50          
85              
86             # using OP_PUSHDATA, as operations present most data pushes as this
87 2         5 push @result, $part->[2];
88             }
89             elsif ($part->[0]->name =~ /^OP_\d+$/) {
90              
91             # first index is the whole op, so this gets the push from OP_0 - OP_15
92 2         7 push @result, $part->[1];
93             }
94             else {
95 0         0 die sprintf 'previous signature not a PUSH operation (%s)', $part->[0]->name;
96             }
97             }
98              
99 2         9 return \@result;
100             }
101             }
102              
103             sub _set_signature
104             {
105 19     19   61 my ($self, $signature_parts, $append) = @_;
106              
107 19 100       400 if ($self->segwit) {
108 13 100       197 if (!$append) {
109 8         138 $self->input->set_witness([]);
110             }
111              
112 13         635 push @{$self->input->witness}, @$signature_parts;
  13         217  
113             }
114             else {
115 6 50       145 if (!$append) {
116 6         30 my $script = btc_script->new;
117 6         115 $self->input->set_signature_script($script);
118             }
119              
120 6         396 foreach my $part (@$signature_parts) {
121 12         207 $self->input->signature_script->push($part);
122             }
123             }
124             }
125              
126             # NOTE: should only be run in P2SH after initial checks. P2SH script is run
127             # (without the transaction access) to verify whether HASH160 checksum matches.
128             # If it does, witness program is a is a nested segwit script.
129             sub _check_segwit_nested
130             {
131 5     5   10 my ($self) = @_;
132              
133             my $check_locking_script = sub {
134 9     9   17 my ($program) = @_;
135              
136 9         169 my $runner = $self->input->utxo->output->locking_script->run(
137             [$program->to_serialized]
138             );
139              
140 9         37 return $runner->success;
141 5         27 };
142              
143 5         28 my @to_check = (
144             $self->key->get_public_key->witness_program, # P2SH(P2WPKH)
145             );
146              
147             # P2SH(P2WSH)
148 5 100       68 push @to_check, $self->redeem_script->witness_program
149             if $self->has_redeem_script;
150              
151 5         17 foreach my $program (@to_check) {
152 9 100       24 if ($check_locking_script->($program)) {
153 3         24 return $program;
154             }
155             }
156              
157 2         23 return undef;
158             }
159              
160             sub _sign_P2PK
161             {
162 2     2   7 my ($self, $signature) = @_;
163              
164 2   33     16 $self->_set_signature(
165             [
166             $signature // $self->_get_signature()
167             ]
168             );
169             }
170              
171             sub _sign_P2PKH
172             {
173 2     2   15 my ($self, $signature) = @_;
174              
175 2   33     13 $self->_set_signature(
176             [
177             $signature // $self->_get_signature(),
178             $self->key->get_public_key->to_serialized
179             ]
180             );
181             }
182              
183             sub _sign_P2MS
184             {
185 7     7   30 my ($self, $signature) = @_;
186              
187 7 50       32 die 'trying to sign payout from P2MS but no multisig was specified'
188             unless $self->has_multisig;
189              
190 7         19 my ($this_signature, $total_signatures) = @{$self->multisig};
  7         27  
191              
192 7         25 my $sig = $self->_get_old_signature;
193 7         95 $sig->[0] = '';
194              
195 7         29 foreach my $sig_num (1 .. $total_signatures) {
196 13 100       38 if ($sig_num == $this_signature) {
197              
198             # set this signature
199 7   33     32 $sig->[$sig_num] = $signature // $self->_get_signature();
200             }
201             else {
202             # Do not touch other signatures if they exist at all
203 6   100     34 $sig->[$sig_num] //= "\x00";
204             }
205             }
206              
207             # cut off any remaining signature parts (like P2SH serialized script)
208 7         32 $#$sig = $total_signatures;
209              
210 7         31 $self->_set_signature($sig);
211             }
212              
213             sub _sign_P2SH
214             {
215 5     5   15 my ($self) = @_;
216              
217 5         17 my $segwit_nested = $self->_check_segwit_nested;
218 5 100       31 if (defined $segwit_nested) {
219 3         72 $self->set_segwit(!!1);
220              
221             # for nested segwit, signature script need to be present before signing
222             # for proper transaction digests to be generated
223 3         187 $self->input->set_signature_script(
224             btc_script->new->push($segwit_nested->to_serialized)
225             );
226 3         186 $self->_sign_type($segwit_nested->type, $self->_get_signature($segwit_nested->to_serialized));
227             }
228             else {
229 2 50       14 die 'trying to sign payout from P2SH but no redeem_script was specified'
230             unless $self->has_redeem_script;
231 2         10 my $redeem_script = $self->redeem_script;
232              
233 2 50       7 die 'cannot automatically sign with a non-standard P2SH redeem script'
234             unless $redeem_script->has_type;
235 2 50       72 die 'P2SH nested inside P2SH'
236             if $redeem_script->type eq 'P2SH';
237              
238 2         57 $self->_sign_type($redeem_script->type, $self->_get_signature($redeem_script->to_serialized));
239 2         49 $self->input->signature_script->push($redeem_script->to_serialized);
240             }
241             }
242              
243             sub _sign_P2WPKH
244             {
245 3     3   17 my ($self, $signature) = @_;
246              
247 3   66     24 $self->_set_signature(
248             [
249             $signature // $self->_get_signature(),
250             $self->key->get_public_key->to_serialized
251             ]
252             );
253             }
254              
255             sub _sign_P2WSH
256             {
257 5     5   25 my ($self) = @_;
258              
259 5 50       29 die 'trying to sign payout from P2WSH but no redeem_script was specified'
260             unless $self->has_redeem_script;
261              
262 5         17 my $redeem_script = $self->redeem_script;
263 5 50       19 die 'cannot automatically sign with a non-standard P2WSH redeem script'
264             unless $redeem_script->has_type;
265 5 50       197 die 'P2SH nested inside P2WSH'
266             if $redeem_script->type eq 'P2SH';
267 5 50       144 die 'P2WSH nested inside P2WSH'
268             if $redeem_script->type eq 'P2WSH';
269              
270 5         140 $self->_sign_type($redeem_script->type, $self->_get_signature($redeem_script->to_serialized));
271 5         84 $self->_set_signature([$redeem_script->to_serialized], !!1);
272             }
273              
274             sub _sign_type
275             {
276 24     24   244 my ($self, $type, @rest) = @_;
277              
278 24         76 my $method = "_sign_$type";
279 24 50       121 Bitcoin::Crypto::Exception::ScriptType->raise(
280             "don't know how to sign standard script type $type"
281             ) unless $self->can($method);
282              
283 24         104 return $self->$method(@rest);
284             }
285              
286             sub sign
287             {
288 14     14 0 54 my ($self) = @_;
289              
290             Bitcoin::Crypto::Exception::Sign->trap_into(
291             sub {
292 14 50   14   367 die 'no such input' if !$self->input;
293              
294 14         247 my $utxo = $self->input->utxo->output;
295              
296 14 50       186 die 'cannot automatically sign a non-standard locking script'
297             if !$utxo->is_standard;
298              
299 14         437 $self->_sign_type($utxo->locking_script->type);
300             },
301 14         228 "Can't sign transaction input " . $self->signing_index
302             );
303             }
304              
305             1;
306