File Coverage

blib/lib/Bitcoin/Crypto/Helpers.pm
Criterion Covered Total %
statement 98 117 83.7
branch 23 36 63.8
condition 0 6 0.0
subroutine 19 20 95.0
pod 0 7 0.0
total 140 186 75.2


line stmt bran cond sub pod time code
1             package Bitcoin::Crypto::Helpers;
2             $Bitcoin::Crypto::Helpers::VERSION = '2.000_01'; # TRIAL
3             $Bitcoin::Crypto::Helpers::VERSION = '2.00001';
4 36     36   151415 use v5.10;
  36         151  
5 36     36   250 use strict;
  36         131  
  36         791  
6 36     36   185 use warnings;
  36         94  
  36         1186  
7 36     36   208 use Exporter qw(import);
  36         108  
  36         1476  
8 36     36   230 use List::Util qw(max);
  36         80  
  36         4416  
9 36     36   11843 use Crypt::PK::ECC;
  36         290094  
  36         1889  
10 36     36   286 use Carp qw(carp);
  36         96  
  36         1686  
11              
12 36     36   8479 use Bitcoin::Crypto::Constants;
  36         84  
  36         1108  
13 36     36   14768 use Bitcoin::Crypto::Exception;
  36         283  
  36         3645  
14              
15             BEGIN {
16 36     36   54733 require Math::BigInt;
17              
18             # Version 1.6003 of optional GMP is required for the from_bytes / to_bytes implementations
19 36 50       1304157 if (eval { require Math::BigInt::GMP; Math::BigInt::GMP->VERSION('1.6003'); 1 }) {
  36         6757  
  0         0  
  0         0  
20 0         0 Math::BigInt->import(try => 'GMP,LTM');
21             }
22             else {
23 36         257 Math::BigInt->import(try => 'LTM');
24             }
25             }
26              
27             our @EXPORT_OK = qw(
28             pad_hex
29             ensure_length
30             add_ec_points
31             pack_varint
32             unpack_varint
33             carp_once
34             );
35              
36             our @CARP_NOT;
37             my %warned;
38              
39             sub carp_once
40             {
41 21     21 0 57 my ($msg) = @_;
42              
43 21 100       91 return if $warned{$msg};
44 3         13 $warned{$msg} = 1;
45 3         13 local @CARP_NOT = ((caller)[0]);
46 3         124 carp($msg);
47             }
48              
49             sub pad_hex
50             {
51 373     373 0 6653 my ($hex) = @_;
52 373         909 $hex =~ s/\A0x//;
53 373         895 $hex =~ tr/0-9a-fA-F//cd;
54 373         2447 return '0' x (length($hex) % 2) . $hex;
55             }
56              
57             sub ensure_length
58             {
59 1207     1207 0 5396 my ($packed, $bytelen) = @_;
60 1207         2269 my $missing = $bytelen - length $packed;
61              
62 1207 100       2699 Bitcoin::Crypto::Exception->raise(
63             "packed string exceeds maximum number of bytes allowed ($bytelen)"
64             ) if $missing < 0;
65              
66 1206         5399 return pack("x$missing") . $packed;
67             }
68              
69             sub pack_varint
70             {
71 445     445 0 10447 my ($value) = @_;
72              
73 445 50       1039 Bitcoin::Crypto::Exception->raise(
74             "VarInt must be positive or zero"
75             ) if $value < 0;
76              
77 445 100       1026 if ($value <= 0xfc) {
    100          
    50          
78 440         1571 return pack 'C', $value;
79             }
80             elsif ($value <= 0xffff) {
81 4         32 return "\xfd" . pack 'v', $value;
82             }
83             elsif ($value <= 0xffffffff) {
84 1         9 return "\xfe" . pack 'V', $value;
85             }
86             else {
87             # 32 bit archs should not reach this
88 0         0 return "\xff" . (pack 'V', $value & 0xffffffff) . (pack 'V', $value >> 32);
89             }
90             }
91              
92             sub unpack_varint
93             {
94 137     137 0 443 my ($stream) = @_;
95              
96 137         320 my $value = ord substr $stream, 0, 1, '';
97 137         226 my $length = 1;
98              
99 137 100       499 if ($value == 0xfd) {
    100          
    50          
100 2 50       10 Bitcoin::Crypto::Exception->raise(
101             "cannot unpack VarInt: not enough data in stream"
102             ) if length $stream < 2;
103              
104 2         9 $value = unpack 'v', substr $stream, 0, 2;
105 2         4 $length += 2;
106             }
107             elsif ($value == 0xfe) {
108 1 50       13 Bitcoin::Crypto::Exception->raise(
109             "cannot unpack VarInt: not enough data in stream"
110             ) if length $stream < 4;
111              
112 1         8 $value = unpack 'V', substr $stream, 0, 4;
113 1         2 $length += 4;
114             }
115             elsif ($value == 0xff) {
116 0         0 Bitcoin::Crypto::Exception->raise(
117             "cannot unpack VarInt: no 64 bit support"
118             ) if !Bitcoin::Crypto::Constants::is_64bit;
119              
120 0 0       0 Bitcoin::Crypto::Exception->raise(
121             "cannot unpack VarInt: not enough data in stream"
122             ) if length $stream < 8;
123              
124 0         0 my $lower = unpack 'V', substr $stream, 0, 4;
125 0         0 my $higher = unpack 'V', substr $stream, 4, 4;
126 0         0 $value = ($higher << 32) + $lower;
127 0         0 $length += 8;
128             }
129              
130 137         470 return ($length, $value);
131             }
132              
133             # Self-contained implementation on elliptic curve points addition.
134             # This is only a partial implementation, but should be good enough for key
135             # derivation needs. Code borrowed from the archived Math::EllipticCurve::Prime
136             # module. Returns undef for infinity points, expects to get a valid uncompressed
137             # point data on input
138             sub add_ec_points
139             {
140 17     17 0 57 my ($point1, $point2) = @_;
141              
142 17         32 my $curve_size = Bitcoin::Crypto::Constants::key_max_length;
143 17         67 my $curve_data = Crypt::PK::ECC->new->generate_key(Bitcoin::Crypto::Constants::curve_name)->curve2hash;
144 17         51750 my $p = Math::BigInt->from_hex($curve_data->{prime});
145 17         3698 my $a = Math::BigInt->from_hex($curve_data->{A});
146              
147             my $add_points = sub {
148 17     17   63 my ($x1, $x2, $y1, $lambda) = @_;
149              
150 17         52 my $x = $lambda->copy->bmodpow(2, $p);
151 17         3701 $x->bsub($x1);
152 17         1506 $x->bsub($x2);
153 17         1387 $x->bmod($p);
154              
155 17         1315 my $y = $x1->copy->bsub($x);
156 17         1796 $y->bmul($lambda);
157 17         871 $y->bsub($y1);
158 17         1412 $y->bmod($p);
159              
160 17         1444 return {x => $x, y => $y};
161 17         2586 };
162              
163             my $double = sub {
164 0     0   0 my ($x, $y) = @_;
165 0         0 my $lambda = $x->copy->bmodpow(2, $p);
166 0         0 $lambda->bmul(3);
167 0         0 $lambda->badd($a);
168 0         0 my $bottom = $y->copy->bmul(2)->bmodinv($p);
169 0         0 $lambda->bmul($bottom)->bmod($p);
170              
171 0         0 return $add_points->($x, $x, $y, $lambda);
172 17         82 };
173              
174 17         53 my $format = "(a$curve_size)*";
175 17         103 my ($px1, $py1) = map { Math::BigInt->from_bytes($_) } unpack $format, substr $point1, 1;
  34         1690  
176 17         1600 my ($px2, $py2) = map { Math::BigInt->from_bytes($_) } unpack $format, substr $point2, 1;
  34         1488  
177              
178             my $ret = sub {
179 17 50 0 17   60 if ($px1->bcmp($px2)) {
    0 0        
180 17         418 my $lambda = $py2->copy->bsub($py1);
181 17         2193 my $bottom = $px2->copy->bsub($px1)->bmodinv($p);
182 17         5698 $lambda->bmul($bottom)->bmod($p);
183              
184 17         2247 return $add_points->($px1, $px2, $py1, $lambda);
185             }
186             elsif ($py1->is_zero || $py2->is_zero || $py1->bcmp($py2)) {
187 0         0 return undef;
188             }
189             else {
190 0         0 return $double->($px1, $py1);
191             }
192             }
193 17         1616 ->();
194              
195 17         190 my $exp_x = $ret->{x}->to_bytes;
196 17         546 my $exp_y = $ret->{y}->to_bytes;
197              
198 17 50       427 return defined $ret
199             ? "\x04" .
200             ensure_length($exp_x, $curve_size) .
201             ensure_length($exp_y, $curve_size)
202             : undef;
203             }
204              
205             # not exported - used exclusively by the internal FormatDesc type
206              
207             sub parse_formatdesc
208             {
209 246     246 0 184039 my ($type, $data) = @{$_[0]};
  246         638  
210              
211 246 100       815 if ($type eq 'hex') {
    50          
212 209         659 $data = pack 'H*', pad_hex $data;
213             }
214             elsif ($type eq 'base58') {
215 37         196 require Bitcoin::Crypto::Base58;
216 37         160 $data = Bitcoin::Crypto::Base58::decode_base58check($data);
217             }
218              
219 244         1373 return $data;
220             }
221              
222             1;
223              
224             # Internal use only
225