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) Ashish Gulhati
5             #
6             # $Id: lib/Crypt/ECDSA/Blind.pm v1.015 Tue Oct 16 22:40:55 PDT 2018 $
7              
8             package Crypt::ECDSA::Blind;
9              
10 2     2   130509 use warnings;
  2         16  
  2         65  
11 2     2   11 use strict;
  2         4  
  2         37  
12 2     2   3026 use DBI;
  2         34297  
  2         124  
13 2     2   1246 use Bytes::Random::Secure;
  2         19380  
  2         112  
14 2     2   877 use Math::EllipticCurve::Prime;
  2         117512  
  2         76  
15 2     2   1011 use Digest::SHA;
  2         5037  
  2         113  
16 2     2   16 use vars qw( $VERSION $AUTOLOAD );
  2         5  
  2         5186  
17              
18             our ( $VERSION ) = '$Revision: 1.015 $' =~ /\s+([\d\.]+)/;
19              
20             sub new {
21 1   50 1 1 89 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         12 my $db = DBI->connect("dbi:SQLite:dbname=$dbname", undef, undef, {AutoCommit => 1});
24 1         11226 my @tables = $db->tables('%','%','initkeys','TABLE');
25 1 50       732 unless ($tables[0]) {
26 1 50       6 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       317 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         197 @tables = $db->tables('%','%','preinits','TABLE');
39 1 50       428 unless ($tables[0]) {
40 1 50       12 if ($arg{Create}) {
41 1 50       5 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         217 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 4913 my $self = shift;
58 1         9 my $random = Bytes::Random::Secure->new( Bits => 128 );
59 1         108 my $d = _makerandom($self->curve->n);
60 1         9 my $Q = $self->curve->g->multiply($d);
61 1         10635857 $self->_diag("keygen(): d: $d, Q: x:" . $Q->x . ', y:' . $Q->y . "\n");
62 1         17 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 968 my $self = shift;
81 2         6 my ($k, $Rp, $rp) = $self->_getpreinit;
82 2 50       6 unless ($k) {
83 2         5 until ($rp) {
84 2         11 $k = _makerandom($self->curve->n);
85 2         12 $Rp = $self->curve->g->multiply($k);
86 2         19921905 $rp = $Rp->x;
87 2         16 $Rp = _compress($Rp);
88             }
89 2         393 $self->_initkey($Rp => $k);
90             }
91 2         535 return $Rp;
92             }
93              
94             sub request { # Create a signing request
95 2     2 1 11 my ($self, %arg) = @_;
96 2         12 my $n = $self->curve->n;
97 2         16 my $Rp = _point_from_hex($arg{Init});
98 2         8 my $rp = $Rp->x->bmod($n);
99 2         253 my $A = _makerandom($n);
100 2         8 my $B = _makerandom($n);
101 2         17 my $R = $Rp->multiply($A)->add($self->curve->g->multiply($B));
102 2         24438715 my $r = $R->x->bmod($n);
103 2         216 my $hasher = Digest::SHA->new('sha256');
104 2         59 $hasher->add($arg{Message});
105 2         20 my $hash = $hasher->hexdigest; $hash =~ s/\s//g;
  2         7  
106 2         10 my $H = Math::BigInt->from_hex($hash);
107 2         1890 my $mp = ($A * $H * $rp * $r->copy->bmodinv($n))->bmod($n);
108 2         57447 $self->_request($arg{Init} => { R => $R, B => $B, H => $H });
109 2         11 $self->_diag("request(): mp: $mp\n");
110 2         45 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         19 my $Rp = _point_from_hex($arg{Init});
117 2         9 my $rp = $Rp->x->bmod($n);
118 2 50       239 return unless my $k = $self->_initkey($arg{Init});
119 2         149 my $sp = ($arg{Key}->d * $rp + $k * $arg{Message})->bmod($n);
120 2         3065 $self->_diag("sign(): sp: $sp\n");
121 2         28 return $sp;
122             }
123              
124             sub unblind { # Unblind a blind signature
125 2     2 1 698 my ($self, %arg) = @_;
126 2         13 my $n = $self->curve->n;
127 2         17 my $Rp = _point_from_hex($arg{Init});
128 2         9 my $rp = $Rp->x->bmod($n);
129 2 50       301 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         230 my $s = ($arg{Signature} * $r * $rp->copy->bmodinv($n) + $req->{H} * $req->{B})->bmod($n);
133 2         58005 $self->_diag("unblind(): s: $s\n"); $s = $s->as_hex; $s =~ s/^0x//;
  2         42  
  2         2122  
134             return ( bless { s => $s,
135             R => _compress($req->{R})
136 2         10 }, 'Crypt::ECDSA::Blind::Signature' );
137             }
138              
139             sub verify { # Verify a signature
140 2     2 1 5439 my ($self, %arg) = @_;
141 2         11 my $r = $arg{Signature}->R->x->bmod($self->curve->n);
142 2         271 my $Q = $arg{Key}->Q;
143 2         15 my $hasher = Digest::SHA->new('sha256');
144 2         63 $hasher->add($arg{Message});
145 2         24 my $hash = $hasher->hexdigest; $hash =~ s/\s//g;
  2         8  
146 2         9 my $H = Math::BigInt->from_hex($hash);
147 2         1766 $self->_diag('verify(): s: ' . $arg{Signature}->s . ', R(x): ' . $arg{Signature}->R->x . ", H: $H\n");
148 2         23 my $u1 = $self->curve->g->multiply($arg{Signature}->s);
149 2         25753092 my $u2 = $Q->multiply($r)->add($arg{Signature}->R->multiply($H));
150 2         25517169 $self->_diag('verify(): u1: ' . $u1->x . ', u2: ' . $u2->x . "\n");
151 2         14 $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         3 my $timestamp = time;
157 2         3 while (1) {
158 2         16 my ($k,$Rp) = $self->db->selectrow_array("SELECT k,Rp FROM preinits LIMIT 1;");
159 2 50       224 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   10 my $self = shift;
168 4         7 my $Rp = $_[0]; my $timestamp = time;
  4         10  
169 4 100       11 if ($_[1]) {
170 2         42 $self->db->do("INSERT INTO initkeys values ('$Rp','$_[1]','$timestamp');");
171             }
172             else {
173 2         21 my $k = $self->db->selectcol_arrayref("SELECT k from initkeys WHERE Rp='$Rp';")->[0];
174 2         464 $self->db->do("DELETE FROM initkeys WHERE Rp='$Rp';");
175 2         379 return Math::BigInt->new($k);
176             }
177             }
178              
179             sub _request { # Save or destructively retrieve a saved request
180 4     4   12 my $self = shift;
181 4         10 my $Rp = $_[0]; my $ret;
  4         10  
182 4 100       15 if ($_[1]) {
183 2         16 $self->{Requests}->{$Rp} = $_[1];
184             }
185             else {
186 2         9 $ret = $self->{Requests}->{$Rp};
187 2         7 delete $self->{Requests}->{$Rp};
188             }
189 4         13 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         6710 my $random = Bytes::Random::Secure->new( Bits => 128 );
195 7         560 my $r = 0;
196 7   66     43 $r = Math::BigInt->from_bin($random->string_from('01',$nlen)) until ($r > 1 and $r < $n);
197 7         559797652 return $r;
198             }
199              
200             sub _point_from_hex {
201 13     13   45 my $P = Math::EllipticCurve::Prime::Point->from_hex(_decompress(shift));
202 13         25445 $P->curve(Math::EllipticCurve::Prime->from_name('secp256k1'));
203 13         53532 $P;
204             }
205              
206             sub _decompress {
207 13     13   28 my $Kc = shift; $Kc =~ /^(..)(.*)/;
  13         58  
208 13         40 my $i = $1; my $K = '04' . '0' x (64 - length($2)) . $2; my $x = Math::BigInt->from_hex($2);
  13         63  
  13         52  
209 13         11496 my $curve = Math::EllipticCurve::Prime->from_name('secp256k1');
210 13         54343 my ($p, $a, $b) = ($curve->p, $curve->a, $curve->b);
211 13         205 my $y = ($x->bmodpow(3,$p)+$a*$x+$b)->bmodpow(($p+1)/4,$p);
212 13 100       5337004 $y = $p - $y if $i%2 ne $y%2;
213 13         4819 my $yhex = $y->as_hex; $yhex =~ s/^0x//;
  13         13451  
214 13         80 $K .= '0' x (64 - length($yhex)) . $yhex; # print "D:$K\n";
215 13         160 return $K;
216             }
217              
218             sub _compress {
219 5     5   10 my $K = shift; # print 'C:'. $K->to_hex . "\n";
220 5         16 my $Kc = $K->x->as_hex; $Kc =~ s/^0x//;
  5         4671  
221 5         18 $Kc = '0' x (64 - length($Kc)) . $Kc;
222 5 100       17 $Kc = ($K->y % 2 ? '03' : '02') . $Kc;
223             }
224              
225             sub _diag {
226 11     11   837 my $self = shift;
227 11 50       100 print @_ if $self->debug;
228             }
229              
230             sub AUTOLOAD {
231 38     38   25073823 my $self = shift; (my $auto = $AUTOLOAD) =~ s/.*:://;
  38         237  
232 38 100       463 return if $auto eq 'DESTROY';
233 37 100       156 if ($auto =~ /^(db|debug)$/x) {
234 19 50       62 $self->{$auto} = shift if (defined $_[0]);
235             }
236 37 50       188 if ($auto =~ /^(curve|db|debug)$/x) {
237 37         262 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   383 Crypt::ECDSA::Blind::_compress(shift->Q);
254             }
255              
256             sub from_hex {
257 1     1   415 bless {Q => Crypt::ECDSA::Blind::_point_from_hex(shift)}, 'Crypt::ECDSA::Blind::PubKey';
258             }
259              
260             sub Q {
261 3     3   18 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   4 my $d = shift->d->as_hex; $d =~ s/^0x//;
  1         814  
270 1         5 $d;
271             }
272              
273             sub from_hex {
274 1     1   4 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   30 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   33 Math::BigInt->from_hex(shift->{'s'});
291             }
292              
293             sub R {
294 6     6   23987358 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__