modular_lucas_sequences_U_V.pl 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. #!/usr/bin/perl
  2. # Algorithm due to M. Joye and J.-J. Quisquater for efficiently computing the Lucas U and V sequences (mod m).
  3. # See also:
  4. # https://en.wikipedia.org/wiki/Lucas_sequence
  5. use 5.020;
  6. use warnings;
  7. use experimental qw(signatures);
  8. use Math::GMPz;
  9. sub lucas_UV_mod ($P, $Q, $n, $m) {
  10. $n = Math::GMPz->new("$n");
  11. $P = Math::GMPz->new("$P");
  12. $Q = Math::GMPz->new("$Q");
  13. $m = Math::GMPz->new("$m");
  14. my $U1 = Math::GMPz::Rmpz_init_set_ui(1);
  15. my ($V1, $V2) = (Math::GMPz::Rmpz_init_set_ui(2), Math::GMPz::Rmpz_init_set($P));
  16. my ($Q1, $Q2) = (Math::GMPz::Rmpz_init_set_ui(1), Math::GMPz::Rmpz_init_set_ui(1));
  17. my $t = Math::GMPz::Rmpz_init_set_ui(2);
  18. my $s = Math::GMPz::Rmpz_remove($t, $n, $t);
  19. foreach my $bit (split(//, substr(Math::GMPz::Rmpz_get_str($t, 2), 0, -1))) {
  20. Math::GMPz::Rmpz_mul($Q1, $Q1, $Q2);
  21. Math::GMPz::Rmpz_mod($Q1, $Q1, $m);
  22. if ($bit) {
  23. #~ Q2 = (Q1 * Q)%m
  24. #~ U1 = (U1 * V2)%m
  25. #~ V1 = (V2*V1 - P*Q1)%m
  26. #~ V2 = (V2*V2 - 2*Q2)%m
  27. Math::GMPz::Rmpz_mul($Q2, $Q1, $Q);
  28. Math::GMPz::Rmpz_mul($U1, $U1, $V2);
  29. Math::GMPz::Rmpz_mul($V1, $V1, $V2);
  30. Math::GMPz::Rmpz_powm_ui($V2, $V2, 2, $m);
  31. Math::GMPz::Rmpz_submul($V1, $Q1, $P);
  32. Math::GMPz::Rmpz_submul_ui($V2, $Q2, 2);
  33. Math::GMPz::Rmpz_mod($V1, $V1, $m);
  34. Math::GMPz::Rmpz_mod($U1, $U1, $m);
  35. }
  36. else {
  37. #~ Q2 = Q1
  38. #~ U1 = (U1*V1 - Q1)%m
  39. #~ V2 = (V2*V1 - P*Q1)%m
  40. #~ V1 = (V1*V1 - 2*Q2)%m
  41. Math::GMPz::Rmpz_set($Q2, $Q1);
  42. Math::GMPz::Rmpz_mul($U1, $U1, $V1);
  43. Math::GMPz::Rmpz_mul($V2, $V2, $V1);
  44. Math::GMPz::Rmpz_sub($U1, $U1, $Q1);
  45. Math::GMPz::Rmpz_powm_ui($V1, $V1, 2, $m);
  46. Math::GMPz::Rmpz_submul($V2, $Q1, $P);
  47. Math::GMPz::Rmpz_submul_ui($V1, $Q2, 2);
  48. Math::GMPz::Rmpz_mod($V2, $V2, $m);
  49. Math::GMPz::Rmpz_mod($U1, $U1, $m);
  50. }
  51. }
  52. #~ Q1 = (Q1 * Q2)%m
  53. #~ Q2 = (Q1 * Q)%m
  54. #~ U1 = (U1*V1 - Q1)%m
  55. #~ V1 = (V2*V1 - P*Q1)%m
  56. #~ Q1 = (Q1 * Q2)%m
  57. Math::GMPz::Rmpz_mul($Q1, $Q1, $Q2);
  58. Math::GMPz::Rmpz_mul($Q2, $Q1, $Q);
  59. Math::GMPz::Rmpz_mul($U1, $U1, $V1);
  60. Math::GMPz::Rmpz_mul($V1, $V1, $V2);
  61. Math::GMPz::Rmpz_sub($U1, $U1, $Q1);
  62. Math::GMPz::Rmpz_submul($V1, $Q1, $P);
  63. Math::GMPz::Rmpz_mul($Q1, $Q1, $Q2);
  64. for (1 .. $s) {
  65. #~ U1 = (U1 * V1)%m
  66. #~ V1 = (V1*V1 - 2*Q1)%m
  67. #~ Q1 = (Q1 * Q1)%m
  68. Math::GMPz::Rmpz_mul($U1, $U1, $V1);
  69. Math::GMPz::Rmpz_mod($U1, $U1, $m);
  70. Math::GMPz::Rmpz_powm_ui($V1, $V1, 2, $m);
  71. Math::GMPz::Rmpz_submul_ui($V1, $Q1, 2);
  72. Math::GMPz::Rmpz_powm_ui($Q1, $Q1, 2, $m);
  73. }
  74. Math::GMPz::Rmpz_mod($U1, $U1, $m);
  75. Math::GMPz::Rmpz_mod($V1, $V1, $m);
  76. return ($U1, $V1);
  77. }
  78. say join(' ', lucas_UV_mod( 1, -1, 123456, 12345)); #=> 1122 4487
  79. say join(' ', lucas_UV_mod(-3, 4, 987654, 12345)); #=> 3855 3928
  80. say join(' ', lucas_UV_mod(-5, -7, 314159, 12345)); #=> 8038 4565