File Coverage

blib/lib/Math/Prime/Util/PrimalityProving.pm
Criterion Covered Total %
statement 497 567 87.6
branch 269 436 61.7
condition 59 144 40.9
subroutine 32 36 88.8
pod 4 4 100.0
total 861 1187 72.5


line stmt bran cond sub pod time code
1             package Math::Prime::Util::PrimalityProving;
2 5     5   1720 use strict;
  5         13  
  5         185  
3 5     5   30 use warnings;
  5         11  
  5         188  
4 5     5   28 use Carp qw/carp croak confess/;
  5         10  
  5         403  
5 5         33 use Math::Prime::Util qw/is_prob_prime is_strong_pseudoprime
6             is_provable_prime_with_cert
7             lucas_sequence
8             factor
9             prime_get_config
10 5     5   32 /;
  5         10  
11              
12             BEGIN {
13 5     5   20 $Math::Prime::Util::PrimalityProving::AUTHORITY = 'cpan:DANAJ';
14 5         245 $Math::Prime::Util::PrimalityProving::VERSION = '0.69';
15             }
16              
17             BEGIN {
18 5 50   5   35672 do { require Math::BigInt; Math::BigInt->import(try=>"GMP,Pari"); }
  0         0  
  0         0  
19             unless defined $Math::BigInt::VERSION;
20             }
21              
22             my $_smallval = Math::BigInt->new("18446744073709551615");
23             my $_maxint = Math::BigInt->new( (~0 > 4294967296 && $] < 5.008) ? "562949953421312" : ''.~0 );
24              
25              
26             ###############################################################################
27             # Pure Perl proofs
28             ###############################################################################
29              
30             my @_fsublist = (
31             sub { Math::Prime::Util::PP::pbrent_factor (shift, 32*1024, 1) },
32             sub { Math::Prime::Util::PP::pminus1_factor(shift, 1_000_000) },
33             sub { Math::Prime::Util::PP::ecm_factor (shift, 1_000, 5_000, 15) },
34             sub { Math::Prime::Util::PP::pbrent_factor (shift, 512*1024, 7) },
35             sub { Math::Prime::Util::PP::pminus1_factor(shift, 4_000_000) },
36             sub { Math::Prime::Util::PP::ecm_factor (shift, 10_000, 50_000, 10) },
37             sub { Math::Prime::Util::PP::pbrent_factor (shift, 512*1024, 11) },
38             sub { Math::Prime::Util::PP::pminus1_factor(shift,20_000_000) },
39             sub { Math::Prime::Util::PP::ecm_factor (shift, 100_000, 800_000, 10) },
40             sub { Math::Prime::Util::PP::pbrent_factor (shift, 2048*1024, 13) },
41             sub { Math::Prime::Util::PP::ecm_factor (shift, 1_000_000, 1_000_000, 20)},
42             sub { Math::Prime::Util::PP::pminus1_factor(shift, 100_000_000, 500_000_000)},
43             );
44              
45             sub _small_cert {
46 0     0   0 my $n = shift;
47 0 0       0 return '' unless is_prob_prime($n);
48 0         0 return join "\n", "[MPU - Primality Certificate]",
49             "Version 1.0",
50             "",
51             "Proof for:",
52             "N $n",
53             "",
54             "Type Small",
55             "N $n",
56             "";
57             }
58              
59             # For stripping off the header on certificates so they can be combined.
60             sub _strip_proof_header {
61 2     2   64 my $proof = shift;
62 2         18 $proof =~ s/^\[MPU - Primality Certificate\]\nVersion \S+\n+Proof for:\nN (\d+)\n+//ms;
63 2         12 return $proof;
64             }
65              
66              
67             sub primality_proof_lucas {
68 1     1 1 1250 my ($n) = shift;
69 1         3 my @composite = (0, '');
70              
71             # Since this can take a very long time with a composite, try some easy cuts
72 1 50 33     10 return @composite if !defined $n || $n < 2;
73 1 50       5 return (2, _small_cert($n)) if $n < 4;
74 1 50       40 return @composite if is_strong_pseudoprime($n,2,15,325) == 0;
75              
76 1         3 my $nm1 = $n-1;
77 1         7 my @factors = factor($nm1);
78             { # remove duplicate factors and make a sorted array of bigints
79 1         3 my %uf;
  1         2  
80 1         6 undef @uf{@factors};
81 1         4 @factors = sort {$a<=>$b} map { Math::BigInt->new("$_") } keys %uf;
  5         137  
  4         171  
82             }
83 1         25 my $cert = "[MPU - Primality Certificate]\nVersion 1.0\n\nProof for:\nN $n\n\n";
84 1         4 $cert .= "Type Lucas\nN $n\n";
85 1         5 foreach my $i (1 .. scalar @factors) {
86 4         67 $cert .= "Q[$i] " . $factors[$i-1] . "\n";
87             }
88 1         17 for (my $a = 2; $a < $nm1; $a++) {
89 1         5 my $ap = Math::BigInt->new("$a");
90             # 1. a must be coprime to n
91 1 50       44 next unless Math::BigInt::bgcd($ap, $n) == 1;
92             # 2. a^(n-1) = 1 mod n.
93 1 50       382 next unless $ap->copy->bmodpow($nm1, $n) == 1;
94             # 3. a^((n-1)/f) != 1 mod n for all f.
95 4         1010 next if (scalar grep { $_ == 1 }
96 1 50       901 map { $ap->copy->bmodpow(int($nm1/$_),$n); }
  4         2533  
97             @factors) > 0;
98             # Verify each factor and add to proof
99 1         86 my @fac_proofs;
100 1         4 foreach my $f (@factors) {
101 4         115 my ($isp, $fproof) = Math::Prime::Util::is_provable_prime_with_cert($f);
102 4 50       10 if ($isp != 2) {
103 0         0 carp "could not prove primality of $n.\n";
104 0         0 return (1, '');
105             }
106 4 50       12 push @fac_proofs, _strip_proof_header($fproof) if $f > $_smallval;
107             }
108 1         31 $cert .= "A $a\n";
109 1         3 foreach my $proof (@fac_proofs) {
110 0         0 $cert .= "\n$proof";
111             }
112 1         12 return (2, $cert);
113             }
114 0         0 return @composite;
115             }
116              
117             sub primality_proof_bls75 {
118 13     13 1 124 my ($n) = shift;
119 13         42 my @composite = (0, '');
120              
121             # Since this can take a very long time with a composite, try some easy tests
122 13 50 33     89 return @composite if !defined $n || $n < 2;
123 13 50       1287 return (2, _small_cert($n)) if $n < 4;
124 13 50       1148 return @composite if ($n & 1) == 0;
125 13 50       5192 return @composite if is_strong_pseudoprime($n,2,15,325) == 0;
126              
127 13         172 require Math::Prime::Util::PP;
128 13 100       74 $n = Math::BigInt->new("$n") unless ref($n) eq 'Math::BigInt';
129 13         173 my $nm1 = $n->copy->bdec;
130 13         1097 my $ONE = $nm1->copy->bone;
131 13         1482 my $TWO = $ONE->copy->binc;
132 13         776 my $A = $ONE->copy; # factored part
133 13         234 my $B = $nm1->copy; # unfactored part
134 13         240 my @factors = ($TWO);
135 13 50       55 croak "BLS75 error: n-1 not even" unless $nm1->is_even();
136             {
137 13         215 while ($B->is_even) { $B->bdiv($TWO); $A->bmul($TWO); }
  13         36  
  19         570  
  19         3052  
138 13         774 my @tf;
139 13 100 66     48 if ($B <= $_maxint && prime_get_config->{'xs'}) {
140 2         8 @tf = Math::Prime::Util::trial_factor("$B", 20000);
141 2 100       151 pop @tf if $tf[-1] > 20000;
142             } else {
143 11         478 @tf = Math::Prime::Util::PP::trial_factor($B, 5000);
144 11 50       42 pop @tf if $tf[-1] > 5000;
145             }
146 13         392 foreach my $f (@tf) {
147 45 100       8952 next if $f == $factors[-1];
148 34         1316 push @factors, $f;
149 34         104 while (($B % $f) == 0) { $B /= $f; $A *= $f; }
  45         14734  
  45         9371  
150             }
151             }
152 13         5203 my @nstack;
153             # nstack should only hold composites
154 13 100       42 if ($B->is_one) {
    100          
155             # Completely factored. Nothing.
156             } elsif (is_prob_prime($B)) {
157 3         335 push @factors, $B;
158 3         9 $A *= $B; $B /= $B; # completely factored already
  3         312  
159             } else {
160 9         409 push @nstack, $B;
161             }
162 13         570 while (@nstack) {
163 14         57 my ($s,$r) = $B->copy->bdiv($A->copy->bmul($TWO));
164 14         4154 my $fpart = ($A+$ONE) * ($TWO*$A*$A + ($r-$ONE) * $A + $ONE);
165 14 100       8566 last if $n < $fpart;
166              
167 13         480 my $m = pop @nstack;
168             # Don't use bignum if it has gotten small enough.
169 13 100 100     80 $m = int($m->bstr) if ref($m) eq 'Math::BigInt' && $m <= $_maxint;
170             # Try to find factors of m, using the default set of factor subs.
171 13         484 my @ftry;
172 13         40 foreach my $sub (@_fsublist) {
173 13         67 @ftry = $sub->($m);
174 13 50       75 last if scalar @ftry >= 2;
175             }
176             # If we couldn't find a factor, skip it.
177 13 50       68 next unless scalar @ftry > 1;
178             # Process each factor
179 13         60 foreach my $f (@ftry) {
180 26 50 33     3330 croak "Invalid factoring: B=$B m=$m f=$f" if $f == 1 || $f == $m || !$B->copy->bmod($f)->is_zero;
      33        
181 26 100       6916 if (is_prob_prime($f)) {
182 21         720 push @factors, $f;
183 21         34 do { $B /= $f; $A *= $f; } while $B->copy->bmod($f)->is_zero;
  21         91  
  21         4197  
184             } else {
185 5         62 push @nstack, $f;
186             }
187             }
188             }
189             { # remove duplicate factors and make a sorted array of bigints
190 13         2453 my %uf = map { $_ => 1 } @factors;
  13         43  
  71         151  
191 13         608 @factors = sort {$a<=>$b} map { Math::BigInt->new("$_") } keys %uf;
  111         2424  
  71         2035  
192             }
193             # Just in case:
194 13         338 foreach my $f (@factors) {
195 71         5684 while ($B->copy->bmod($f)->is_zero) {
196 0         0 $B /= $f; $A *= $f;
  0         0  
197             }
198             }
199             # Did we factor enough?
200 13         1456 my ($s,$r) = $B->copy->bdiv($A->copy->bmul($TWO));
201 13         3578 my $fpart = ($A+$ONE) * ($TWO*$A*$A + ($r-$ONE) * $A + $ONE);
202 13 50       9816 return (1,'') if $n >= $fpart;
203             # Check we didn't mess up
204 13 50       557 croak "BLS75 error: $A * $B != $nm1" unless $A*$B == $nm1;
205 13 50       1578 croak "BLS75 error: $A not even" unless $A->is_even();
206 13 50       237 croak "BLS75 error: A and B not coprime" unless Math::BigInt::bgcd($A, $B)->is_one;
207              
208 13         3263 my $rtest = $r*$r - 8*$s;
209 13         3788 my $rtestroot = $rtest->copy->bsqrt;
210 13 50 66     1631 return @composite if $s != 0 && ($rtestroot*$rtestroot) == $rtest;
211              
212 13         2527 my $cert = "[MPU - Primality Certificate]\nVersion 1.0\n\nProof for:\nN $n\n\n";
213 13         424 $cert .= "Type BLS5\nN $n\n";
214 13         306 my $qnum = 0;
215 13         28 my $atext = '';
216 13         29 my @fac_proofs;
217 13         36 foreach my $f (@factors) {
218 71         2440 my $success = 0;
219 71 100       248 if ($qnum == 0) {
220 13 50       39 die "BLS5 Perl proof: Internal error, first factor not 2" unless $f == 2;
221             } else {
222 58         282 $cert .= "Q[$qnum] $f\n";
223             }
224 71         2724 my $nm1_div_f = $nm1 / $f;
225 71         14695 foreach my $a (2 .. 10000) {
226 81         210981 my $ap = Math::BigInt->new($a);
227 81 50       3308 next unless $ap->copy->bmodpow($nm1, $n)->is_one;
228 81 100       2281295 next unless Math::BigInt::bgcd($ap->copy->bmodpow($nm1_div_f, $n)->bdec, $n)->is_one;
229 71 100       1949301 $atext .= "A[$qnum] $a\n" unless $a == 2;
230 71         217 $success = 1;
231 71         276 last;
232             }
233 71         204 $qnum++;
234 71 50       220 return @composite unless $success;
235 71         481 my ($isp, $fproof) = is_provable_prime_with_cert($f);
236 71 50       234 if ($isp != 2) {
237 0         0 carp "could not prove primality of $n.\n";
238 0         0 return (1, '');
239             }
240 71 100       249 push @fac_proofs, _strip_proof_header($fproof) if $f > $_smallval;
241             }
242 13         495 $cert .= $atext;
243 13         36 $cert .= "----\n";
244 13         35 foreach my $proof (@fac_proofs) {
245 2         7 $cert .= "\n$proof";
246             }
247 13         304 return (2, $cert);
248             }
249              
250             ###############################################################################
251             # Convert certificates from old array format to new string format
252             ###############################################################################
253              
254             sub _convert_cert {
255 38     38   83 my $pdata = shift; # pdata is a ref
256              
257 38 50       127 return '' if scalar @$pdata == 0;
258 38         88 my $n = shift @$pdata;
259 38 100       160 if (length($n) == 1) {
260 3 100       24 return "Type Small\nN $n\n" if $n =~ /^[2357]$/;
261 2         8 return '';
262             }
263 35 50       242 $n = Math::BigInt->new("$n") if ref($n) ne 'Math::BigInt';
264 35 100       2324 return '' if $n->is_even;
265              
266 34 100       636 my $method = (scalar @$pdata > 0) ? shift @$pdata : 'BPSW';
267              
268 34 100       121 if ($method eq 'BPSW') {
269 3 100       14 return '' if $n > $_smallval;
270 2 50       95 return '' if is_prob_prime($n) != 2;
271 0         0 return "Type Small\nN $n\n";
272             }
273              
274 31 100 66     174 if ($method eq 'Pratt' || $method eq 'Lucas') {
275 12 50 33     109 if (scalar @$pdata != 2 || ref($$pdata[0]) ne 'ARRAY' || ref($$pdata[1]) eq 'ARRAY') {
      33        
276 0         0 carp "verify_prime: incorrect Pratt format, must have factors and a value\n";
277 0         0 return '';
278             }
279 12         24 my @factors = @{shift @$pdata};
  12         36  
280 12         30 my $a = shift @$pdata;
281 12         46 my $cert = "Type Lucas\nN $n\n";
282 12         289 foreach my $i (0 .. $#factors) {
283 49 100       103 my $f = (ref($factors[$i]) eq 'ARRAY') ? $factors[$i]->[0] : $factors[$i];
284 49         151 $cert .= sprintf("Q[%d] %s\n", $i+1, $f);
285             }
286 12         35 $cert .= "A $a\n\n";
287              
288 12         28 foreach my $farray (@factors) {
289 49 100       105 if (ref($farray) eq 'ARRAY') {
290 4         10 $cert .= _convert_cert($farray);
291             }
292             }
293 12         52 return $cert;
294             }
295 19 100       70 if ($method eq 'n-1') {
296 12 0 33     44 if (scalar @$pdata == 3 && ref($$pdata[0]) eq 'ARRAY' && $$pdata[0]->[0] =~ /^(B|T7|Theorem\s*7)$/i) {
      33        
297 0         0 croak "Unsupported BLS7 proof in conversion";
298             }
299 12 50 33     97 if (scalar @$pdata != 2 || ref($$pdata[0]) ne 'ARRAY' || ref($$pdata[1]) ne 'ARRAY') {
      33        
300 0         0 carp "verify_prime: incorrect n-1 format, must have factors and a values\n";
301 0         0 return '';
302             }
303 12         25 my @factors = @{shift @$pdata};
  12         34  
304 12         26 my @as = @{shift @$pdata};
  12         28  
305 12 50       41 if (scalar @factors != scalar @as) {
306 0         0 carp "verify_prime: incorrect n-1 format, must have a value for each factor\n";
307 0         0 return '';
308             }
309             # Make sure 2 is at the top
310 12         41 foreach my $i (1 .. $#factors) {
311 38 100       73 my $f = (ref($factors[$i]) eq 'ARRAY') ? $factors[$i]->[0] : $factors[$i];
312 38 50       90 if ($f == 2) {
313 0         0 my $tf = $factors[0]; $factors[0] = $factors[$i]; $factors[$i] = $tf;
  0         0  
  0         0  
314 0         0 my $ta = $as[0]; $as[0] = $as[$i]; $as[$i] = $ta;
  0         0  
  0         0  
315             }
316             }
317 12 100       36 return '' unless $factors[0] == 2;
318 11         44 my $cert = "Type BLS5\nN $n\n";
319 11         315 foreach my $i (1 .. $#factors) {
320 35 100       87 my $f = (ref($factors[$i]) eq 'ARRAY') ? $factors[$i]->[0] : $factors[$i];
321 35         105 $cert .= sprintf("Q[%d] %s\n", $i, $f);
322             }
323 11         31 foreach my $i (0 .. $#as) {
324 46 100       107 $cert .= sprintf("A[%d] %s\n", $i, $as[$i]) if $as[$i] != 2;
325             }
326 11         31 $cert .= "----\n\n";
327 11         23 foreach my $farray (@factors) {
328 46 100       85 if (ref($farray) eq 'ARRAY') {
329 3         42 $cert .= _convert_cert($farray);
330             }
331             }
332 11         93 return $cert;
333             }
334 7 50 33     44 if ($method eq 'ECPP' || $method eq 'AGKM') {
335 7 50       30 if (scalar @$pdata < 1) {
336 0         0 carp "verify_prime: incorrect AGKM format\n";
337 0         0 return '';
338             }
339 7         21 my $cert = '';
340 7         24 my $q = $n;
341 7         26 foreach my $block (@$pdata) {
342 14 50 33     126 if (ref($block) ne 'ARRAY' || scalar @$block != 6) {
343 0         0 carp "verify_prime: incorrect AGKM block format\n";
344 0         0 return '';
345             }
346 14         50 my($ni, $a, $b, $m, $qval, $P) = @$block;
347 14 50       77 if (Math::BigInt->new("$ni") != Math::BigInt->new("$q")) {
348 0         0 carp "verify_prime: incorrect AGKM block format: block n != q\n";
349 0         0 return '';
350             }
351 14 100       2147 $q = ref($qval) eq 'ARRAY' ? $qval->[0] : $qval;
352 14 50 33     79 if (ref($P) ne 'ARRAY' || scalar @$P != 2) {
353 0         0 carp "verify_prime: incorrect AGKM block point format\n";
354 0         0 return '';
355             }
356 14         25 my ($x, $y) = @{$P};
  14         32  
357 14         78 $cert .= "Type ECPP\nN $ni\nA $a\nB $b\nM $m\nQ $q\nX $x\nY $y\n\n";
358 14 100       52 if (ref($qval) eq 'ARRAY') {
359 1         5 $cert .= _convert_cert($qval);
360             }
361             }
362 7         34 return $cert;
363             }
364 0         0 carp "verify_prime: Unknown method: '$method'.\n";
365 0         0 return '';
366             }
367              
368              
369             sub convert_array_cert_to_string {
370 31     31 1 119 my @pdata = @_;
371              
372             # Convert reference input to array
373 31 100 66     268 @pdata = @{$pdata[0]} if scalar @pdata == 1 && ref($pdata[0]) eq 'ARRAY';
  29         118  
374              
375 31 100       129 return '' if scalar @pdata == 0;
376              
377 30         75 my $n = $pdata[0];
378 30         116 my $header = "[MPU - Primality Certificate]\nVersion 1.0\n\nProof for:\nN $n\n\n";
379              
380 30         135 my $cert = _convert_cert(\@pdata);
381 30 100       336 return '' if $cert eq '';
382 25         198 return $header . $cert;
383             }
384              
385              
386             ###############################################################################
387             # Verify certificate
388             ###############################################################################
389              
390             sub _primality_error ($) { ## no critic qw(ProhibitSubroutinePrototypes)
391 0 0   0   0 print "primality fail: $_[0]\n" if prime_get_config->{'verbose'};
392 0         0 return; # error in certificate
393             }
394              
395             sub _pfail ($) { ## no critic qw(ProhibitSubroutinePrototypes)
396 19 50   19   20042 print "primality fail: $_[0]\n" if prime_get_config->{'verbose'};
397 19         245 return; # Failed a condition
398             }
399              
400             sub _read_vars {
401 72     72   154 my $lines = shift;
402 72         154 my $type = shift;
403 72         193 my %vars = map { $_ => 1 } @_;
  178         578  
404 72         177 my %return;
405 72         283 while (scalar keys %vars) {
406 178         365 my $line = shift @$lines;
407 178 50       400 return _primality_error("end of file during type $type") unless defined $line;
408             # Skip comments and blank lines
409 178 50 33     921 next if $line =~ /^\s*#/ or $line =~ /^\s*$/;
410 178         317 chomp($line);
411 178 50       382 return _primality_error("Still missing values in type $type") if $line =~ /^Type /;
412 178 50       712 if ($line =~ /^(\S+)\s+(-?\d+)/) {
413 178         527 my ($var, $val) = ($1, $2);
414 178         366 $var =~ tr/a-z/A-Z/;
415 178 50       439 return _primality_error("Type $type: repeated or inappropriate var: $line") unless defined $vars{$var};
416 178         366 $return{$var} = $val;
417 178         511 delete $vars{$var};
418             } else {
419 0         0 return _primality_error("Unrecognized line: $line");
420             }
421             }
422             # Now return them in the order given, turned into bigints.
423 72         179 return map { Math::BigInt->new("$return{$_}") } @_;
  178         5118  
424             }
425              
426             sub _is_perfect_square {
427 4     4   2257 my($n) = @_;
428              
429 4 50       18 if (ref($n) eq 'Math::BigInt') {
430 4         17 my $mc = int(($n & 31)->bstr);
431 4 50 66     1351 if ($mc==0||$mc==1||$mc==4||$mc==9||$mc==16||$mc==17||$mc==25) {
      66        
      100        
      66        
      100        
      66        
432 4         11 my $sq = $n->copy->bsqrt->bfloor;
433 4         7843 $sq->bmul($sq);
434 4 50       636 return 1 if $sq == $n;
435             }
436             } else {
437 0         0 my $mc = $n & 31;
438 0 0 0     0 if ($mc==0||$mc==1||$mc==4||$mc==9||$mc==16||$mc==17||$mc==25) {
      0        
      0        
      0        
      0        
      0        
439 0         0 my $sq = int(sqrt($n));
440 0 0       0 return 1 if ($sq*$sq) == $n;
441             }
442             }
443 4         180 0;
444             }
445              
446             # Calculate Jacobi symbol (M|N)
447             sub _jacobi {
448 1     1   4 my($n, $m) = @_;
449 1 50 33     3 return 0 if $m <= 0 || ($m % 2) == 0;
450 1         460 my $j = 1;
451 1 50       4 if ($n < 0) {
452 1         127 $n = -$n;
453 1 50       32 $j = -$j if ($m % 4) == 3;
454             }
455             # Split loop so we can reduce n/m to non-bigints after first iteration.
456 1 50       239 if ($n != 0) {
457 1         131 while (($n % 2) == 0) {
458 3         1699 $n >>= 1;
459 3 50 33     426 $j = -$j if ($m % 8) == 3 || ($m % 8) == 5;
460             }
461 1         711 ($n, $m) = ($m, $n);
462 1 50 33     4 $j = -$j if ($n % 4) == 3 && ($m % 4) == 3;
463 1         229 $n = $n % $m;
464 1 50 33     89 $n = int($n->bstr) if ref($n) eq 'Math::BigInt' && $n <= $_maxint;
465 1 50 33     104 $m = int($m->bstr) if ref($m) eq 'Math::BigInt' && $m <= $_maxint;
466             }
467 1         46 while ($n != 0) {
468 0         0 while (($n % 2) == 0) {
469 0         0 $n >>= 1;
470 0 0 0     0 $j = -$j if ($m % 8) == 3 || ($m % 8) == 5;
471             }
472 0         0 ($n, $m) = ($m, $n);
473 0 0 0     0 $j = -$j if ($n % 4) == 3 && ($m % 4) == 3;
474 0         0 $n = $n % $m;
475             }
476 1 50       6 return ($m == 1) ? $j : 0;
477             }
478              
479             # Proof handlers (parse input and call verification)
480              
481             sub _prove_ecpp {
482 13     13   64 _verify_ecpp( _read_vars($_[0], 'ECPP', qw/N A B M Q X Y/) );
483             }
484             sub _prove_ecpp3 {
485 1     1   5 _verify_ecpp3( _read_vars($_[0], 'ECPP3', qw/N S R A B T/) );
486             }
487             sub _prove_ecpp4 {
488 0     0   0 _verify_ecpp4( _read_vars($_[0], 'ECPP4', qw/N S R J T/) );
489             }
490             sub _prove_bls15 {
491 1     1   7 _verify_bls15( _read_vars($_[0], 'BLS15', qw/N Q LP LQ/) );
492             }
493             sub _prove_bls3 {
494 5     5   29 _verify_bls3( _read_vars($_[0], 'BLS3', qw/N Q A/) );
495             }
496             sub _prove_pock {
497 5     5   25 _verify_pock( _read_vars($_[0], 'POCKLINGTON', qw/N Q A/) );
498             }
499             sub _prove_small {
500 3     3   10 _verify_small( _read_vars($_[0], 'Small', qw/N/) );
501             }
502             sub _prove_bls5 {
503 16     16   39 my $lines = shift;
504             # No good way to do this using read_vars
505 16         36 my ($n, @Q, @A);
506 16         33 my $index = 0;
507 16         66 $Q[0] = Math::BigInt->new(2); # 2 is implicit
508 16         711 while (1) {
509 124         3742 my $line = shift @$lines;
510 124 50       242 return _primality_error("end of file during type BLS5") unless defined $line;
511             # Skip comments and blank lines
512 124 50 33     511 next if $line =~ /^\s*#/ or $line =~ /^\s*$/;
513             # Stop when we see a line starting with -.
514 124 100       234 last if $line =~ /^-/;
515 108         145 chomp($line);
516 108 100       411 if ($line =~ /^N\s+(\d+)/) {
    100          
    50          
517 16 50       49 return _primality_error("BLS5: N redefined") if defined $n;
518 16         72 $n = Math::BigInt->new("$1");
519             } elsif ($line =~ /^Q\[(\d+)\]\s+(\d+)/) {
520 64         92 $index++;
521 64 50       150 return _primality_error("BLS5: Invalid index: $1") unless $1 == $index;
522 64         187 $Q[$1] = Math::BigInt->new("$2");
523             } elsif ($line =~ /^A\[(\d+)\]\s+(\d+)/) {
524 28 50 33     117 return _primality_error("BLS5: Invalid index: A[$1]") unless $1 >= 0 && $1 <= $index;
525 28         81 $A[$1] = Math::BigInt->new("$2");
526             } else {
527 0         0 return _primality_error("Unrecognized line: $line");
528             }
529             }
530 16         64 _verify_bls5($n, \@Q, \@A);
531             }
532              
533             sub _prove_lucas {
534 11     11   17 my $lines = shift;
535             # No good way to do this using read_vars
536 11         23 my ($n, @Q, $a);
537 11         18 my $index = 0;
538 11         17 while (1) {
539 67         1989 my $line = shift @$lines;
540 67 50       137 return _primality_error("end of file during type Lucas") unless defined $line;
541             # Skip comments and blank lines
542 67 50 33     310 next if $line =~ /^\s*#/ or $line =~ /^\s*$/;
543 67         105 chomp($line);
544 67 100       253 if ($line =~ /^N\s+(\d+)/) {
    100          
    50          
545 11 50       29 return _primality_error("Lucas: N redefined") if defined $n;
546 11         45 $n = Math::BigInt->new("$1");
547             } elsif ($line =~ /^Q\[(\d+)\]\s+(\d+)/) {
548 45         73 $index++;
549 45 50       100 return _primality_error("Lucas: Invalid index: $1") unless $1 == $index;
550 45         132 $Q[$1] = Math::BigInt->new("$2");
551             } elsif ($line =~ /^A\s+(\d+)/) {
552 11         43 $a = Math::BigInt->new("$1");
553 11         422 last;
554             } else {
555 0         0 return _primality_error("Unrecognized line: $line");
556             }
557             }
558 11         54 _verify_lucas($n, \@Q, $a);
559             }
560              
561             # Verification routines
562              
563             sub _verify_ecpp {
564 14     14   1376 my ($n, $a, $b, $m, $q, $x, $y) = @_;
565 14 50       62 return unless defined $n;
566 14 50       59 $a %= $n if $a < 0;
567 14 50       2631 $b %= $n if $b < 0;
568 14 50       2339 return _pfail "ECPP: $n failed N > 0" unless $n > 0;
569 14 100       2415 return _pfail "ECPP: $n failed gcd(N, 6) = 1" unless Math::BigInt::bgcd($n, 6) == 1;
570 13 100       4162 return _pfail "ECPP: $n failed gcd(4*a^3 + 27*b^2, N) = 1"
571             unless Math::BigInt::bgcd(4*$a*$a*$a+27*$b*$b,$n) == 1;
572 12 50       14503 return _pfail "ECPP: $n failed Y^2 = X^3 + A*X + B mod N"
573             unless ($y*$y) % $n == ($x*$x*$x + $a*$x + $b) % $n;
574 12 100       9552 return _pfail "ECPP: $n failed M >= N - 2*sqrt(N) + 1" unless $m >= $n + 1 - $n->copy->bmul(4)->bsqrt();
575 11 50       11028 return _pfail "ECPP: $n failed M <= N + 2*sqrt(N) + 1" unless $m <= $n + 1 + $n->copy->bmul(4)->bsqrt();
576 11 100       10102 return _pfail "ECPP: $n failed Q > (N^(1/4)+1)^2" unless $q > $n->copy->broot(4)->badd(1)->bpow(2);
577 10 50       15086 return _pfail "ECPP: $n failed Q < N" unless $q < $n;
578 10 50       336 return _pfail "ECPP: $n failed M != Q" unless $m != $q;
579 10         365 my ($mdivq, $rem) = $m->copy->bdiv($q);
580 10 100       2212 return _pfail "ECPP: $n failed Q divides M" unless $rem == 0;
581              
582             # Now verify the elliptic curve
583 9         1438 my $correct_point = 0;
584 9 50 33     53 if (prime_get_config->{'gmp'} && defined &Math::Prime::Util::GMP::_validate_ecpp_curve) {
585 0         0 $correct_point = Math::Prime::Util::GMP::_validate_ecpp_curve($a, $b, $n, $x, $y, $m, $q);
586             } else {
587 9 100       36 if (!defined $Math::Prime::Util::ECAffinePoint::VERSION) {
588 1         544 eval { require Math::Prime::Util::ECAffinePoint; 1; }
  1         5  
589 1 50       3 or do { die "Cannot load Math::Prime::Util::ECAffinePoint"; };
  0         0  
590             }
591 9         58 my $ECP = Math::Prime::Util::ECAffinePoint->new($a, $b, $n, $x, $y);
592             # Compute U = (m/q)P, check U != point at infinity
593 9         29 $ECP->mul( $m->copy->bdiv($q)->as_int );
594 9 50       53 if (!$ECP->is_infinity) {
595             # Compute V = qU, check V = point at infinity
596 9         137 $ECP->mul( $q );
597 9 50       42 $correct_point = 1 if $ECP->is_infinity;
598             }
599             }
600 9 50       326 return _pfail "ECPP: $n failed elliptic curve conditions" unless $correct_point;
601 9         87 ($n, $q);
602             }
603             sub _verify_ecpp3 {
604 1     1   34 my ($n, $s, $r, $a, $b, $t) = @_;
605 1 50       6 return unless defined $n;
606 1 50       4 return _pfail "ECPP3: $n failed |A| <= N/2" unless abs($a) <= $n/2;
607 1 50       304 return _pfail "ECPP3: $n failed |B| <= N/2" unless abs($b) <= $n/2;
608 1 50       254 return _pfail "ECPP3: $n failed T >= 0" unless $t >= 0;
609 1 50       139 return _pfail "ECPP3: $n failed T < N" unless $t < $n;
610 1         34 my $l = ($t*$t*$t + $a*$t + $b) % $n;
611 1         393 _verify_ecpp( $n,
612             ($a * $l*$l) % $n,
613             ($b * $l*$l*$l) % $n,
614             $r*$s,
615             $r,
616             ($t*$l) % $n,
617             ($l*$l) % $n );
618             }
619             sub _verify_ecpp4 {
620 0     0   0 my ($n, $s, $r, $j, $t) = @_;
621 0 0       0 return unless defined $n;
622 0 0       0 return _pfail "ECPP4: $n failed |J| <= N/2" unless abs($j) <= $n/2;
623 0 0       0 return _pfail "ECPP4: $n failed T >= 0" unless $t >= 0;
624 0 0       0 return _pfail "ECPP4: $n failed T < N" unless $t < $n;
625 0         0 my $a = 3 * $j * (1728 - $j);
626 0         0 my $b = 2 * $j * (1728 - $j) * (1728 - $j);
627 0         0 my $l = ($t*$t*$t + $a*$t + $b) % $n;
628 0         0 _verify_ecpp( $n,
629             ($a * $l*$l) % $n,
630             ($b * $l*$l*$l) % $n,
631             $r*$s,
632             $r,
633             ($t*$l) % $n,
634             ($l*$l) % $n );
635             }
636              
637             sub _verify_bls15 {
638 1     1   34 my ($n, $q, $lp, $lq) = @_;
639 1 50       4 return unless defined $n;
640 1 50       6 return _pfail "BLS15: $n failed Q odd" unless $q->is_odd();
641 1 50       18 return _pfail "BLS15: $n failed Q > 2" unless $q > 2;
642 1         99 my ($m, $rem) = ($n+1)->copy->bdiv($q);
643 1 50       426 return _pfail "BLS15: $n failed Q divides N+1" unless $rem == 0;
644 1 50       138 return _pfail "BLS15: $n failed MQ-1 = N" unless $m*$q-1 == $n;
645 1 50       309 return _pfail "BLS15: $n failed M > 0" unless $m > 0;
646 1 50       136 return _pfail "BLS15: $n failed 2Q-1 > sqrt(N)" unless 2*$q-1 > $n->copy->bsqrt();
647 1         1075 my $D = $lp*$lp - 4*$lq;
648 1 50       277 return _pfail "BLS15: $n failed D != 0" unless $D != 0;
649 1 50       135 return _pfail "BLS15: $n failed jacobi(D,N) = -1" unless _jacobi($D,$n) == -1;
650 1 50       5 return _pfail "BLS15: $n failed V_{m/2} mod N != 0"
651             unless (lucas_sequence($n, $lp, $lq, $m/2))[1] != 0;
652 1 50       213 return _pfail "BLS15: $n failed V_{(N+1)/2} mod N == 0"
653             unless (lucas_sequence($n, $lp, $lq, ($n+1)/2))[1] == 0;
654 1         218 ($n, $q);
655             }
656              
657             sub _verify_bls3 {
658 5     5   171 my ($n, $q, $a) = @_;
659 5 50       21 return unless defined $n;
660 5 50       21 return _pfail "BLS3: $n failed Q odd" unless $q->is_odd();
661 5 50       122 return _pfail "BLS3: $n failed Q > 2" unless $q > 2;
662 5         523 my ($m, $rem) = ($n-1)->copy->bdiv($q);
663 5 50       2192 return _pfail "BLS3: $n failed Q divides N-1" unless $rem == 0;
664 5 50       709 return _pfail "BLS3: $n failed MQ+1 = N" unless $m*$q+1 == $n;
665 5 50       1457 return _pfail "BLS3: $n failed M > 0" unless $m > 0;
666 5 50       671 return _pfail "BLS3: $n failed 2Q+1 > sqrt(n)" unless 2*$q+1 > $n->copy->bsqrt();
667 5 50       5525 return _pfail "BLS3: $n failed A^((N-1)/2) = N-1 mod N" unless $a->copy->bmodpow(($n-1)/2, $n) == $n-1;
668 5 50       134668 return _pfail "BLS3: $n failed A^(M/2) != N-1 mod N" unless $a->copy->bmodpow($m/2,$n) != $n-1;
669 5         50437 ($n, $q);
670             }
671             sub _verify_pock {
672 5     5   206 my ($n, $q, $a) = @_;
673 5 50       19 return unless defined $n;
674 5         19 my ($m, $rem) = ($n-1)->copy->bdiv($q);
675 5 50       2273 return _pfail "Pocklington: $n failed Q divides N-1" unless $rem == 0;
676 5 50       772 return _pfail "Pocklington: $n failed M is even" unless $m->is_even();
677 5 50       79 return _pfail "Pocklington: $n failed M > 0" unless $m > 0;
678 5 50       702 return _pfail "Pocklington: $n failed M < Q" unless $m < $q;
679 5 50       180 return _pfail "Pocklington: $n failed MQ+1 = N" unless $m*$q+1 == $n;
680 5 50       1423 return _pfail "Pocklington: $n failed A > 1" unless $a > 1;
681 5 50       482 return _pfail "Pocklington: $n failed A^(N-1) mod N = 1"
682             unless $a->copy->bmodpow($n-1, $n) == 1;
683 5 50       150635 return _pfail "Pocklington: $n failed gcd(A^M - 1, N) = 1"
684             unless Math::BigInt::bgcd($a->copy->bmodpow($m, $n)-1, $n) == 1;
685 5         77553 ($n, $q);
686             }
687             sub _verify_small {
688 3     3   157 my ($n) = @_;
689 3 50       10 return unless defined $n;
690 3 50       13 return _pfail "Small n $n is > 2^64\n" if $n > $_smallval;
691 3 50       174 return _pfail "Small n $n does not pass BPSW" unless is_prob_prime($n);
692 3         187 ($n);
693             }
694              
695             sub _verify_bls5 {
696 16     16   46 my ($n, $Qr, $Ar) = @_;
697 16 50       67 return unless defined $n;
698 16         64 my @Q = @{$Qr};
  16         50  
699 16         31 my @A = @{$Ar};
  16         36  
700 16         57 my $nm1 = $n - 1;
701 16         3390 my $F = Math::BigInt->bone;
702 16         481 my $R = $nm1->copy;
703 16         297 my $index = $#Q;
704 16         56 foreach my $i (0 .. $index) {
705 77 50       25466 return _primality_error "BLS5: $n failed Q[$i] doesn't exist" unless defined $Q[$i];
706 77 100       228 $A[$i] = Math::BigInt->new(2) unless defined $A[$i];
707 77 50       1721 return _pfail "BLS5: $n failed Q[$i] > 1" unless $Q[$i] > 1;
708 77 50       6912 return _pfail "BLS5: $n failed Q[$i] < N-1" unless $Q[$i] < $nm1;
709 77 50       2214 return _pfail "BLS5: $n failed A[$i] > 1" unless $A[$i] > 1;
710 77 50       6465 return _pfail "BLS5: $n failed A[$i] < N" unless $A[$i] < $n;
711 77 100       2097 return _pfail "BLS5: $n failed Q[$i] divides N-1" unless ($nm1 % $Q[$i]) == 0;
712 76         21110 while (($R % $Q[$i]) == 0) {
713 88         23252 $F *= $Q[$i];
714 88         5129 $R /= $Q[$i];
715             }
716             }
717 15 50       5339 die "BLS5: Internal error R != (N-1)/F\n" unless $R == $nm1/$F;
718 15 50       3934 return _pfail "BLS5: $n failed F is even" unless $F->is_even();
719 15 50       241 return _pfail "BLS5: $n failed gcd(F, R) = 1\n" unless Math::BigInt::bgcd($F,$R) == 1;
720 15         16577 my ($s, $r) = $R->copy->bdiv(2*$F);
721 15         5250 my $P = ($F+1) * (2 * $F * $F + ($r-1)*$F + 1);
722 15 100       15289 return _pfail "BLS5: $n failed n < P" unless $n < $P;
723 13 50 66     453 return _pfail "BLS5: $n failed s=0 OR r^2-8s not a perfect square"
724             unless $s == 0 or !_is_perfect_square($r*$r - 8*$s);
725 13         1308 foreach my $i (0 .. $index) {
726 68         33093044 my $a = $A[$i];
727 68         165 my $q = $Q[$i];
728 68 50       258 return _pfail "BLS5: $n failed A[i]^(N-1) mod N = 1"
729             unless $a->copy->bmodpow($nm1, $n)->is_one;
730 68 100       47474401 return _pfail "BLS5: $n failed gcd(A[i]^((N-1)/Q[i])-1, N) = 1"
731             unless Math::BigInt::bgcd($a->copy->bmodpow($nm1/$q, $n)->bdec, $n)->is_one;
732             }
733 12         2806487 ($n, @Q);
734             }
735              
736             sub _verify_lucas {
737 11     11   32 my ($n, $Qr, $a) = @_;
738 11 50       32 return unless defined $n;
739 11         17 my @Q = @{$Qr};
  11         29  
740 11         22 my $index = $#Q;
741 11 50       38 return _pfail "Lucas: $n failed A > 1" unless $a > 1;
742 11 100       1179 return _pfail "Lucas: $n failed A < N" unless $a < $n;
743 10         338 my $nm1 = $n - 1;
744 10         2106 my $F = Math::BigInt->bone;
745 10         295 my $R = $nm1->copy;
746 10 50       194 return _pfail "Lucas: $n failed A^(N-1) mod N = 1"
747             unless $a->copy->bmodpow($nm1, $n) == 1;
748 10         43218 foreach my $i (1 .. $index) {
749 26 50       6499 return _primality_error "Lucas: $n failed Q[$i] doesn't exist" unless defined $Q[$i];
750 26 50       86 return _pfail "Lucas: $n failed Q[$i] > 1" unless $Q[$i] > 1;
751 26 50       2803 return _pfail "Lucas: $n failed Q[$i] < N-1" unless $Q[$i] < $nm1;
752 26 100       899 return _pfail "Lucas: $n failed Q[$i] divides N-1" unless ($nm1 % $Q[$i]) == 0;
753 23 100       6340 return _pfail "Lucas: $n failed A^((N-1)/Q[$i]) mod N != 1"
754             unless $a->copy->bmodpow($nm1/$Q[$i], $n) != 1;
755 22         78577 while (($R % $Q[$i]) == 0) {
756 29         8519 $F *= $Q[$i];
757 29         1728 $R /= $Q[$i];
758             }
759             }
760 6 100 66     1989 return _pfail("Lucas: $n failed N-1 has only factors Q") unless $R == 1 && $F == $nm1;
761 5         550 shift @Q; # Remove Q[0]
762 5         28 ($n, @Q);
763             }
764              
765             sub verify_cert {
766 50 100   50 1 232 my $cert = (@_ == 1) ? $_[0] : convert_array_cert_to_string(@_);
767 50 100       296 $cert = convert_array_cert_to_string($cert) if ref($cert) eq 'ARRAY';
768 50 100       193 return 0 if $cert eq '';
769              
770 44         99 my %parts; # Map of "N is prime if Q is prime"
771 44         510 my %proof_funcs = (
772             ECPP => \&_prove_ecpp, # Standard ECPP proof
773             ECPP3 => \&_prove_ecpp3, # Primo type 3
774             ECPP4 => \&_prove_ecpp4, # Primo type 4
775             BLS15 => \&_prove_bls15, # basic n+1, includes Primo type 2
776             BLS3 => \&_prove_bls3, # basic n-1
777             BLS5 => \&_prove_bls5, # much better n-1
778             SMALL => \&_prove_small, # n <= 2^64
779             POCKLINGTON => \&_prove_pock, # simple n-1, Primo type 1
780             LUCAS => \&_prove_lucas, # n-1 completely factored
781             );
782 44         122 my $base = 10;
783 44         116 my $cert_type = 'Unknown';
784 44         80 my $N;
785              
786 44         286 my @lines = split /^/, $cert;
787 44         101 my $lines = \@lines;
788 44         167 while (@$lines) {
789 287         1290 my $line = shift @$lines;
790 287 100 66     1707 next if $line =~ /^\s*#/ or $line =~ /^\s*$/; # Skip comments / blank lines
791 187         492 chomp($line);
792 187 100       633 if ($line =~ /^\[(\S+) - Primality Certificate\]/) {
793 44 50       207 if ($1 ne 'MPU') {
794 0         0 return _primality_error "Unknown certificate type: $1";
795             }
796 44         116 $cert_type = $1;
797 44         145 next;
798             }
799 143 100 33     1037 if ( ($cert_type eq 'PRIMO' && $line =~ /^\[Candidate\]/) || ($cert_type eq 'MPU' && $line =~ /^Proof for:/) ) {
      66        
      66        
800 44 50       139 return _primality_error "Certificate with multiple N values" if defined $N;
801 44         175 ($N) = _read_vars($lines, 'Proof for', qw/N/);
802 44 100       3040 if (!is_prob_prime($N)) {
803 2         156 _pfail "N '$N' does not look prime.";
804 2         20 return 0;
805             }
806 42         4632 next;
807             }
808 99 50       288 if ($line =~ /^Base (\d+)/) {
809 0         0 $base = $1;
810 0 0       0 return _primality_error "Only base 10 supported, sorry" unless $base == 10;
811 0         0 next;
812             }
813 99 100       457 if ($line =~ /^Type (.*?)\s*$/) {
814 55 50       178 return _primality_error("Starting type without telling me the N value!") unless defined $N;
815 55         164 my $type = $1;
816 55         142 $type =~ tr/a-z/A-Z/;
817 55 50       200 error("Unknown type: $type") unless defined $proof_funcs{$type};
818 55         216 my ($n, @q) = $proof_funcs{$type}->($lines);
819 55 100       565 return 0 unless defined $n;
820 40         224 $parts{$n} = [@q];
821             }
822             }
823              
824 27 50       969 return _primality_error("No N") unless defined $N;
825 27         85 my @qs = ($N);
826 27         102 while (@qs) {
827 126         3214 my $q = shift @qs;
828             # Check that this q has a chain
829 126 100       245 if (!defined $parts{$q}) {
830 90 50       1503 if ($q > $_smallval) {
831 0         0 _primality_error "q value $q has no proof\n";
832 0         0 return 0;
833             }
834 90 100       2979 if (!is_prob_prime($q)) {
835 2         69 _pfail "Small n $q does not pass BPSW";
836 2         36 return 0;
837             }
838             } else {
839 36 50       888 die "Internal error: Invalid parts entry" if ref($parts{$q}) ne 'ARRAY';
840             # q is prime if all it's chains are prime.
841 36         811 push @qs, @{$parts{$q}};
  36         77  
842             }
843             }
844 25         1825 1;
845             }
846              
847             1;
848              
849             __END__