modular_square_root_2.pl 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. #!/usr/bin/perl
  2. # Daniel "Trizen" Șuteu
  3. # Date: 21 July 2018
  4. # https://github.com/trizen
  5. # Find (almost) all solutions to the quadratic congruence:
  6. # x^2 = a (mod n)
  7. use 5.020;
  8. use warnings;
  9. use experimental qw(signatures);
  10. use List::Util qw(uniq);
  11. use ntheory qw(factor_exp chinese forsetproduct);
  12. use Math::Prime::Util::GMP qw(sqrtmod);
  13. use Math::AnyNum qw(:overload powmod ipow);
  14. sub sqrt_mod_n ($z, $n) {
  15. my @roots = sub ($z, $n) {
  16. return 0 if ($n == 1);
  17. return () if ($n < 1);
  18. $z %= $n;
  19. return $n if ($z == 0);
  20. my %congruences;
  21. foreach my $factor (factor_exp($n)) {
  22. my ($p, $e) = @$factor;
  23. my $pp = ipow($p, $e);
  24. if ($p eq '2') {
  25. if ($e == 1) {
  26. if ($z & 1) {
  27. push @{$congruences{$pp}}, [1, $pp];
  28. }
  29. else {
  30. push @{$congruences{$pp}}, [0, $pp];
  31. }
  32. }
  33. elsif ($e == 2) {
  34. if ($z % 4 == 1) {
  35. push @{$congruences{$pp}}, [1, $pp], [3, $pp];
  36. }
  37. else {
  38. push @{$congruences{$pp}}, [0, $pp], [2, $pp];
  39. }
  40. }
  41. elsif ($e == 3) {
  42. if ($z % 8 == 1) {
  43. push @{$congruences{$pp}}, [1, $pp], [3, $pp], [5, $pp], [7, $pp];
  44. }
  45. else {
  46. push @{$congruences{$pp}}, [0, $pp], [2, $pp], [4, $pp], [6, $pp];
  47. }
  48. }
  49. elsif ($z == 1) {
  50. push @{$congruences{$pp}}, [1, $pp], [($pp >> 1) - 1, $pp], [($pp >> 1) + 1, $pp], [$pp - 1, $pp];
  51. }
  52. foreach my $s (__SUB__->($z, $pp >> 1)) {
  53. my $i = ((($s * $s - $z) >> ($e - 1)) % 2);
  54. my $r = ($s + ($i << ($e - 2)));
  55. push @{$congruences{$pp}}, [$r, $pp], [$pp - $r, $pp];
  56. }
  57. next;
  58. }
  59. $p = Math::AnyNum->new($p);
  60. my $x = sqrtmod($z, $p) // next; # Tonelli-Shanks algorithm
  61. my $r = $pp / $p;
  62. my $u = ($pp - 2 * $r + 1) >> 1;
  63. my $t = (powmod($x, $r, $pp) * powmod($z, $u, $pp)) % $pp;
  64. push @{$congruences{$pp}}, [$t, $pp], [$pp - $t, $pp];
  65. }
  66. my @roots;
  67. forsetproduct {
  68. push @roots, chinese(@_);
  69. } values %congruences;
  70. return grep { powmod($_, 2, $n) == $z } uniq(@roots);
  71. }->($z, $n);
  72. sort { $a <=> $b } @roots;
  73. }
  74. my @tests = ([1104, 6630], [2641, 4465], [993, 2048], [472, 972], [441, 920], [841, 905], [289, 992],);
  75. sub bf_sqrtmod ($z, $n) {
  76. grep { ($_ * $_) % $n == $z } 1 .. $n;
  77. }
  78. foreach my $t (@tests) {
  79. my @r = sqrt_mod_n($t->[0], $t->[1]);
  80. say "x^2 = $t->[0] (mod $t->[1]) = {", join(', ', @r), "}";
  81. die "error1 for (@$t) -- @r" if (@r != grep { ($_ * $_) % $t->[1] == $t->[0] } @r);
  82. die "error2 for (@$t) -- @r" if (join(' ', @r) ne join(' ', bf_sqrtmod($t->[0], $t->[1])));
  83. }
  84. say '';
  85. # The algorithm also works for arbitrary large integers
  86. say join(' ', sqrt_mod_n(13**18 * 5**7 - 1, 13**18 * 5**7)); #=> 633398078861605286438568 2308322911594648160422943 6477255756527023177780182 8152180589260066051764557
  87. foreach my $n (1 .. 100) {
  88. my $m = int(rand(10000));
  89. my $z = int(rand($m));
  90. my @a1 = sqrt_mod_n($z, $m);
  91. my @a2 = bf_sqrtmod($z, $m);
  92. if ("@a1" ne "@a2") {
  93. warn "\nerror for ($z, $m):\n\t(@a1) != (@a2)\n";
  94. }
  95. }
  96. say '';
  97. # Too few solutions for some inputs
  98. say 'x^2 = 1701 (mod 6300) = {' . join(' ', sqrt_mod_n(1701, 6300)) . '}';
  99. say 'x^2 = 1701 (mod 6300) = {' . join(', ', bf_sqrtmod(1701, 6300)) . '}';
  100. # No solutions for some inputs (although solutions do exist)
  101. say join(' ', sqrt_mod_n(306, 810));
  102. say join(' ', sqrt_mod_n(2754, 6561));
  103. say join(' ', sqrt_mod_n(17640, 48465));
  104. __END__
  105. x^2 = 1104 (mod 6630) = {642, 1152, 1968, 2478, 4152, 4662, 5478, 5988}
  106. x^2 = 2641 (mod 4465) = {1501, 2071, 2394, 2964}
  107. x^2 = 993 (mod 2048) = {369, 655, 1393, 1679}
  108. x^2 = 472 (mod 972) = {38, 448, 524, 934}
  109. x^2 = 441 (mod 920) = {21, 71, 159, 209, 251, 301, 389, 439, 481, 531, 619, 669, 711, 761, 849, 899}
  110. x^2 = 841 (mod 905) = {29, 391, 514, 876}
  111. x^2 = 289 (mod 992) = {17, 79, 417, 479, 513, 575, 913, 975}