hybrid_prime_factorization.pl 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. #!/usr/bin/perl
  2. # Daniel "Trizen" Șuteu
  3. # Date: 08 March 2018
  4. # https://github.com/trizen
  5. # A hybrid factorization algorithm, using:
  6. # * Pollard's p-1 algorithm
  7. # * Pollard's rho algorithm
  8. # * A simple version of the continued-fraction factorization method
  9. # * Fermat's factorization method
  10. # See also:
  11. # https://en.wikipedia.org/wiki/Quadratic_sieve
  12. # https://en.wikipedia.org/wiki/Dixon%27s_factorization_method
  13. # https://en.wikipedia.org/wiki/Fermat%27s_factorization_method
  14. # https://en.wikipedia.org/wiki/Pollard%27s_p_%E2%88%92_1_algorithm
  15. use 5.020;
  16. use strict;
  17. use warnings;
  18. use experimental qw(signatures);
  19. use ntheory qw(is_prime random_prime vecprod);
  20. use Math::AnyNum qw(
  21. gcd valuation powmod irand ipow
  22. isqrt idiv is_square next_prime
  23. );
  24. sub fermat_hybrid_factorization ($n) {
  25. return () if $n <= 1;
  26. return ($n) if is_prime($n);
  27. # Test for divisibility by 2
  28. if (!($n & 1)) {
  29. my $v = valuation($n, 2);
  30. my $t = $n >> $v;
  31. my @factors = (2) x $v;
  32. if ($t > 1) {
  33. push @factors, __SUB__->($t);
  34. }
  35. return @factors;
  36. }
  37. my $p = isqrt($n);
  38. my $x = $p;
  39. my $q = ($p * $p - $n);
  40. my $t = 1;
  41. my $h = 1;
  42. my $z = Math::AnyNum->new(random_prime($n));
  43. my $g = 1;
  44. my $c = $q + $p;
  45. my $a0 = 1;
  46. my $a1 = ($a0 * $a0 + $c);
  47. my $a2 = ($a1 * $a1 + $c);
  48. my $c1 = $p;
  49. my $c2 = 1;
  50. my $r = $p + $p;
  51. my ($e1, $e2) = (1, 0);
  52. my ($f1, $f2) = (0, 1);
  53. while (not is_square($q)) {
  54. $q += 2 * $p++ + 1;
  55. # Pollard's rho algorithm
  56. $g = gcd($n, $a2 - $a1);
  57. if ($g > 1 and $g < $n) {
  58. return sort { $a <=> $b } (
  59. __SUB__->($g),
  60. __SUB__->($n / $g),
  61. );
  62. }
  63. $a1 = (($a1 * $a1 + $c) % $n);
  64. $a2 = (($a2 * $a2 + $c) % $n);
  65. $a2 = (($a2 * $a2 + $c) % $n);
  66. # Simple version of the continued-fraction factorization method.
  67. # Efficient for numbers that have factors relatively close to sqrt(n)
  68. $c1 = $r * $c2 - $c1;
  69. $c2 = idiv($n - $c1 * $c1, $c2);
  70. my $x1 = ($x * $f2 + $e2) % $n;
  71. my $y1 = ($x1 * $x1) % $n;
  72. if (is_square($y1)) {
  73. $g = gcd($x1 - isqrt($y1), $n);
  74. if ($g > 1 and $g < $n) {
  75. return sort { $a <=> $b } (
  76. __SUB__->($g),
  77. __SUB__->($n / $g),
  78. );
  79. }
  80. }
  81. $r = idiv($x + $c1, $c2);
  82. ($f1, $f2) = ($f2, ($r * $f2 + $f1) % $n);
  83. ($e1, $e2) = ($e2, ($r * $e2 + $e1) % $n);
  84. # Pollard's p-a algorithm (random variation)
  85. $t = $z;
  86. $h = next_prime($h);
  87. $z = powmod($z, $h, $n);
  88. $g = gcd($z * powmod($t, irand($n), $n) - 1, $n);
  89. if ($g > 1) {
  90. if ($g == $n) {
  91. $h = 1;
  92. $z = Math::AnyNum->new(random_prime($n));
  93. next;
  94. }
  95. return sort { $a <=> $b } (
  96. __SUB__->($g),
  97. __SUB__->($n / $g),
  98. );
  99. }
  100. }
  101. # Fermat's method
  102. my $s = isqrt($q);
  103. return sort { $a <=> $b } (
  104. __SUB__->($p + $s),
  105. __SUB__->($p - $s),
  106. );
  107. }
  108. my @tests = map { Math::AnyNum->new($_) } qw(
  109. 160587846247027 5040 65127835124 6469693230
  110. 12129569695640600539 38568900844635025971879799293495379321
  111. 5057557777500469647488909553014309710588182149566739774380944488183531188525863600127265768146701283
  112. );
  113. foreach my $n (@tests) {
  114. my @f = fermat_hybrid_factorization($n);
  115. say "$n = ", join(' * ', @f);
  116. die 'error' if vecprod(@f) != $n;
  117. die 'error' if grep { !is_prime($_) } @f;
  118. }
  119. say "\n=> Factoring 2^k+1";
  120. foreach my $k (1 .. 100) {
  121. my $n = ipow(2, $k) + 1;
  122. my @f = fermat_hybrid_factorization($n);
  123. say "2^$k + 1 = ", join(' * ', @f);
  124. die 'error' if vecprod(@f) != $n;
  125. die 'error' if grep { !is_prime($_) } @f;
  126. }
  127. # Test the continued-fraction method with factors relatively close to sqrt(n)
  128. foreach my $k (1 .. 100) {
  129. my $p = random_prime(ipow(2, 100 + $k));
  130. my $n = next_prime($p + irand(10**15)) * $p;
  131. my @f = fermat_hybrid_factorization($n);
  132. #say join(' * ', @f), " = $n";
  133. die 'error' if vecprod(@f) != $n;
  134. die 'error' if grep { !is_prime($_) } @f;
  135. }
  136. # Test for small numbers
  137. for my $n (1 .. 1000) {
  138. my @f = fermat_hybrid_factorization($n);
  139. die 'error' if vecprod(@f) != $n;
  140. die 'error' if grep { !is_prime($_) } @f;
  141. }