=head1 Name
Math::Modular::SquareRoot - Modular square roots
=head1 Synopsis
=cut
package Math::Modular::SquareRoot;
use Carp;
use strict;
use Scalar::Util qw(looks_like_number);
# Check a parameter is a positive integer
sub posInteger($$$)
{my ($s, $p, $n) = @_;
++$p;
if (ref($n) eq "Math::BigInt")
{$n > 0 or croak "$s(parameter $p) must be a positive integer not $n";
}
else
{looks_like_number($n) or croak "$s(parameter $p): $n not a number";
int($n) == $n or croak "$s(parameter $p): $n not an integer";
$n > 1 or croak "$s(parameter $p): $n not allowed as argument";
}
}
# Check a parameter is any integer
sub anyInteger($$$)
{my ($s, $p, $n) = @_;
++$p;
if (ref($n) eq "Math::BigInt")
{}
else
{looks_like_number($n) or croak "$s(parameter $p): $n not a number";
int($n) == $n or croak "$s(parameter $p): $n not an integer";
}
}
=pod
Find the integer square roots of $S modulo $a, where $S,$a are integers:
use Math::Modular::SquareRoot qw(:msqrt);
msqrt1(3,11);
# 5 6
=cut
sub msqrt1($$)
{my ($S, $a) = @_;
anyInteger('msqrt1',0,$S);
posInteger('msqrt1',1,$a);
$S %= $a;
my @r;
push @r, 0 if $S == 0;
my $l = 0;
for($_ = 1; $_ < $a; ++$_)
{$l += 2*$_-1;
$l %= $a;
push @r, $_ if $l == $S;
}
@r
}
=pod
Find the integer square roots of $S modulo $a*$b when $S,$a,$b are
integers:
use Math::Modular::SquareRoot qw(:msqrt);
msqrt2((243243 **2, 1_000_037, 1_000_039);
# 243243 243252243227 756823758219 1000075758200
=cut
sub msqrt2($$$)
{my ($S, $a, $b) = @_;
anyInteger('msqrt2',0,$S);
posInteger('msqrt2',1,$a);
posInteger('msqrt2',2,$b);
my @A = msqrt1($S, $a);
my @B = msqrt1($S, $b);
my ($m, $n) = dgcd($a, $b);
my @r;
for my $A(@A)
{for my $B(@B)
{push @r, (($B-$A)*$a*$m+$A) % ($a*$b);
}
}
@r
}
=pod
Find the greatest common divisor of a list of numbers:
use Math::Modular::SquareRoot qw(gcd);
gcd 10,12,6;
# 2
=cut
sub gcd(@)
{my (@n) = grep {$_} @_;
# Validate
anyInteger('gcd',$_,$_[$_]) for 0..$#_;
@n > 0 or croak "gcd(@_) requires at least one non zero numeric argument";
$_ = abs($_) for @n;
# Find gcd
my $g = sub
{my ($a, $b) = @_;
for(;my $r = $a % $b;)
{$a = $b; $b = $r;
}
$b
};
# Find gcd of list
my $n = shift @n;
$n = &$g($n, $_) for @n;
$n
}
=pod
Find the greatest common divisor of two numbers, optimized for speed
with no parameter checking:
use Math::Modular::SquareRoot qw(gcd2);
gcd2 9,24;
# 3
=cut
sub gcd2($$)
{my ($a, $b) = @_;
for(;my $r = $a % $b;)
{$a = $b; $b = $r;
}
abs $b
}
my $comment = << 'end';
given: a,b, gcd(a,b) == 1, N % a = A, N % b = B find N
=> N = ai + A = bj + B
=> ai - bj = B - A = C
We can find am-bn = 1
=> Cam-Cbn = C
=> Cam = ai, Cbn = bj
=> N = Cam+A = Cbn+B
=> N = (B-A)am+A = (B-A)bn+B
To find m,n for 41m-12n=1
a*m - b*n = c
41 - 12*3 = 5 12/5 = 2, 2+1 = 3
41*3 - 12*10 = 3
5 - 3 = 2
5*2 - 3*3 = 1
=>
41*2 - 12*6 = 10
41*9 - 12*30 = 9
=>
41*-7 - 12*-24 = 1
=>
12*24 - 41*7 = 1
end
=pod
Solve $a*$m+$b*$n == 1 for integers $m,$n, given integers $a,$b where
gcd($a,$b) == 1
use Math::Modular::SquareRoot qw(dgcd);
dgcd(12, 41);
# 24 -7
# 24*12-7*41 == 1
=cut
sub dgcd($$)
{anyInteger('dgcd',$_,$_[$_]) for 0..$#_;
{my $d = gcd2($_[0], $_[1]);
$d == 1 or croak "dgcd(@_) == $d: arguments are not coprime to each other";
}
my $d; $d = sub
{my ($a, $b) = @_;
return ($a,$b) if $b == 1;
my ($m, $n) = (1, ($a - $a % $b) / $b);
my $c = ($a*$m - $b*$n);
return($m, $n) if $c == 1;
my $c1 = ($b - $b % $c) / $c + 1;
my ($M, $N) = ($c1*$m, $c1*$n+1);
my $C = $a*$M - $b*$N;
return($M, $N) if $C == 1;
my ($mM, $nN);
($mM, $nN) = &$d($c, $C) if $c > $C;
($nN, $mM) = &$d($C, $c) if $C > $c;
($m*$mM-$M*$nN, $n*$mM-$N*$nN)
};
my ($a, $b) = @_;
my ($A, $B) = (0, 0);
my ($m, $n);
($A = 1, $a = -$a) if $a < 0;
($B = 1, $b = -$b) if $b < 0;
if ($a > $b)
{($m, $n) = &$d($a, $b); $n = -$n;
}
else
{($n, $m) = &$d($b, $a); $m = -$m;
}
$m = -$m if $A;
$n = -$n if $B;
{$a = -$a if $A;
$b = -$b if $B;
my $r = $m*$a+$b*$n;
$r == 1 or croak "dgcd(@_): m=$m*a=$a+b=$b*n=$n == $r != 1";
}
($m, $n);
}
=pod
Factorial of a number:
use Math::Modular::SquareRoot qw(factorial);
factorial(6);
# 720
=cut
sub factorial($)
{my ($n) = @_;
posInteger('factorial',0,$n);
return 1 if $n == 1;
my $p = 1; $p *= $_ for 2..$n;
$p
}
=pod
Check whether an integer is a prime:
use Math::Modular::SquareRoot qw(prime);
prime(9);
# 0
or possibly prime by trying to factor a specified number of times:
use Math::Modular::SquareRoot qw(prime);
prime(2**31-1, 7);
# 1
=cut
sub prime($;$)
{my ($p, $n) = @_;
posInteger('prime',$_,$_[$_]) for 0..$#_;
return 1 if $p == 1 or $p == 2 or$p == 3 or $p == 5 or $p == 7;
return 0 if $p < 11;
my $s = int(sqrt($p))+1;
return 0 if $p % $s == 0;
unless ($n)
{for(2..$s)
{return 0 unless $p % $_;
}
return 1;
}
my $N = 10**$n;
$N = $s if $s < $N;
my $D = $s - $N;
for(2..$N)
{return 0 if $p % $_ == 0 or gcd2($N+int(rand($D)), $p) > 1;
}
1
}
# Export details
require 5;
require Exporter;
use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS $VERSION);
@ISA = qw(Exporter);
@EXPORT_OK = qw(dgcd gcd gcd2 factorial msqrt1 msqrt2 prime);
%EXPORT_TAGS = (all=>[@EXPORT_OK], msqrt=>[qw(msqrt1 msqrt2)]);
$VERSION = '1.001'; # Monday 23 March 2009
=head1 Description
The routines
msqrt1 ($S,$a*$b)>
msqrt2 ($S,$a,$b)>
demonstrate the difference in time required to find the modular square
root of a number $S modulo $p when the factorization of $p is
respectively unknown and known. To see this difference, compare the time
required to process test: C<t/1.t> with line 11 uncommented with that of
C<test/2.t>. The time required to find the modular square root of $S
modulo $p grows exponentially with the length $l in characters of the
number $p. For well chosen:
$p=$a*$b
the difference in times required to recover the square root can be made
very large for small $l. The difference can be made so large that the
unfactored version takes more than a year's effort by all the computers
on planet Earth to solve, whilst the factored version can be solved in a
few seconds on one personal computer.
Ideally $a,$b and should be prime. This prevents alternate
factorizarizations of $p being present which would lower the difference
in time to find the modular square root.
=head2 msqrt1() msqrt2()
C<msqrt1($S,$a)> finds the square roots of $S modulo $a where $S,$a are
integers. There are normally either zero or two roots for a given pair
of numbers if gcd($S,$a) == 1 although in the case that $S==0 and $a is
prime, zero will have just one square root: zero. If gcd($S,$a) != 1
there will be more pairs of square roots. The square roots are returned
as a list. C<msqrt1($a,$S)> will croak if its arguments are not
integers, or if $a is zero.
C<msqrt2($a,$b,$S)> finds the square roots of $S modulo $a*$b where
$S,$a,$b are integers. There are normally either zero or four roots for
a given triple of numbers if gcd($S,$a) == 1 and gcd($S,$b) == 1. If
this is not so there will be more pairs of square roots. The square
roots are returned as a list. C<msqrt2($a,$b,$S)> will croak if its
arguments are not integers, or if $a or $b are zero.
=head2 gcd() gcd2()
C<gcd(@_)> finds the greatest common divisor of a list of numbers @_,
with error checks to validate the parameter list. C<gcd(@_)> will croak
unless all of its arguments are integers. At least one of these integers
must be non zero.
C<gcd2($a,$b)> finds the greatest common divisor of two integers $a,$b
as quickly as possible with no error checks to validate the parameter
list. C<gcd2(@_)> can always be used as a plug in replacement for
C<gcd($a,$b)> but not vice versa.
C<dgcd($a,$b)> solves the equation:
$a*$m+$b*$n == 1
for $m,$n given $a,$b where $a,$b,$m,$n are integers and
gcd($a,$b) == 1
The returned value is the list:
($m, $n)
A check is made that the solution does solve the above equation, a croak
is issued if this test fails. C<dgcd($a,$b)> will also croak unless
supplied with two non zero integers as parameters.
=head2 prime()
C<prime($p)> checks that $p is prime, returning 1 if it is, 0 if it is
not. C<prime($p)> will croak unless it is supplied with one integer
parameter greater than zero.
C<prime($p,$n)> checks that $p is prime by trying the first $N =
10**$n integers as divisors, while at the same time, finding the
greatest common divisor of $p and a number at chosen at random between
$N and the square root of $p $N times. If neither of these techniques
finds a divisor, it is possible that $p is prime and the
function retuerns 1, else 0.
=head2 factorial()
C<factorial($n)> finds the product of the integers from 1 to $n.
C<factorial($n)> will croak unless $n is a positive integer.
=head1 Export
C<dgcd() factorial() gcd() gcd2() msqrt1() msqrt2() prime()> are
exported upon request. Alternatively the tag B<:all> exports all these
functions, while the tag B<:sqrt> exports just C<msqrt1() msqrt2()>.
=head1 Installation
Standard Module::Build process for building and installing modules:
perl Build.PL
./Build
./Build test
./Build install
Or, if you're on a platform (like DOS or Windows) that doesn't require
the "./" notation, you can do this:
perl Build.PL
Build
Build test
Build install
=head1 Author
PhilipRBrenan@handybackup.com
http://www.handybackup.com
=head1 See Also
=over
=back
=head1 Copyright
Copyright (c) 2009 Philip R Brenan.
This module is free software. It may be used, redistributed and/or
modified under the same terms as Perl itself.
=cut