File Coverage

blib/lib/Steemit/ECDSA.pm
Criterion Covered Total %
statement 115 136 84.5
branch 16 34 47.0
condition 26 55 47.2
subroutine 16 17 94.1
pod 9 12 75.0
total 182 254 71.6


line stmt bran cond sub pod time code
1             package Steemit::ECDSA;
2 1     1   2351 use Modern::Perl;
  1         9224  
  1         6  
3 1     1   138 use Math::EllipticCurve::Prime;
  1         3  
  1         20  
4 1     1   458 use Digest::SHA;
  1         2639  
  1         47  
5 1     1   7 use Carp;
  1         1  
  1         1758  
6              
7             my $curve = Math::EllipticCurve::Prime->from_name('secp256k1');
8             $::testing_only::inject_k = undef;
9              
10             # https://tools.ietf.org/html/rfc6979#section-3.2
11             sub deterministicGenerateK {
12 2     2 0 8 my ( $hash, $d, $nonce) = @_;
13              
14 2   50     6 $nonce //= 0;
15              
16 2 50       12 if ($nonce) {
17 0         0 $hash = Digest::SHA::sha256( $hash.pack( "C",$nonce) );
18             }
19              
20             # sanity check
21 2 50       7 die " hash must be 256 bit but byte length is ".length($hash) unless length($hash) == 32;
22              
23 2         4 my $x = $d;
24             # Step B
25 2         4 my $v = pack "H*", 'F'x64;
26              
27             # Step C
28 2         4 my $k = pack "H*", '0'x64;
29              
30             # Step D
31 2         8 $k = Digest::SHA::hmac_sha256( $v.pack('C',0).$x->to_bytes.$hash, $k);
32              
33             # Step E
34 2         130 $v = Digest::SHA::hmac_sha256($v, $k);
35              
36             # Step F
37 2         8 $k = Digest::SHA::hmac_sha256($v.pack('C',1).$x->to_bytes.$hash ,$k);
38              
39             #// Step G
40 2         107 $v = Digest::SHA::hmac_sha256($v,$k);
41              
42             #// Step H1/H2a, ignored as tlen === qlen (256 bit)
43             #// Step H2b
44 2         12 $v = Digest::SHA::hmac_sha256( $v,$k);
45              
46 2         6 my $T = Math::BigInt->from_bytes( $v );
47              
48 2         2146 return($T,$v,$k)
49             }
50              
51             sub ecdsa_sign {
52 2     2 0 5448 my( $message, $key ) = @_;
53 2         19 my $n = $curve->n; my $nlen = length($n->to_bytes);
  2         15  
54 2         1754 my $sha256 = Digest::SHA::sha256( $message );
55 2         13 my $z = Math::BigInt->from_bytes(substr($sha256,0,$nlen));
56 2         2108 my $N_OVER_TWO = $n->copy->brsft(1);
57              
58 2         357 my $is_canonical;
59 2         5 my ($k, $r, $s, $i );
60 2         3 my $nonce = 0;
61 2         3 while( 1){
62              
63 2         4 ($k, $r, $s, $i ) = map {Math::BigInt->new($_) }(0,0,0);
  6         320  
64 2         123 my ($k,$v,$k2) = deterministicGenerateK( substr($sha256,0,$nlen), $key, $nonce);
65              
66             # we need r and s to be in a valid form first
67 2   33     10 until( $k > 0 and $k < $n and $r > 0 and $s > 0 and length($r->to_bytes) == 32 and length($s->to_bytes) == 32 and _are_rs_canonical($r,$s) ){
      66        
      66        
      66        
      33        
      66        
68              
69 5         609 $k2 = Digest::SHA::hmac_sha256( $v.pack('C',0), $k2 );
70 5         46 $v = Digest::SHA::hmac_sha256( $v, $k2 );
71 5   66     28 $k = $::testing_only::inject_k // Math::BigInt->from_bytes( $v );
72              
73             #$k = $::testing_only::inject_k // Math::BigInt->from_bin($random->string_from('01',$nlen-2)) until $k > 1 and $k < $n;
74 5         4447 my $point = $curve->g->multiply($k);
75 5         48255457 $r = $point->x->bmod($n);
76 5         426 $s = (($z + $key * $r) * $k->bmodinv($n))->bmod($n);
77             }
78              
79 2 50       8 if( $s > $N_OVER_TWO ){
80 0         0 $s = $n - $s;
81             }
82              
83 2         51 $i = eval{ calcPubKeyRecoveryParam($message, $r, $s, get_public_key_point( $key ) ) };
  2         8  
84 2 50 33     158 if( my $error = $@ and not $i ){
85 0 0       0 die $error unless $error =~ /Unable to find valid recovery factor/;
86 0         0 $nonce++;
87 0         0 next;
88             }
89 2         23 return ( $r, $s, $i );
90             }
91              
92 0         0 return ( $r, $s, $i );
93             }
94              
95             sub _are_rs_canonical {
96 5     5   119500 my($r,$s) = @_;
97 5         13 return is_signature_canonical_canonical( pack('C','0').$r->to_bytes.$s->to_bytes)
98             }
99              
100             sub is_signature_canonical_canonical{
101 5     5 1 8322 my( $c ) = @_;
102             #https://github.com/steemit/steem/blob/2945196ca5ead5049e78679d69affea98d97e27b/libraries/fc/src/crypto/elliptic_common.cpp#L171
103 5   33     61 return !(unpack("xC",$c) & 0x80)
104             && !( unpack("xC",$c) == 0 && !( unpack("x[2]C",$c) & 0x80))
105             && !( unpack("x[33]C",$c) & 0x80)
106             && !( unpack("x[33]C",$c) == 0 && !( unpack("x[34]C",$c) & 0x80));
107 0         0 return 1
108             }
109              
110             sub bytes_32_sha256 {
111 4     4 1 20 my ( $message ) = @_;
112 4         43 my $sha256 = Digest::SHA::sha256( $message );
113 4         23 my $n = $curve->n; my $nlen = length($n->to_bytes);
  4         27  
114 4         3534 my $z = Math::BigInt->from_bytes(substr($sha256,0,$nlen));
115 4         4582 return $z;
116             }
117              
118             sub ecdsa_verify {
119 2     2 0 2218 my ($message, $pubkey, $r, $s) = @_;
120 2         12 my $n = $curve->n;
121 2 50 33     15 return unless $r > 0 and $r < $n and $s > 0 and $s < $n;
      33        
      33        
122              
123 2         644 my $nlen = length($n->to_bytes);
124 2         1848 my $sha256 = Digest::SHA::sha256( $message );
125 2         13 my $z = Math::BigInt->from_bytes(substr($sha256,0,$nlen));
126              
127 2         2181 my $w = $s->copy->bmodinv($n);
128 2         38390 my $u1 = ($w * $z)->bmod($n); my $u2 = ($w * $r)->bmod($n);
  2         1595  
129 2         1579 my $x1 = $curve->g->multiply($u1)->add($pubkey->multiply($u2))->x->bmod($n);
130 2         40018719 return $x1 == $r;
131             }
132              
133              
134             sub calcPubKeyRecoveryParam {
135 2     2 1 6 my ( $message, $r, $s, $Q ) = @_;
136 2         9 for ( my $i = 0; $i < 4; $i++ ){
137 3         52 my $Qprime = recoverPubKey($message,$r,$s,$i);
138 3 100 66     41 if( $Qprime->x == $Q->x and $Qprime->y == $Q->y ){
139 2         151 return Math::BigInt->new($i);
140             }
141             }
142              
143 0         0 die ('Unable to find valid recovery factor')
144             }
145              
146              
147             sub recoverPubKey {
148 4     4 1 16 my ( $message, $r, $s, $i ) = @_;
149              
150 4   50     23 $i //= 0;
151 4         26 my $e = bytes_32_sha256($message );
152 4 50 33     27 die "i must be 0 <= i < 4" unless $i >= 0 and $i < 4;
153              
154 4         212 my $n = $curve->n;
155 4         24 my $G = $curve->g;
156              
157 4 50 33     23 die "invalid r" if $r < 0 or $r > $n;
158 4 50 33     809 die "invalid s" if $s < 0 or $s > $n;
159              
160 4   66     537 my $isYOdd = ( $i == 1 or $i == 3 );
161              
162 4         142 my $isSecondKey = $i > 2;
163              
164 4 50       77 my $x = $isSecondKey ? ( $r + $n ) : $r;
165 4         13 my $R = point_from_x( $r, $isYOdd );
166              
167 4         94 my $nR = $R->multiply( $n );
168 4 50       45164070 die "nR is not a valid curve point " unless $nR->infinity;
169              
170 4         29 my $eNeg = $e->copy->bneg->bmod($n);
171              
172 4         604 my $rInv = $r->copy->bmodinv($n);
173              
174 4         89818 my $Q = $R->multiply( $s )->badd( $G->multiply($eNeg) )->multiply( $rInv );
175              
176 4         117473860 return $Q;
177             }
178              
179             sub get_compressed_public_key {
180 0     0 1 0 my( $key ) = @_;
181              
182 0         0 my $Q = get_public_key_point( $key );
183 0         0 my $buffer;
184              
185 0 0       0 if( $Q->y % 2 ){
186 0         0 $buffer = pack 'C', 0x03;
187             }else{
188 0         0 $buffer = pack 'C', 0x02;
189             }
190              
191 0         0 $buffer .= pack( 'H*', "0" x (( length($curve->p->to_bytes) - length($Q->x->to_bytes) ) * 2 ));
192 0         0 $buffer .= $Q->x->to_bytes;
193              
194 0         0 return $buffer;
195             }
196              
197             sub get_recovery_factor {
198 1     1 1 5060 my ( $x,$y ) = @_;
199 1         4 my ($p, $a, $b) = ($curve->p, $curve->a, $curve->b);
200 1         16 $x = $x->copy;
201 1         39 $y = $y->copy;
202              
203 1         24 my $yr = ($x->bmodpow(3,$p)+$a*$x+$b)->bmodpow(($p+1)/4,$p);
204 1 50       393973 if( $y eq $yr ){
205 1         104 return $yr%2;
206             }
207 0         0 $yr = $p - $y;
208 0 0       0 if( $y eq $yr ){
209 0         0 return ( $yr%2 + 1 ) % 2;
210             }
211 0         0 confess "unable to determine recovery factor";
212             }
213              
214             sub point_from_x {
215 4     4 1 11 my ( $x,$i ) = @_;
216 4         15 my $y = recover_y( $x, $i );
217 4         27 return Math::EllipticCurve::Prime::Point->new(
218             x => $x,
219             y => $y,
220             curve => $curve
221             );
222             }
223              
224             sub recover_y {
225 5     5 1 248 my ( $x,$i ) = @_;
226 5         19 my ($p, $a, $b) = ($curve->p, $curve->a, $curve->b);
227 5         75 $x = $x->copy;
228              
229 5         83 my $y = ($x->bmodpow(3,$p)+$a*$x+$b)->bmodpow(($p+1)/4,$p);
230              
231 5 100       1648325 $y = $p - $y if $i%2 ne $y%2;
232 5         1914 return $y;
233             }
234              
235              
236             sub get_public_key_point {
237 3     3 1 990 my( $key ) = @_;
238              
239 3 50       13 die "key needs to be a Math::BigInt Object " unless( $key->isa('Math::BigInt') );
240              
241 3         17 my $public_key = $curve->g->multiply( $key );
242              
243 3         79726 return $public_key;
244             }
245             1;
246              
247              
248             __END__