Хорошо, вот что я сделал.
Начало файла
{-# LANGUAGE NoMonomorphismRestriction #-}
import Test.QuickCheck
import Control.Applicative
Весь код, указанный в вопросе, кроме prop_rsa. Это было (очевидно) сильно изменено:
prop_rsa = forAll primePair $ \(p,q) ->
let n = p*q
in forAll (genUnder n) $ \x ->
let e = 3
t = (p-1)*(q-1)
d = mInverse e t
a `powModN` b = modExp a b n
in p /= q &&
rPrime e t ==>
x == (x `powModN` e) `powModN` d
Тип для primePair
- Gen (Int, Int)
, а для genUnder
- Int -> Gen Int
. Я не совсем уверен, в чем заключается магия forAll
, но я уверен, что это правильно. Я сделал некоторые специальные корректировки для 1) убедиться, что он потерпит неудачу, если я испорчу условия, и 2) убедиться, что вложенный forAll
изменяет значение x
в разных тестовых примерах.
Так вот, как написать эти генераторы. Как только я понял, что <generator>
в документации означало что-то типа Gen a
, это был торт.
genNonzero = (\x -> if x == 0 then 1 else x) `fmap` arbitrary
genUnder :: Int -> Gen Int
genUnder n = ((`mod` n) . abs) `fmap` genNonzero
genSmallPrime = ((\x -> (primes !! (x `mod` 2500))) . abs) `fmap` arbitrary
primePair :: Gen (Int, Int)
primePair = (,) <$> genSmallPrime <*> genSmallPrime
primePair
потребовалось несколько проб и ошибок, чтобы я понял все правильно; Я знал, что некоторые комбинаторы, подобные этому, должны работать, но я все еще не настолько знаком с fmap
, <$>
и <*>
, как хотелось бы. Я ограничил вычисление только выбором из первых 2500 простых чисел; в противном случае он, очевидно, хотел выбрать несколько действительно больших, которые потребовались бы вечно.
Случайная вещь на заметку
Благодаря лени, d = mInverse e t
не вычисляется, если не выполнены условия. Что хорошо, потому что не определено, когда условие rPrime e t
ложно. В английском языке целое число a
имеет только мультипликативный обратный знак (mod b), когда a
и b
относительно простые числа.