sqrt_mod_p_tonelli-shanks_mpz.pl 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. #!/usr/bin/perl
  2. # Daniel "Trizen" Șuteu
  3. # Date: 11 April 2018
  4. # https://github.com/trizen
  5. # An efficient implementation of the Tonelli-Shanks algorithm, using Math::GMPz.
  6. use 5.020;
  7. use strict;
  8. use warnings;
  9. use Math::GMPz;
  10. use experimental qw(signatures);
  11. sub sqrt_mod ($n, $p) {
  12. if (ref($n) ne 'Math::GMPz') {
  13. $n = Math::GMPz::Rmpz_init_set_str("$n", 10);
  14. }
  15. if (ref($p) ne 'Math::GMPz') {
  16. $p = Math::GMPz::Rmpz_init_set_str("$p", 10);
  17. }
  18. my $q = Math::GMPz::Rmpz_init_set($p);
  19. if (Math::GMPz::Rmpz_divisible_p($n, $p)) {
  20. Math::GMPz::Rmpz_mod($q, $q, $p);
  21. return $q;
  22. }
  23. if (Math::GMPz::Rmpz_legendre($n, $p) != 1) {
  24. die "Not a quadratic residue!";
  25. }
  26. if (Math::GMPz::Rmpz_tstbit($p, 1) == 1) { # p = 3 (mod 4)
  27. # q = n ^ ((p+1) / 4) (mod p)
  28. Math::GMPz::Rmpz_add_ui($q, $q, 1); # q = p+1
  29. Math::GMPz::Rmpz_fdiv_q_2exp($q, $q, 2); # q = (p+1)/4
  30. Math::GMPz::Rmpz_powm($q, $n, $q, $p); # q = n^q (mod p)
  31. return $q;
  32. }
  33. Math::GMPz::Rmpz_sub_ui($q, $q, 1); # q = p-1
  34. # Factor out 2^s from q
  35. my $s = Math::GMPz::Rmpz_remove($q, $q, Math::GMPz::Rmpz_init_set_ui(2));
  36. # Search for a non-residue mod p by picking the first w such that (w|p) is -1
  37. my $w = 2;
  38. while (Math::GMPz::Rmpz_ui_kronecker($w, $p) != -1) { ++$w }
  39. $w = Math::GMPz::Rmpz_init_set_ui($w);
  40. Math::GMPz::Rmpz_powm($w, $w, $q, $p); # w = w^q (mod p)
  41. Math::GMPz::Rmpz_add_ui($q, $q, 1); # q = q+1
  42. Math::GMPz::Rmpz_fdiv_q_2exp($q, $q, 1); # q = (q+1) / 2
  43. my $n_inv = Math::GMPz::Rmpz_init();
  44. Math::GMPz::Rmpz_powm($q, $n, $q, $p); # q = n^q (mod p)
  45. Math::GMPz::Rmpz_invert($n_inv, $n, $p);
  46. my $y = Math::GMPz::Rmpz_init();
  47. for (; ;) {
  48. Math::GMPz::Rmpz_powm_ui($y, $q, 2, $p); # y = q^2 (mod p)
  49. Math::GMPz::Rmpz_mul($y, $y, $n_inv);
  50. Math::GMPz::Rmpz_mod($y, $y, $p); # y = y * n^-1 (mod p)
  51. my $i = 0;
  52. for (; Math::GMPz::Rmpz_cmp_ui($y, 1) ; ++$i) {
  53. Math::GMPz::Rmpz_powm_ui($y, $y, 2, $p); # y = y ^ 2 (mod p)
  54. }
  55. if ($i == 0) { # q^2 * n^-1 = 1 (mod p)
  56. return $q;
  57. }
  58. if ($s - $i == 1) {
  59. Math::GMPz::Rmpz_mul($q, $q, $w);
  60. }
  61. else {
  62. Math::GMPz::Rmpz_powm_ui($y, $w, 1 << ($s - $i - 1), $p);
  63. Math::GMPz::Rmpz_mul($q, $q, $y);
  64. }
  65. Math::GMPz::Rmpz_mod($q, $q, $p);
  66. }
  67. return $q;
  68. }
  69. say sqrt_mod('1030', '10009');
  70. say sqrt_mod('44402', '100049');
  71. say sqrt_mod('665820697', '1000000009');
  72. say sqrt_mod('881398088036', '1000000000039');
  73. say sqrt_mod('41660815127637347468140745042827704103445750172002', '100000000000000000000000000000000000000000000000577');