elliptic-curve_factorization_method_with_B2_stage.pl 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. #!/usr/bin/perl
  2. # The elliptic-curve factorization method (ECM), due to Hendrik Lenstra, with B2 stage.
  3. # Code translated from the SymPy file "ntheory/ecm.py".
  4. package Point {
  5. use 5.036;
  6. use Math::Prime::Util::GMP qw(:all);
  7. if (!defined(&submod)) {
  8. *submod = sub ($x, $y, $m) {
  9. addmod($x, "-$y", $m);
  10. };
  11. }
  12. if (!defined(&muladdmod)) {
  13. *muladdmod = sub ($x, $y, $z, $m) {
  14. addmod(mulmod($x, $y, $m), $z, $m);
  15. };
  16. }
  17. sub new {
  18. my ($class, $x_cord, $z_cord, $a_24, $mod) = @_;
  19. bless {
  20. x_cord => $x_cord,
  21. z_cord => $z_cord,
  22. a_24 => $a_24,
  23. mod => $mod,
  24. }, $class;
  25. }
  26. sub add ($self, $Q, $diff) {
  27. my $u = mulmod(submod($self->{x_cord}, $self->{z_cord}, $self->{mod}), addmod($Q->{x_cord}, $Q->{z_cord}, $self->{mod}), $self->{mod});
  28. my $v = mulmod(addmod($self->{x_cord}, $self->{z_cord}, $self->{mod}), submod($Q->{x_cord}, $Q->{z_cord}, $self->{mod}), $self->{mod});
  29. my ($add, $subt) = (addmod($u, $v, $self->{mod}), submod($u, $v, $self->{mod}));
  30. my $new_x_cord = mulmod($diff->{z_cord}, mulmod($add, $add, $self->{mod}), $self->{mod});
  31. my $new_z_cord = mulmod($diff->{x_cord}, mulmod($subt, $subt, $self->{mod}), $self->{mod});
  32. return Point->new($new_x_cord, $new_z_cord, $self->{a_24}, $self->{mod});
  33. }
  34. sub double ($self) {
  35. my $u = powmod(addmod($self->{x_cord}, $self->{z_cord}, $self->{mod}), 2, $self->{mod});
  36. my $v = powmod(submod($self->{x_cord}, $self->{z_cord}, $self->{mod}), 2, $self->{mod});
  37. my $diff = submod($u, $v, $self->{mod});
  38. my $new_x_cord = mulmod($u, $v, $self->{mod});
  39. my $new_z_cord = mulmod($diff, muladdmod($self->{a_24}, $diff, $v, $self->{mod}), $self->{mod});
  40. return Point->new($new_x_cord, $new_z_cord, $self->{a_24}, $self->{mod});
  41. }
  42. sub mont_ladder ($self, $k) {
  43. my $Q = $self;
  44. my $R = $self->double();
  45. my @bits = todigits($k, 2);
  46. shift @bits;
  47. foreach my $i (@bits) {
  48. if ($i eq '1') {
  49. $Q = $R->add($Q, $self);
  50. $R = $R->double();
  51. }
  52. else {
  53. $R = $Q->add($R, $self);
  54. $Q = $Q->double();
  55. }
  56. }
  57. return $Q;
  58. }
  59. }
  60. use 5.036;
  61. use List::Util qw(uniq min);
  62. use Math::Prime::Util::GMP qw(:all);
  63. if (!defined(&submod)) {
  64. *submod = sub ($x, $y, $m) {
  65. addmod($x, "-$y", $m);
  66. };
  67. }
  68. if (!defined(&mulsubmod)) {
  69. *mulsubmod = sub ($x, $y, $z, $m) {
  70. addmod(mulmod($x, "-$y", $m), $z, $m);
  71. };
  72. }
  73. if (!defined(&muladdmod)) {
  74. *muladdmod = sub ($x, $y, $z, $m) {
  75. addmod(mulmod($x, $y, $m), $z, $m);
  76. };
  77. }
  78. sub ecm_one_factor ($n, $B1 = 10_000, $B2 = 100_000, $max_curves = 200) {
  79. if (($B1 % 2 == 1) or ($B2 % 2 == 1)) {
  80. die "The Bounds should be even integers";
  81. }
  82. is_prime($n) && return $n;
  83. my $D = min(sqrtint($B2), ($B1 >> 1) - 1);
  84. my $k = consecutive_integer_lcm($B1);
  85. my (@S, @beta);
  86. my @deltas_list;
  87. my $r_min = $B1 + 2 * $D;
  88. my $r_max = $B2 + 2 * $D;
  89. my $r_step = 4 * $D;
  90. for (my $r = $r_min ; $r <= $r_max ; $r += $r_step) {
  91. my @deltas;
  92. foreach my $q (sieve_primes($r - 2 * $D, $r + 2 * $D)) {
  93. push @deltas, ((abs($q - $r) - 1) >> 1);
  94. }
  95. push @deltas_list, [uniq(@deltas)];
  96. }
  97. for (1 .. $max_curves) {
  98. # Suyama's parametrization
  99. my $sigma = urandomr(6, subint($n, 1));
  100. my $u = mulsubmod($sigma, $sigma, 5, $n);
  101. my $v = mulmod($sigma, 4, $n);
  102. my $u_3 = powmod($u, 3, $n);
  103. my $inv = invmod(mulmod(mulmod($u_3, $v, $n), 16, $n), $n) || return gcd(lcm($u_3, $v), $n);
  104. my $a24 = mulmod(mulmod(powmod(submod($v, $u, $n), 3, $n), muladdmod(3, $u, $v, $n), $n), $inv, $n);
  105. my $Q = Point->new($u_3, powmod($v, 3, $n), $a24, $n);
  106. $Q = $Q->mont_ladder($k);
  107. my $g = gcd($Q->{z_cord}, $n);
  108. # Stage 1 factor
  109. if ($g > 1 and $g < $n) {
  110. return $g;
  111. }
  112. # Stage 1 failure. Q.z = 0, Try another curve
  113. elsif ($g == $n) {
  114. next;
  115. }
  116. # Stage 2 - Improved Standard Continuation
  117. $S[0] = $Q;
  118. my $Q2 = $Q->double();
  119. $S[1] = $Q2->add($Q, $Q);
  120. $beta[0] = mulmod($S[0]->{x_cord}, $S[0]->{z_cord}, $n);
  121. $beta[1] = mulmod($S[1]->{x_cord}, $S[1]->{z_cord}, $n);
  122. foreach my $d (2 .. $D - 1) {
  123. $S[$d] = $S[$d - 1]->add($Q2, $S[$d - 2]);
  124. $beta[$d] = mulmod($S[$d]->{x_cord}, $S[$d]->{z_cord}, $n);
  125. }
  126. $g = 1;
  127. my $W = $Q->mont_ladder(4 * $D);
  128. my $T = $Q->mont_ladder($B1 - 2 * $D);
  129. my $R = $Q->mont_ladder($B1 + 2 * $D);
  130. foreach my $deltas (@deltas_list) {
  131. my $alpha = mulmod($R->{x_cord}, $R->{z_cord}, $n);
  132. foreach my $delta (@$deltas) {
  133. $g = mulmod(
  134. $g,
  135. addmod(
  136. submod(
  137. mulmod(submod($R->{x_cord}, $S[$delta]->{x_cord}, $n), addmod($R->{z_cord}, $S[$delta]->{z_cord}, $n), $n),
  138. $alpha, $n
  139. ),
  140. $beta[$delta],
  141. $n
  142. ),
  143. $n
  144. );
  145. }
  146. # Swap
  147. ($T, $R) = ($R, $R->add($W, $T));
  148. }
  149. $g = gcd($n, $g);
  150. # Stage 2 Factor found
  151. if ($g > 1 and $g < $n) {
  152. return $g;
  153. }
  154. }
  155. # ECM failed, Increase the bounds
  156. die "Increase the bounds";
  157. }
  158. # Params from:
  159. # https://www.rieselprime.de/ziki/Elliptic_curve_method
  160. my @ECM_PARAMS = (
  161. # d B1 curves
  162. [5, 200, 4],
  163. [10, 360, 7],
  164. [13, 600, 20],
  165. [15, 2000, 10],
  166. [20, 11000, 90],
  167. [25, 50000, 300],
  168. [30, 250000, 700],
  169. [35, 1000000, 1800],
  170. [40, 3000000, 5100],
  171. [45, 11000000, 10600],
  172. [50, 43000000, 19300],
  173. [55, 110000000, 49000],
  174. [60, 260000000, 124000],
  175. [65, 850000000, 210000],
  176. [70, 2900000000, 340000],
  177. );
  178. sub ecm ($n, $B1 = undef, $B2 = undef, $max_curves = undef) {
  179. $n <= 1 and die "n must be greater than 1";
  180. if (!defined($B1)) {
  181. foreach my $row (@ECM_PARAMS) {
  182. my ($d, $B1, $curves) = @$row;
  183. ## say ":: Trying to find a prime factor with $d digits using B1 = $B1 with $curves curves";
  184. my @f = eval { __SUB__->($n, $B1, $B1 * 20, $curves) };
  185. return @f if !$@;
  186. }
  187. }
  188. state $primorial = primorial(100_000);
  189. my @factors;
  190. my $g = gcd($n, $primorial);
  191. if ($g > 1) {
  192. push @factors, factor($g);
  193. foreach my $p (@factors) {
  194. $n = divint($n, powint($p, valuation($n, $p)));
  195. }
  196. }
  197. while ($n > 1) {
  198. my $factor = eval { ecm_one_factor($n, $B1, $B2, $max_curves) };
  199. if ($@) {
  200. die "Failed to factor $n: $@";
  201. }
  202. push @factors, $factor;
  203. $n = divint($n, powint($factor, valuation($n, $factor)));
  204. }
  205. @factors = uniq(@factors);
  206. my @final_factors;
  207. foreach my $factor (@factors) {
  208. if (is_prime($factor)) {
  209. push @final_factors, $factor;
  210. }
  211. else {
  212. push @final_factors, __SUB__->($factor, $B1, $B2, $max_curves);
  213. }
  214. }
  215. return sort { $a <=> $b } @final_factors;
  216. }
  217. # Support for numbers provided as command-line arguments
  218. if (@ARGV) {
  219. foreach my $n (@ARGV) {
  220. say "rad($n) = ", join ' * ', ecm($n);
  221. }
  222. exit;
  223. }
  224. say join ' * ', ecm('314159265358979323'); #=> 317213509 * 990371647
  225. say join ' * ', ecm('14304849576137459'); #=> 16100431 * 888476189
  226. say join ' * ', ecm('9804659461513846513'); #=> 4641991 * 2112166839943
  227. say join ' * ', ecm('25645121643901801'); #=> 5394769 * 4753701529
  228. say join ' * ', ecm('17177619065692036843'); #=> 2957613037 * 5807933239
  229. say join ' * ', ecm('195905123644566489241411490581'); #=> 259719190596553 * 754295911652077
  230. say join ' * ', ecm(addint(powint(2, 64), 1)); #=> 274177 * 67280421310721
  231. say join ' * ', ecm(subint(powint(2, 128), 1)); #=> 3 * 5 * 17 * 257 * 641 * 65537 * 274177 * 6700417 * 67280421310721
  232. say join ' * ', ecm(addint(powint(2, 128), 1)); #=> 59649589127497217 * 5704689200685129054721
  233. # Run some tests when no argument is provided
  234. foreach my $n (map { addint(urandomb($_), 2) } 2 .. 100) {
  235. say "rad($n) = ", join(' * ', map { is_prime($_) ? $_ : "$_ (composite)" } ecm($n));
  236. }