File Coverage

blib/lib/Math/Prime/Util/PrimalityProving.pm
Criterion Covered Total %
statement 506 567 89.2
branch 271 436 62.1
condition 68 144 47.2
subroutine 31 36 86.1
pod 4 4 100.0
total 880 1187 74.1


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