File Coverage

blib/lib/Bitcoin/Crypto/Helpers.pm
Criterion Covered Total %
statement 82 94 87.2
branch 8 14 57.1
condition 3 9 33.3
subroutine 20 21 95.2
pod 0 7 0.0
total 113 145 77.9


line stmt bran cond sub pod time code
1             package Bitcoin::Crypto::Helpers;
2             $Bitcoin::Crypto::Helpers::VERSION = '1.008';
3 17     17   212709 use v5.10;
  17         98  
4 17     17   97 use strict;
  17         37  
  17         378  
5 17     17   113 use warnings;
  17         39  
  17         495  
6 17     17   104 use Exporter qw(import);
  17         41  
  17         669  
7 17     17   7923 use Crypt::Digest::RIPEMD160 qw(ripemd160);
  17         38406  
  17         1022  
8 17     17   5850 use Crypt::Digest::SHA256 qw(sha256);
  17         8053  
  17         946  
9 17     17   110 use List::Util qw(max);
  17         47  
  17         1656  
10 17     17   8071 use Crypt::PK::ECC;
  17         134791  
  17         860  
11              
12 17     17   5851 use Bitcoin::Crypto::Config;
  17         45  
  17         483  
13 17     17   5751 use Bitcoin::Crypto::Exception;
  17         113  
  17         1712  
14              
15             BEGIN {
16 17     17   24496 require Math::BigInt;
17              
18             # Version 1.6003 of optional GMP is required for the from_bytes / to_bytes implementations
19 17 50       611935 if (eval { require Math::BigInt::GMP; Math::BigInt::GMP->VERSION('1.6003'); 1 }) {
  17         3390  
  0         0  
  0         0  
20 0         0 Math::BigInt->import(try => 'GMP,LTM');
21             }
22             else {
23 17         144 Math::BigInt->import(try => 'LTM');
24             }
25             }
26              
27             our @EXPORT_OK = qw(
28             new_bigint
29             pad_hex
30             ensure_length
31             verify_bytestring
32             hash160
33             hash256
34             add_ec_points
35             );
36              
37             sub new_bigint
38             {
39 907     907 0 19732 my ($bytes) = @_;
40 907         3883 return Math::BigInt->from_hex(unpack "H*", $bytes);
41             }
42              
43             sub pad_hex
44             {
45 42     42 0 5602 my ($hex) = @_;
46 42         138 $hex =~ s/^0x//;
47 42         406 return "0" x (length($hex) % 2) . $hex;
48             }
49              
50             sub ensure_length
51             {
52 1246     1246 0 11077 my ($packed, $bytelen) = @_;
53 1246         2201 my $missing = $bytelen - length $packed;
54              
55 1246 100       2517 Bitcoin::Crypto::Exception->raise(
56             "packed string exceeds maximum number of bytes allowed ($bytelen)"
57             ) if $missing < 0;
58              
59 1245         4993 return pack("x$missing") . $packed;
60             }
61              
62             sub verify_bytestring
63             {
64 447     447 0 1543 my ($string) = @_;
65              
66 447 100 100     2135 Bitcoin::Crypto::Exception->raise(
67             "invalid input value, expected string"
68             ) if !defined $string || ref $string;
69              
70 445         3734 my @characters = split //, $string;
71              
72             Bitcoin::Crypto::Exception->raise(
73             "string contains characters with numeric values over 255 and cannot be used as a byte string"
74 445 50       1078 ) if (grep { ord($_) > 255 } @characters) > 0;
  20618         32893  
75             }
76              
77             sub hash160
78             {
79 341     341 0 863 my ($data) = @_;
80              
81 341         940 return ripemd160(sha256($data));
82             }
83              
84             sub hash256
85             {
86 227     227 0 1167 my ($data) = @_;
87              
88 227         630 return sha256(sha256($data));
89             }
90              
91             # Self-contained implementation on elliptic curve points addition.
92             # This is only a partial implementation, but should be good enough for key
93             # derivation needs. Code borrowed from the archived Math::EllipticCurve::Prime
94             # module. Returns undef for infinity points, expects to get a valid uncompressed
95             # point data on input
96             sub add_ec_points
97             {
98 15     15 0 43 my ($point1, $point2) = @_;
99              
100 15         30 my $curve_size = Bitcoin::Crypto::Config::key_max_length;
101 15         80 my $curve_data = Crypt::PK::ECC->new->generate_key(Bitcoin::Crypto::Config::curve_name)->curve2hash;
102 15         45927 my $p = new_bigint(pack "H*", $curve_data->{prime});
103 15         3279 my $a = new_bigint(pack "H*", $curve_data->{A});
104              
105             my $add_points = sub {
106 15     15   41 my ($x1, $x2, $y1, $lambda) = @_;
107              
108 15         40 my $x = $lambda->copy->bmodpow(2, $p);
109 15         3157 $x->bsub($x1);
110 15         1253 $x->bsub($x2);
111 15         1193 $x->bmod($p);
112              
113 15         1105 my $y = $x1->copy->bsub($x);
114 15         1475 $y->bmul($lambda);
115 15         708 $y->bsub($y1);
116 15         1206 $y->bmod($p);
117              
118 15         1231 return {x => $x, y => $y};
119 15         2200 };
120              
121             my $double = sub {
122 0     0   0 my ($x, $y) = @_;
123 0         0 my $lambda = $x->copy->bmodpow(2, $p);
124 0         0 $lambda->bmul(3);
125 0         0 $lambda->badd($a);
126 0         0 my $bottom = $y->copy->bmul(2)->bmodinv($p);
127 0         0 $lambda->bmul($bottom)->bmod($p);
128              
129 0         0 return $add_points->($x, $x, $y, $lambda);
130 15         66 };
131              
132 15         47 my $format = "(a$curve_size)*";
133 15         122 my ($px1, $py1) = map { new_bigint($_) } unpack $format, substr $point1, 1;
  30         3054  
134 15         3126 my ($px2, $py2) = map { new_bigint($_) } unpack $format, substr $point2, 1;
  30         2894  
135              
136             my $ret = sub {
137 15 50 0 15   55 if ($px1->bcmp($px2)) {
    0 0        
138 15         350 my $lambda = $py2->copy->bsub($py1);
139 15         1916 my $bottom = $px2->copy->bsub($px1)->bmodinv($p);
140 15         4543 $lambda->bmul($bottom)->bmod($p);
141              
142 15         1858 return $add_points->($px1, $px2, $py1, $lambda);
143             }
144             elsif ($py1->is_zero || $py2->is_zero || $py1->bcmp($py2)) {
145 0         0 return undef;
146             }
147             else {
148 0         0 return $double->($px1, $py1);
149             }
150             }
151 15         2993 ->();
152              
153 15         138 my $exp_x = $ret->{x}->to_bytes;
154 15         479 my $exp_y = $ret->{y}->to_bytes;
155              
156 15 50       369 return defined $ret
157             ? "\x04" .
158             ensure_length($exp_x, $curve_size) .
159             ensure_length($exp_y, $curve_size)
160             : undef;
161             }
162              
163             1;
164