File Coverage

blib/lib/Crypt/ECDSA/Blind.pm
Criterion Covered Total %
statement 167 187 89.3
branch 27 48 56.2
condition 4 8 50.0
subroutine 31 35 88.5
pod 8 8 100.0
total 237 286 82.8


line stmt bran cond sub pod time code
1             # -*-cperl-*-
2             #
3             # Crypt::ECDSA::Blind - Blind ECDSA signatures
4             # Copyright (c) 2017 Ashish Gulhati
5             #
6             # $Id: lib/Crypt/ECDSA/Blind.pm v1.014 Sat Jun 10 00:32:47 PDT 2017 $
7              
8             package Crypt::ECDSA::Blind;
9              
10 2     2   27573 use warnings;
  2         4  
  2         61  
11 2     2   10 use strict;
  2         4  
  2         32  
12 2     2   2311 use DBI;
  2         26038  
  2         104  
13 2     2   1124 use Bytes::Random::Secure;
  2         15019  
  2         91  
14 2     2   818 use Math::EllipticCurve::Prime;
  2         84817  
  2         64  
15 2     2   884 use Digest::SHA;
  2         4141  
  2         104  
16 2     2   15 use vars qw( $VERSION $AUTOLOAD );
  2         4  
  2         3871  
17              
18             our ( $VERSION ) = '$Revision: 1.014 $' =~ /\s+([\d\.]+)/;
19              
20             sub new {
21 1   50 1 1 13 my ($class, %arg) = @_; my $dbname = $arg{DB} || '/tmp/ceb.db';
  1         5  
22 1 50 33     4 unlink $dbname if $arg{Clobber} and $dbname ne ':memory:';
23 1         8 my $db = DBI->connect("dbi:SQLite:dbname=$dbname", undef, undef, {AutoCommit => 1});
24 1         8778 my @tables = $db->tables('%','%','initkeys','TABLE');
25 1 50       756 unless ($tables[0]) {
26 1 50       4 if ($arg{Create}) {
27 1 50       6 return undef unless $db->do('CREATE TABLE initkeys (
28             Rp TEXT PRIMARY KEY,
29             k TEXT NOT NULL,
30             issued int NOT NULL
31             );');
32 1 50       308 return undef unless $db->do('CREATE INDEX idx_initkeys_Rp ON initkeys(Rp);');
33             }
34             else {
35 0         0 return undef;
36             }
37             }
38 1         168 @tables = $db->tables('%','%','preinits','TABLE');
39 1 50       345 unless ($tables[0]) {
40 1 50       4 if ($arg{Create}) {
41 1 50       4 return undef unless $db->do('CREATE TABLE preinits (
42             Rp TEXT PRIMARY KEY,
43             k TEXT NOT NULL
44             );');
45             }
46             else {
47 0         0 return undef;
48             }
49             }
50 1         196 bless { debug => 0,
51             db => $db,
52             curve => Math::EllipticCurve::Prime->from_name('secp256k1')
53             }, $class;
54             }
55              
56             sub keygen { # Generate public, private key pair
57 1     1 1 4651 my $self = shift;
58 1         8 my $random = Bytes::Random::Secure->new( Bits => 128 );
59 1         107 my $d = _makerandom($self->curve->n);
60 1         7 my $Q = $self->curve->g->multiply($d);
61 1         12322449 $self->_diag("keygen(): d: $d, Q: x:" . $Q->x . ', y:' . $Q->y . "\n");
62 1         22 return ((bless {Q => $Q}, 'Crypt::ECDSA::Blind::PubKey'),(bless {d => $d}, 'Crypt::ECDSA::Blind::SecKey'));
63             }
64              
65             sub preinit { # Create an init vector in advance
66 0     0 1 0 my $self = shift;
67 0         0 my $count = $self->db->selectcol_arrayref("SELECT count() from preinits;")->[0];
68 0 0       0 return if $count > 20;
69 0         0 my ($k, $Rp, $rp);
70 0         0 until ($rp) {
71 0         0 $k = _makerandom($self->curve->n);
72 0         0 $Rp = $self->curve->g->multiply($k);
73 0         0 $rp = $Rp->x;
74 0         0 $Rp = _compress($Rp);
75             }
76 0         0 $self->db->do("INSERT INTO preinits values ('$Rp','$k');");
77             }
78              
79             sub init { # Return an init vector
80 2     2 1 1268 my $self = shift;
81 2         7 my ($k, $Rp, $rp) = $self->_getpreinit;
82 2 50       7 unless ($k) {
83 2         6 until ($rp) {
84 2         13 $k = _makerandom($self->curve->n);
85 2         29 $Rp = $self->curve->g->multiply($k);
86 2         24198370 $rp = $Rp->x;
87 2         15 $Rp = _compress($Rp);
88             }
89 2         426 $self->_initkey($Rp => $k);
90             }
91 2         639 return $Rp;
92             }
93              
94             sub request { # Create a signing request
95 2     2 1 12 my ($self, %arg) = @_;
96 2         12 my $n = $self->curve->n;
97 2         16 my $Rp = _point_from_hex($arg{Init});
98 2         14 my $rp = $Rp->x->bmod($n);
99 2         308 my $A = _makerandom($n);
100 2         10 my $B = _makerandom($n);
101 2         21 my $R = $Rp->multiply($A)->add($self->curve->g->multiply($B));
102 2         23738814 my $r = $R->x->bmod($n);
103 2         221 my $hasher = Digest::SHA->new('sha256');
104 2         68 $hasher->add($arg{Message});
105 2         26 my $hash = $hasher->hexdigest; $hash =~ s/\s//g;
  2         8  
106 2         10 my $H = Math::BigInt->from_hex($hash);
107 2         2056 my $mp = ($A * $H * $rp * $r->copy->bmodinv($n))->bmod($n);
108 2         59622 $self->_request($arg{Init} => { R => $R, B => $B, H => $H });
109 2         11 $self->_diag("request(): mp: $mp\n");
110 2         40 return $mp;
111             }
112              
113             sub sign { # Create a blind signature
114 2     2 1 10 my ($self, %arg) = @_;
115 2         13 my $n = $self->curve->n;
116 2         15 my $Rp = _point_from_hex($arg{Init});
117 2         11 my $rp = $Rp->x->bmod($n);
118 2 50       207 return unless my $k = $self->_initkey($arg{Init});
119 2         129 my $sp = ($arg{Key}->d * $rp + $k * $arg{Message})->bmod($n);
120 2         2880 $self->_diag("sign(): sp: $sp\n");
121 2         23 return $sp;
122             }
123              
124             sub unblind { # Unblind a blind signature
125 2     2 1 934 my ($self, %arg) = @_;
126 2         12 my $n = $self->curve->n;
127 2         16 my $Rp = _point_from_hex($arg{Init});
128 2         10 my $rp = $Rp->x->bmod($n);
129 2 50       214 return unless my $req = $self->_request($arg{Init});
130 2         10 my $r = $req->{R}->x->bmod($n);
131             # Check here that sp and rp are in the range (1, n-1)
132 2         202 my $s = ($arg{Signature} * $r * $rp->copy->bmodinv($n) + $req->{H} * $req->{B})->bmod($n);
133 2         55724 $self->_diag("unblind(): s: $s\n"); $s = $s->as_hex; $s =~ s/^0x//;
  2         11  
  2         1961  
134             return ( bless { s => $s,
135             R => _compress($req->{R})
136 2         9 }, 'Crypt::ECDSA::Blind::Signature' );
137             }
138              
139             sub verify { # Verify a signature
140 2     2 1 5867 my ($self, %arg) = @_;
141 2         10 my $r = $arg{Signature}->R->x->bmod($self->curve->n);
142 2         223 my $Q = $arg{Key}->Q;
143 2         12 my $hasher = Digest::SHA->new('sha256');
144 2         49 $hasher->add($arg{Message});
145 2         22 my $hash = $hasher->hexdigest; $hash =~ s/\s//g;
  2         7  
146 2         7 my $H = Math::BigInt->from_hex($hash);
147 2         1724 $self->_diag('verify(): s: ' . $arg{Signature}->s . ', R(x): ' . $arg{Signature}->R->x . ", H: $H\n");
148 2         19 my $u1 = $self->curve->g->multiply($arg{Signature}->s);
149 2         25175423 my $u2 = $Q->multiply($r)->add($arg{Signature}->R->multiply($H));
150 2         24811025 $self->_diag('verify(): u1: ' . $u1->x . ', u2: ' . $u2->x . "\n");
151 2         16 $u1->to_hex eq $u2->to_hex;
152             }
153              
154             sub _getpreinit { # Get a pre-created init vector
155 2     2   4 my $self = shift;
156 2         14 my $timestamp = time;
157 2         5 while (1) {
158 2         16 my ($k,$Rp) = $self->db->selectrow_array("SELECT k,Rp FROM preinits LIMIT 1;");
159 2 50       272 return undef unless $k;
160 0         0 $self->db->do("DELETE FROM preinits WHERE k='$k';");
161 0 0       0 next unless $self->db->do("INSERT INTO initkeys values ('$Rp','$k','$timestamp');");
162 0         0 return ($k, $Rp);
163             }
164             }
165              
166             sub _initkey { # Save or destructively retrieve a saved init vector
167 4     4   9 my $self = shift;
168 4         8 my $Rp = $_[0]; my $timestamp = time;
  4         11  
169 4 100       11 if ($_[1]) {
170 2         50 $self->db->do("INSERT INTO initkeys values ('$Rp','$_[1]','$timestamp');");
171             }
172             else {
173 2         17 my $k = $self->db->selectcol_arrayref("SELECT k from initkeys WHERE Rp='$Rp';")->[0];
174 2         504 $self->db->do("DELETE FROM initkeys WHERE Rp='$Rp';");
175 2         289 return Math::BigInt->new($k);
176             }
177             }
178              
179             sub _request { # Save or destructively retrieve a saved request
180 4     4   10 my $self = shift;
181 4         10 my $Rp = $_[0]; my $ret;
  4         8  
182 4 100       13 if ($_[1]) {
183 2         8 $self->{Requests}->{$Rp} = $_[1];
184             }
185             else {
186 2         8 $ret = $self->{Requests}->{$Rp};
187 2         6 delete $self->{Requests}->{$Rp};
188             }
189 4         14 return $ret;
190             }
191              
192             sub _makerandom {
193 7     7   31 my $n = shift; my $nlen = length($n->as_bin)-2;
  7         29  
194 7         7830 my $random = Bytes::Random::Secure->new( Bits => 128 );
195 7         693 my $r = 0;
196 7   66     49 $r = Math::BigInt->from_bin($random->string_from('01',$nlen)) until ($r > 1 and $r < $n);
197 7         1772342152 return $r;
198             }
199              
200             sub _point_from_hex {
201 13     13   46 my $P = Math::EllipticCurve::Prime::Point->from_hex(_decompress(shift));
202 13         25221 $P->curve(Math::EllipticCurve::Prime->from_name('secp256k1'));
203 13         55130 $P;
204             }
205              
206             sub _decompress {
207 13     13   27 my $Kc = shift; $Kc =~ /^(..)(.*)/;
  13         45  
208 13         39 my $i = $1; my $K = '04' . '0' x (64 - length($2)) . $2; my $x = Math::BigInt->from_hex($2);
  13         60  
  13         51  
209 13         10808 my $curve = Math::EllipticCurve::Prime->from_name('secp256k1');
210 13         53334 my ($p, $a, $b) = ($curve->p, $curve->a, $curve->b);
211 13         208 my $y = ($x->bmodpow(3,$p)+$a*$x+$b)->bmodpow(($p+1)/4,$p);
212 13 100       5613835 $y = $p - $y if $i%2 ne $y%2;
213 13         5157 my $yhex = $y->as_hex; $yhex =~ s/^0x//;
  13         14097  
214 13         84 $K .= '0' x (64 - length($yhex)) . $yhex; # print "D:$K\n";
215 13         137 return $K;
216             }
217              
218             sub _compress {
219 5     5   14 my $K = shift; # print 'C:'. $K->to_hex . "\n";
220 5         17 my $Kc = $K->x->as_hex; $Kc =~ s/^0x//;
  5         4832  
221 5         19 $Kc = '0' x (64 - length($Kc)) . $Kc;
222 5 100       18 $Kc = ($K->y % 2 ? '03' : '02') . $Kc;
223             }
224              
225             sub _diag {
226 11     11   800 my $self = shift;
227 11 50       92 print @_ if $self->debug;
228             }
229              
230             sub AUTOLOAD {
231 38     38   24528776 my $self = shift; (my $auto = $AUTOLOAD) =~ s/.*:://;
  38         230  
232 38 100       337 return if $auto eq 'DESTROY';
233 37 100       189 if ($auto =~ /^(db|debug)$/x) {
234 19 50       84 $self->{$auto} = shift if (defined $_[0]);
235             }
236 37 50       158 if ($auto =~ /^(curve|db|debug)$/x) {
237 37         256 return $self->{$auto};
238             }
239             else {
240 0         0 die "Could not AUTOLOAD method $auto.";
241             }
242             }
243              
244             1; # End of Crypt::ECDSA::Blind
245              
246             package Crypt::ECDSA::Blind::PubKey;
247              
248             sub write {
249 0     0   0 1;
250             }
251              
252             sub as_hex {
253 1     1   686 Crypt::ECDSA::Blind::_compress(shift->Q);
254             }
255              
256             sub from_hex {
257 1     1   667 bless {Q => Crypt::ECDSA::Blind::_point_from_hex(shift)}, 'Crypt::ECDSA::Blind::PubKey';
258             }
259              
260             sub Q {
261 3     3   36 shift->{Q};
262             }
263              
264             1; # End of Crypt::ECDSA::Blind::PubKey
265              
266             package Crypt::ECDSA::Blind::SecKey;
267              
268             sub as_hex {
269 1     1   5 my $d = shift->d->as_hex; $d =~ s/^0x//;
  1         994  
270 1         5 $d;
271             }
272              
273             sub from_hex {
274 1     1   5 bless {d => Math::BigInt->from_hex(shift)}, 'Crypt::ECDSA::Blind::SecKey';
275             }
276              
277             sub write {
278 0     0   0 1;
279             }
280              
281             sub d {
282 3     3   21 shift->{d};
283             }
284              
285             1; # End of Crypt::ECDSA::Blind::SecKey
286              
287             package Crypt::ECDSA::Blind::Signature;
288              
289             sub s {
290 4     4   28 Math::BigInt->from_hex(shift->{'s'});
291             }
292              
293             sub R {
294 6     6   23909781 Crypt::ECDSA::Blind::_point_from_hex(shift->{R});
295             }
296              
297             sub is_valid {
298 0     0     my $self = shift;
299 0 0         $self->{R} =~ /^[0-9a-f]+$/ and $self->{s} =~ /^[0-9a-f]+$/;
300             }
301              
302             1; # End of Crypt::ECDSA::Blind::Signature
303              
304             __END__