{- |
module: Main
description: Testing the natural number arithmetic library
license: MIT
maintainer: Joe Leslie-Hurd <joe@gilith.com>
stability: provisional
portability: portable
-}
module Main
( main )
where
import OpenTheory.Primitive.Natural
import OpenTheory.Natural
import OpenTheory.Natural.Divides
import qualified OpenTheory.Natural.Bits as Bits
import qualified Data.Maybe as Maybe
import qualified OpenTheory.Natural.Prime as Prime
import qualified Test.QuickCheck as QuickCheck
import qualified OpenTheory.Primitive.Random as Random
import qualified OpenTheory.Natural.Uniform as Uniform
import Arithmetic.Random
import Arithmetic.Prime
import qualified Arithmetic.ContinuedFraction as ContinuedFraction
import qualified Arithmetic.Prime.Factor as Factor
import qualified Arithmetic.Modular as Modular
import qualified Arithmetic.Montgomery as Montgomery
import qualified Arithmetic.Polynomial as Polynomial
import qualified Arithmetic.Quadratic as Quadratic
import qualified Arithmetic.Ring as Ring
import qualified Arithmetic.Williams as Williams
propPrimes :: Natural -> Bool
propPrimes k =
primes !! (fromIntegral k) ==
Prime.primes !! (fromIntegral k)
propRandomPrime :: Natural -> Random.Random -> Bool
propRandomPrime wp rnd =
Bits.width p == w &&
isPrime p r2
where
w = wp + 2
p = randomPrime w r1
(r1,r2) = Random.split rnd
propRandomRSA :: Natural -> Random.Random -> Bool
propRandomRSA wp rnd =
Bits.width n == w &&
not (isPrime n r2)
where
w = wp + 5
n = Factor.toNatural (Factor.randomRSA w r1)
(r1,r2) = Random.split rnd
propTrialDivision :: Natural -> Natural -> Bool
propTrialDivision k np =
Factor.toNatural f * m == n &&
all (\p -> not (divides p m)) ps
where
n = np + 1
ps = take (fromIntegral k) primes
(f,m) = Factor.trialDivision ps n
propModularNegate :: Natural -> Random.Random -> Bool
propModularNegate np rnd =
Modular.add n a b == 0 &&
b < n
where
n = np + 1
a = Uniform.random n rnd
b = Modular.negate n a
propModularSubtract :: Natural -> Natural -> Natural -> Bool
propModularSubtract np a b =
Modular.subtract n a b ==
Ring.subtract r (Ring.fromNatural r a) (Ring.fromNatural r b)
where
n = np + 1
r = Modular.ring n
propModularExp2 :: Natural -> Natural -> Natural -> Bool
propModularExp2 np x k =
Modular.exp2 n x k == Ring.exp2 r (Ring.fromNatural r x) k
where
n = np + 1
r = Modular.ring n
propModularDivide :: Natural -> Natural -> Natural -> Bool
propModularDivide np a b =
case Modular.divide n a b of
Nothing -> not (Modular.divides n a b)
Just c -> Modular.multiply n b c == Modular.normalize n a && c < n
where
n = np + 1
propModularDivides :: Natural -> Natural -> Natural -> Bool
propModularDivides np a b =
Modular.divides n a b ==
Ring.divides r (Ring.fromNatural r a) (Ring.fromNatural r b)
where
n = np + 1
r = Modular.ring n
propModularInvert :: Natural -> Natural -> Bool
propModularInvert np a =
Modular.invert n a == Ring.invert r (Ring.fromNatural r a)
where
n = np + 1
r = Modular.ring n
propFermat :: Natural -> Random.Random -> Bool
propFermat pp rnd =
Modular.exp p a p == a
where
p = nextPrime (pp + 3) r1
a = Uniform.random p r2
(r1,r2) = Random.split rnd
propMontgomeryInvariant :: Natural -> Natural -> Bool
propMontgomeryInvariant np b =
naturalOdd n &&
1 < n &&
n < w2 &&
s * w2 == k * n + 1 &&
s < n &&
k < w2 &&
r == w2 `mod` n &&
r2 == (r * r) `mod` n &&
z `mod` n == 0 &&
w2 <= z &&
z < w2 + n
where
Montgomery.Parameters
{Montgomery.nParameters = n,
Montgomery.wParameters = w,
Montgomery.sParameters = s,
Montgomery.kParameters = k,
Montgomery.rParameters = r,
Montgomery.r2Parameters = r2,
Montgomery.zParameters = z} =
Montgomery.customParameters (2 * np + 3) (Bits.width n + b)
w2 = shiftLeft 1 w
propMontgomeryNormalize :: Natural -> Natural -> Bool
propMontgomeryNormalize np a =
b `mod` n == a `mod` n &&
b < w2
where
n = 2 * np + 3
p = Montgomery.standardParameters n
b = Montgomery.nMontgomery (Montgomery.normalize p a)
w = Montgomery.wParameters p
w2 = shiftLeft 1 w
propMontgomeryReduce :: Natural -> Natural -> Bool
propMontgomeryReduce np a =
b `mod` n == (a * s) `mod` n &&
b < w2 + n
where
n = 2 * np + 3
p = Montgomery.standardParameters n
b = Montgomery.reduce p a
w = Montgomery.wParameters p
s = Montgomery.sParameters p
w2 = shiftLeft 1 w
propMontgomeryReduceSmall :: Natural -> Natural -> Bool
propMontgomeryReduceSmall np ap =
b `mod` n == (a * s) `mod` n &&
b <= n
where
n = 2 * np + 3
p = Montgomery.standardParameters n
a = ap `mod` w2
b = Montgomery.reduce p a
w = Montgomery.wParameters p
s = Montgomery.sParameters p
w2 = shiftLeft 1 w
propMontgomeryToNatural :: Natural -> Natural -> Bool
propMontgomeryToNatural np a =
b == (a * s) `mod` n
where
n = 2 * np + 3
p = Montgomery.standardParameters n
b = Montgomery.toNatural (Montgomery.normalize p a)
s = Montgomery.sParameters p
propMontgomeryFromNatural :: Natural -> Natural -> Bool
propMontgomeryFromNatural np a =
b == a `mod` n
where
n = 2 * np + 3
p = Montgomery.standardParameters n
b = Montgomery.toNatural (Montgomery.fromNatural p a)
propMontgomeryZero :: Natural -> Bool
propMontgomeryZero np =
Montgomery.toNatural (Montgomery.zero p) == 0
where
n = 2 * np + 3
p = Montgomery.standardParameters n
propMontgomeryOne :: Natural -> Bool
propMontgomeryOne np =
Montgomery.toNatural (Montgomery.one p) == 1
where
n = 2 * np + 3
p = Montgomery.standardParameters n
propMontgomeryTwo :: Natural -> Bool
propMontgomeryTwo np =
Montgomery.toNatural (Montgomery.two p) == 2
where
n = 2 * np + 3
p = Montgomery.standardParameters n
propMontgomeryAdd :: Natural -> Natural -> Natural -> Bool
propMontgomeryAdd np ap bp =
Montgomery.toNatural c ==
Modular.add n (Montgomery.toNatural a) (Montgomery.toNatural b) &&
Montgomery.nMontgomery c < w2
where
n = 2 * np + 3
p = Montgomery.standardParameters n
a = Montgomery.normalize p ap
b = Montgomery.normalize p bp
c = Montgomery.add a b
w = Montgomery.wParameters p
w2 = shiftLeft 1 w
propMontgomeryNegate :: Natural -> Natural -> Bool
propMontgomeryNegate np ap =
Montgomery.toNatural b == Modular.negate n (Montgomery.toNatural a) &&
Montgomery.nMontgomery b < w2
where
n = 2 * np + 3
p = Montgomery.standardParameters n
a = Montgomery.normalize p ap
b = Montgomery.negate a
w = Montgomery.wParameters p
w2 = shiftLeft 1 w
propMontgomeryMultiply :: Natural -> Natural -> Natural -> Bool
propMontgomeryMultiply np ap bp =
Montgomery.toNatural c ==
Modular.multiply n (Montgomery.toNatural a) (Montgomery.toNatural b) &&
Montgomery.nMontgomery c < w2
where
n = 2 * np + 3
p = Montgomery.standardParameters n
a = Montgomery.normalize p ap
b = Montgomery.normalize p bp
c = Montgomery.multiply a b
w = Montgomery.wParameters p
w2 = shiftLeft 1 w
propMontgomeryModexp :: Natural -> Natural -> Natural -> Bool
propMontgomeryModexp np x k =
Montgomery.modexp n x k == Modular.exp n x k
where
n = 2 * np + 3
propMontgomeryModexp2 :: Natural -> Natural -> Natural -> Bool
propMontgomeryModexp2 np x k =
Montgomery.modexp2 n x k == Modular.exp2 n x k
where
n = 2 * np + 3
propRootFloor :: Natural -> Bool
propRootFloor n =
sq s <= n && n < sq (s + 1)
where
s = Quadratic.rootFloor n
sq i = i * i
propRootCeiling :: Natural -> Bool
propRootCeiling n =
(s == 0 || sq (s - 1) < n) && n <= sq s
where
s = Quadratic.rootCeiling n
sq i = i * i
propRootContinuedFraction :: Natural -> Bool
propRootContinuedFraction n =
cf == spec
where
cf = ContinuedFraction.toDouble (Quadratic.rootContinuedFraction n)
spec = sqrt (fromIntegral n)
propJacobiSymbol :: Natural -> Natural -> Random.Random -> Bool
propJacobiSymbol np m rnd =
case Quadratic.jacobiSymbol n m of
Quadratic.Zero -> not coprime
Quadratic.Residue -> coprime && (mr || not (isPrime n rnd))
Quadratic.NonResidue -> coprime && not mr
where
coprime = gcd m n == 1
n = 2 * np + 1
mn = Modular.normalize n m
mr = any (\k -> Modular.square n k == mn) [1..np]
propRootModuloPrime3Mod4 :: Natural -> Random.Random -> Bool
propRootModuloPrime3Mod4 pp rnd =
Modular.square p r == a
where
p = nextPrime3Mod4 pp r1
a = randomFilter (Quadratic.isResidue p) (Uniform.random p) r2
r = Quadratic.rootModuloPrime3Mod4 p a
(r1,r2) = Random.split rnd
propRootModuloPrime5Mod8 :: Natural -> Random.Random -> Bool
propRootModuloPrime5Mod8 pp rnd =
Modular.square p r == a
where
p = nextPrime5Mod8 pp r1
a = randomFilter (Quadratic.isResidue p) (Uniform.random p) r2
r = Quadratic.rootModuloPrime5Mod8 p a
(r1,r2) = Random.split rnd
propRootModuloPrime :: Natural -> Random.Random -> Bool
propRootModuloPrime pp rnd =
Modular.square p r == a
where
p = nextPrime pp r1
a = randomFilter (Quadratic.isResidue p) (Uniform.random p) r2
r = Quadratic.rootModuloPrime p a
(r1,r2) = Random.split rnd
propWilliamsNth :: Natural -> Natural -> Natural -> Bool
propWilliamsNth np p k =
Williams.sequence one two sub mult p !! (fromIntegral k) ==
Williams.nth two sub mult p k
where
n = np + 1
one = 1
two = 2
sub = Modular.subtract n
mult = Modular.multiply n
propWilliamsNthProduct :: Natural -> Natural -> Natural -> Natural -> Bool
propWilliamsNthProduct np pp i j =
Williams.nth two sub mult p (i * j) ==
Williams.nth two sub mult (Williams.nth two sub mult p i) j
where
n = np + 1
p = pp + 1
two = Modular.normalize n 2
sub = Modular.subtract n
mult = Modular.multiply n
propWilliamsNthExp :: Natural -> Natural -> Natural -> Natural -> Bool
propWilliamsNthExp np p m k =
Williams.nthExp two sub mult p m k ==
Williams.nth two sub mult p (m ^ k)
where
n = np + 1
two = Modular.normalize n 2
sub = Modular.subtract n
mult = Modular.multiply n
propWilliamsNthEqTwo :: Natural -> Natural -> Natural -> Random.Random -> Bool
propWilliamsNthEqTwo pp a mp rnd =
Williams.nth two sub mult a m == two
where
p = nextPrime (pp + 3) rnd
d = sub (mult a a) 4
t = case Quadratic.jacobiSymbol p d of
Quadratic.Zero -> 2
Quadratic.Residue -> p - 1
Quadratic.NonResidue -> p + 1
m = mp * t
two = Modular.normalize p 2
sub = Modular.subtract p
mult = Modular.multiply p
propWilliamsFactor :: Natural -> Natural -> Natural -> Random.Random -> Bool
propWilliamsFactor np x k rnd =
case Williams.factor x (Just k) n rnd of
Nothing -> True
Just p -> 1 < p && p < n && divides p n
where
n = 2 * np + 5
propPolynomialConstantDegree :: Natural -> Natural -> Bool
propPolynomialConstantDegree np cp =
Polynomial.degree (Polynomial.constant r c) == if c == 0 then 0 else 1
where
n = np + 1
r = Modular.ring n
c = Ring.fromNatural r cp
propPolynomialFromNaturalDegree :: Natural -> Natural -> Bool
propPolynomialFromNaturalDegree np c =
Polynomial.degree (Polynomial.fromNatural r c) ==
if divides n c then 0 else 1
where
n = np + 1
r = Modular.ring n
propPolynomialAddDegree :: Natural -> [Natural] -> [Natural] -> Bool
propPolynomialAddDegree np ps qs =
if d_p == d_q then d_pq <= d_p
else d_pq == max d_p d_q
where
n = np + 1
r = Modular.ring n
p = Polynomial.fromCoefficients r (map (Ring.fromNatural r) ps)
q = Polynomial.fromCoefficients r (map (Ring.fromNatural r) qs)
d_p = Polynomial.degree p
d_q = Polynomial.degree q
pq = Polynomial.add p q
d_pq = Polynomial.degree pq
propPolynomialNegateDegree :: Natural -> [Natural] -> Bool
propPolynomialNegateDegree np ps =
Polynomial.degree (Polynomial.negate p) == Polynomial.degree p
where
n = np + 1
r = Modular.ring n
p = Polynomial.fromCoefficients r (map (Ring.fromNatural r) ps)
propPolynomialMultiplyDegree :: Natural -> [Natural] -> [Natural] -> Bool
propPolynomialMultiplyDegree np ps qs =
if d_p == 0 || d_q == 0 then d_pq == 0
else d_pq + 1 <= d_p + d_q
where
n = np + 1
r = Modular.ring n
p = Polynomial.fromCoefficients r (map (Ring.fromNatural r) ps)
q = Polynomial.fromCoefficients r (map (Ring.fromNatural r) qs)
d_p = Polynomial.degree p
d_q = Polynomial.degree q
pq = Polynomial.multiply p q
d_pq = Polynomial.degree pq
propPolynomialConstantEvaluate :: Natural -> Natural -> Natural -> Bool
propPolynomialConstantEvaluate np cp xp =
Polynomial.evaluate (Polynomial.constant r c) x == c
where
n = np + 1
r = Modular.ring n
c = Ring.fromNatural r cp
x = Ring.fromNatural r xp
propPolynomialFromNaturalEvaluate :: Natural -> Natural -> Natural -> Bool
propPolynomialFromNaturalEvaluate np c xp =
Polynomial.evaluate (Polynomial.fromNatural r c) x ==
Ring.fromNatural r c
where
n = np + 1
r = Modular.ring n
x = Ring.fromNatural r xp
propPolynomialAddEvaluate ::
Natural -> [Natural] -> [Natural] -> Natural -> Bool
propPolynomialAddEvaluate np ps qs xp =
Polynomial.evaluate (Polynomial.add p q) x ==
Ring.add r (Polynomial.evaluate p x) (Polynomial.evaluate q x)
where
n = np + 1
r = Modular.ring n
p = Polynomial.fromCoefficients r (map (Ring.fromNatural r) ps)
q = Polynomial.fromCoefficients r (map (Ring.fromNatural r) qs)
x = Ring.fromNatural r xp
propPolynomialNegateEvaluate :: Natural -> [Natural] -> Natural -> Bool
propPolynomialNegateEvaluate np ps xp =
Polynomial.evaluate (Polynomial.negate p) x ==
Ring.negate r (Polynomial.evaluate p x)
where
n = np + 1
r = Modular.ring n
p = Polynomial.fromCoefficients r (map (Ring.fromNatural r) ps)
x = Ring.fromNatural r xp
propPolynomialMultiplyEvaluate ::
Natural -> [Natural] -> [Natural] -> Natural -> Bool
propPolynomialMultiplyEvaluate np ps qs xp =
Polynomial.evaluate (Polynomial.multiply p q) x ==
Ring.multiply r (Polynomial.evaluate p x) (Polynomial.evaluate q x)
where
n = np + 1
r = Modular.ring n
p = Polynomial.fromCoefficients r (map (Ring.fromNatural r) ps)
q = Polynomial.fromCoefficients r (map (Ring.fromNatural r) qs)
x = Ring.fromNatural r xp
propPolynomialQuotientRemainder :: Natural -> [Natural] -> [Natural] -> Bool
propPolynomialQuotientRemainder np ps qs =
case Polynomial.quotientRemainder p q of
Nothing -> True
Just (a,b) -> Polynomial.coefficients p ==
Polynomial.coefficients (Polynomial.add (Polynomial.multiply a q) b)
where
n = np + 1
r = Modular.ring n
p = Polynomial.fromCoefficients r (map (Ring.fromNatural r) ps)
q = Polynomial.fromCoefficients r (map (Ring.fromNatural r) qs)
propPolynomialQuotientRemainderMonic ::
Natural -> [Natural] -> [Natural] -> Bool
propPolynomialQuotientRemainderMonic np ps qs =
Maybe.isJust (Polynomial.quotientRemainder p q)
where
n = np + 2
r = Modular.ring n
p = Polynomial.fromCoefficients r (map (Ring.fromNatural r) ps)
q = Polynomial.fromCoefficients r (map (Ring.fromNatural r) (qs ++ [1]))
{-
np = (0 :: Natural)
ps = ([] :: [Natural])
qs = ([] :: [Natural])
n = np + 2
r = Modular.ring n
p = Polynomial.fromCoefficients r (map (Ring.fromNatural r) ps)
q = Polynomial.fromCoefficients r (map (Ring.fromNatural r) (qs ++ [1]))
-}
check :: QuickCheck.Testable prop => String -> prop -> IO ()
check desc prop =
do putStr (desc ++ "\n ")
res <- QuickCheck.quickCheckWithResult args prop
case res of
QuickCheck.Failure {} -> error "Proposition failed"
_ -> return ()
where
args = QuickCheck.stdArgs {QuickCheck.maxSuccess = 1000}
main :: IO ()
main =
do check "Sieve of Eratosphenes" propPrimes
check "Generating random primes" propRandomPrime
check "Generating random RSA moduli" propRandomRSA
check "Trial division" propTrialDivision
check "Modular negate" propModularNegate
check "Modular subtract" propModularSubtract
check "Modular exp2" propModularExp2
check "Modular divide" propModularDivide
check "Modular divides" propModularDivides
check "Modular invert" propModularInvert
check "Fermat's little theorem" propFermat
check "Montgomery invariant" propMontgomeryInvariant
check "Montgomery normalize" propMontgomeryNormalize
check "Montgomery reduce" propMontgomeryReduce
check "Montgomery reduce small" propMontgomeryReduceSmall
check "Montgomery toNatural" propMontgomeryToNatural
check "Montgomery fromNatural" propMontgomeryFromNatural
check "Montgomery zero" propMontgomeryZero
check "Montgomery one" propMontgomeryOne
check "Montgomery two" propMontgomeryTwo
check "Montgomery add" propMontgomeryAdd
check "Montgomery negate" propMontgomeryNegate
check "Montgomery multiply" propMontgomeryMultiply
check "Montgomery modexp" propMontgomeryModexp
check "Montgomery modexp2" propMontgomeryModexp2
check "Floor square root" propRootFloor
check "Ceiling square root" propRootCeiling
check "Continued fraction square root" propRootContinuedFraction
check "Jacobi symbol" propJacobiSymbol
check "Square root modulo prime congruent to 3 mod 4"
propRootModuloPrime3Mod4
check "Square root modulo prime congruent to 5 mod 8"
propRootModuloPrime5Mod8
check "Square root modulo prime" propRootModuloPrime
check "Williams sequence" propWilliamsNth
check "Williams sequence product" propWilliamsNthProduct
check "Williams sequence exponential" propWilliamsNthExp
check "Williams sequence equals two" propWilliamsNthEqTwo
check "Williams factorization works" propWilliamsFactor
check "Polynomial constant degree" propPolynomialConstantDegree
check "Polynomial fromNatural degree" propPolynomialFromNaturalDegree
check "Polynomial add degree" propPolynomialAddDegree
check "Polynomial negate degree" propPolynomialNegateDegree
check "Polynomial multiply degree" propPolynomialMultiplyDegree
check "Polynomial constant evaluate" propPolynomialConstantEvaluate
check "Polynomial fromNatural evaluate" propPolynomialFromNaturalEvaluate
check "Polynomial add evaluate" propPolynomialAddEvaluate
check "Polynomial negate evaluate" propPolynomialNegateEvaluate
check "Polynomial multiply evaluate" propPolynomialMultiplyEvaluate
check "Polynomial quotient remainder" propPolynomialQuotientRemainder
check "Polynomial quotient remainder monic"
propPolynomialQuotientRemainderMonic
return ()