Shift the gold testing infrastructure into its own module, and add the Haskell program I used to generate the tests.

This commit is contained in:
2018-04-13 10:56:13 -04:00
parent 675f8adc7e
commit 330dabe017
30 changed files with 195458 additions and 78202 deletions

4
.gitignore vendored
View File

@@ -11,3 +11,7 @@ Cargo.lock
# And these are just annoying
.DS_Store
# And some Haskell stuff, because I can't shake it!
tests/math/cabal.sandbox.config
tests/math/.cabal-sandbox

205
src/cryptonum/gold_tests.rs Normal file
View File

@@ -0,0 +1,205 @@
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::str::Lines;
use cryptonum::unsigned::UCN;
use cryptonum::signed::SCN;
fn next_value_set(line: &str) -> (String, SCN)
{
assert!(line.is_ascii());
assert_eq!(": ", &line[1..3]);
let key = String::from(&line[0..1]);
let val = SCN::from_str(&line[3..]);
(key, val)
}
fn next_test_case(contents: &mut Lines, lines: usize) ->
Option<HashMap<String,SCN>>
{
let mut res = HashMap::new();
let mut count = 0;
while count < lines {
let line = contents.next()?;
let (key, val) = next_value_set(line);
res.insert(key, val);
count += 1;
}
Some(res)
}
fn make_unsigned(m: HashMap<String,SCN>) -> HashMap<String,UCN>
{
let mut res: HashMap<String,UCN> = HashMap::new();
for (key, sval) in m.iter() {
assert!(!sval.is_negative());
res.insert(key.clone(), sval.clone().into());
}
res
}
fn run_test<F>(fname: &'static str, i: usize, f: F)
where F: Fn(HashMap<String,SCN>)
{
let mut file = File::open(fname).unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
let mut iter = contents.lines();
while let Some(scase) = next_test_case(&mut iter, i) {
f(scase);
}
}
#[test]
fn unsigned_sum_test()
{
run_test("tests/math/unsigned_add.tests", 3, |scase| {
let case = make_unsigned(scase);
let x = case.get("x").unwrap();
let y = case.get("y").unwrap();
let z = case.get("z").unwrap();
assert_eq!(x + y, *z);
});
}
#[test]
fn signed_sum_test()
{
run_test("tests/math/signed_add.tests", 3, |case| {
let x = case.get("x").unwrap();
let y = case.get("y").unwrap();
let z = case.get("z").unwrap();
assert_eq!(x + y, *z);
});
}
#[test]
fn unsigned_sub_test()
{
run_test("tests/math/unsigned_sub.tests", 3, |scase| {
let case = make_unsigned(scase);
let x = case.get("x").unwrap();
let y = case.get("y").unwrap();
let z = case.get("z").unwrap();
assert_eq!(x - y, *z);
});
}
#[test]
fn signed_sub_test()
{
run_test("tests/math/signed_sub.tests", 3, |case| {
let x = case.get("x").unwrap();
let y = case.get("y").unwrap();
let z = case.get("z").unwrap();
assert_eq!(x - y, *z);
});
}
#[test]
fn unsigned_mul_test()
{
run_test("tests/math/unsigned_mul.tests", 3, |scase| {
let case = make_unsigned(scase);
let x = case.get("x").unwrap();
let y = case.get("y").unwrap();
let z = case.get("z").unwrap();
assert_eq!(x * y, *z);
});
}
#[test]
fn signed_mul_test()
{
run_test("tests/math/signed_mul.tests", 3, |case| {
let x = case.get("x").unwrap();
let y = case.get("y").unwrap();
let z = case.get("z").unwrap();
assert_eq!(x * y, *z);
});
}
#[test]
fn unsigned_div_test()
{
run_test("tests/math/unsigned_div.tests", 3, |scase| {
let case = make_unsigned(scase);
let x = case.get("x").unwrap();
let y = case.get("y").unwrap();
let z = case.get("z").unwrap();
assert_eq!(x / y, *z);
});
}
#[test]
fn signed_div_test()
{
run_test("tests/math/signed_div.tests", 3, |case| {
let x = case.get("x").unwrap();
let y = case.get("y").unwrap();
let z = case.get("z").unwrap();
assert_eq!(x / y, *z);
});
}
#[test]
fn unsigned_mod_test()
{
run_test("tests/math/unsigned_mod.tests", 3, |scase| {
let case = make_unsigned(scase);
let x = case.get("x").unwrap();
let y = case.get("y").unwrap();
let z = case.get("z").unwrap();
assert_eq!(x % y, *z);
});
}
#[test]
fn signed_mod_test()
{
run_test("tests/math/signed_mod.tests", 3, |case| {
let x = case.get("x").unwrap();
let y = case.get("y").unwrap();
let z = case.get("z").unwrap();
assert_eq!(x % y, *z);
});
}
#[test]
fn modular_exponentiation_test()
{
run_test("tests/math/modexp.tests", 4, |scase| {
let case = make_unsigned(scase);
let a = case.get("a").unwrap();
let b = case.get("b").unwrap();
let m = case.get("m").unwrap();
let z = case.get("z").unwrap();
assert_eq!(a.modexp(&b, &m), *z);
});
}
#[test]
fn barrett_reduction_test()
{
run_test("tests/math/barrett.tests", 5, |scase| {
let case = make_unsigned(scase);
let kbig = case.get("k").unwrap();
let m = case.get("m").unwrap();
let r = case.get("r").unwrap();
let u = case.get("u").unwrap();
let v = case.get("v").unwrap();
let k = usize::from(kbig);
let barrett = m.barrett_u();
let result = v.reduce(&barrett);
assert_eq!(barrett.k, k);
assert_eq!(&barrett.u, u);
assert_eq!(&barrett.m, m);
assert_eq!(&result, r);
});
}

View File

@@ -4,6 +4,8 @@ mod conversions;
mod complete_arith;
mod signed;
mod unsigned;
#[cfg(test)]
mod gold_tests;
pub use self::signed::SCN;
pub use self::unsigned::UCN;

View File

@@ -232,58 +232,8 @@ derive_arithmetic_operators!(SCN, Rem, rem, RemAssign, rem_assign);
#[cfg(test)]
mod test {
use quickcheck::{Arbitrary,Gen};
use std::fs::File;
use std::io::Read;
use super::*;
fn gold_test<F>(name: &str, f: F)
where
F: Fn(SCN,SCN) -> SCN
{
let mut file = File::open(name).unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
let mut iter = contents.lines();
while let Some(xstr) = iter.next() {
let ystr = iter.next().unwrap();
let zstr = iter.next().unwrap();
assert!(xstr.starts_with("x: "));
assert!(ystr.starts_with("y: "));
assert!(zstr.starts_with("z: "));
let x = SCN::from_str(&xstr[3..]);
let y = SCN::from_str(&ystr[3..]);
let z = SCN::from_str(&zstr[3..]);
assert_eq!(f(x,y), z);
}
}
#[test]
fn add_tests() {
gold_test("tests/add_tests_signed.txt", |x,y| x + y);
}
#[test]
fn sub_tests() {
gold_test("tests/sub_tests_signed.txt", |x,y| x - y);
}
#[test]
fn mul_tests() {
gold_test("tests/mul_tests_signed.txt", |x,y| x * y);
}
#[test]
fn div_tests() {
gold_test("tests/div_tests_signed.txt", |x,y| x / y);
}
#[test]
fn mod_tests() {
gold_test("tests/mod_tests_signed.txt", |x,y| x % y);
}
impl Arbitrary for SCN {
fn arbitrary<G: Gen>(g: &mut G) -> SCN {
let neg = (g.next_u32() & 1) == 1;

View File

@@ -13,9 +13,9 @@ pub struct UCN {
}
pub struct BarrettUCN {
u: UCN,
k: usize,
m: UCN
pub(crate) u: UCN,
pub(crate) k: usize,
pub(crate) m: UCN
}
static SMALL_PRIMES: [u32; 310] = [
@@ -968,8 +968,6 @@ derive_arithmetic_operators!(UCN, Rem, rem, RemAssign, rem_assign);
#[cfg(test)]
mod test {
use quickcheck::{Arbitrary,Gen};
use std::fs::File;
use std::io::Read;
use super::*;
#[test]
@@ -1235,111 +1233,6 @@ mod test {
}
}
fn gold_test<F>(name: &str, f: F)
where
F: Fn(UCN,UCN) -> UCN
{
let mut file = File::open(name).unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
let mut iter = contents.lines();
while let Some(xstr) = iter.next() {
let ystr = iter.next().unwrap();
let zstr = iter.next().unwrap();
assert!(xstr.starts_with("x: "));
assert!(ystr.starts_with("y: "));
assert!(zstr.starts_with("z: "));
let x = UCN::from_str(&xstr[3..]);
let y = UCN::from_str(&ystr[3..]);
let z = UCN::from_str(&zstr[3..]);
assert_eq!(f(x,y), z);
}
}
#[test]
fn add_tests() {
gold_test("tests/add_tests.txt", |x,y| x + y);
}
#[test]
fn sub_tests() {
gold_test("tests/sub_tests.txt", |x,y| x - y);
}
#[test]
fn mul_tests() {
gold_test("tests/mul_tests.txt", |x,y| x * y);
}
#[test]
fn div_tests() {
gold_test("tests/div_tests.txt", |x,y| x / y);
}
#[test]
fn mod_tests() {
gold_test("tests/mod_tests.txt", |x,y| x % y);
}
#[test]
fn modexp_tests() {
let mut file = File::open("tests/modpow_tests.txt").unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
let mut iter = contents.lines();
while let Some(xstr) = iter.next() {
let ystr = iter.next().unwrap();
let zstr = iter.next().unwrap();
let rstr = iter.next().unwrap();
assert!(xstr.starts_with("x: "));
assert!(ystr.starts_with("y: "));
assert!(zstr.starts_with("z: "));
assert!(rstr.starts_with("r: "));
let x = UCN::from_str(&xstr[3..]);
let y = UCN::from_str(&ystr[3..]);
let z = UCN::from_str(&zstr[3..]);
let r = UCN::from_str(&rstr[3..]);
assert_eq!(x.modexp(&y, &z), r);
}
}
#[test]
fn barrett_tests() {
let mut file = File::open("tests/barrett_tests.txt").unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
let mut iter = contents.lines();
while let Some(mstr) = iter.next() {
let kstr = iter.next().unwrap();
let ustr = iter.next().unwrap();
let vstr = iter.next().unwrap();
let rstr = iter.next().unwrap();
assert!(mstr.starts_with("m: "));
assert!(kstr.starts_with("k: "));
assert!(ustr.starts_with("u: "));
assert!(vstr.starts_with("v: "));
assert!(rstr.starts_with("r: "));
let m = UCN::from_str(&mstr[3..]);
let k = usize::from_str_radix(&kstr[3..], 10).unwrap();
let u = UCN::from_str(&ustr[3..]);
let v = UCN::from_str(&vstr[3..]);
let r = UCN::from_str(&rstr[3..]);
let b = m.barrett_u();
assert_eq!(b.k, k);
println!("");
println!("b.u: {:X}", b.u);
println!("u: {:X}", u);
assert_eq!(b.u, u);
assert_eq!(v.reduce(&b), r);
}
}
quickcheck! {
fn and_over_or_distribution(a: UCN, b: UCN, c: UCN) -> bool {
(&a & (&b | &c)) == ((&a & &b) | (&a & &c))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,244 @@
{-# LANGUAGE ScopedTypeVariables #-}
import Control.Monad
import Data.Bits
import Data.List
import qualified Data.Map.Strict as Map
import GHC.Integer.GMP.Internals
import Numeric
import System.IO
import System.Random
import Debug.Trace
type Generator a = StdGen -> a -> (Maybe [(String, Integer)], a, StdGen)
iterations :: Int
iterations = 5000
maxSize :: Int
maxSize = 8
randomVal :: (Integer -> Bool) -> StdGen -> (Integer, StdGen)
randomVal filter g =
let (mySize, g') = randomR (1, maxSize) g
(possible, g'') = go g' mySize
in if filter possible
then (possible, g'')
else randomVal filter g''
where
go rng 0 = (0, rng)
go rng i =
let (other, rng') = go rng (i - 1)
(self, rng'') = random rng'
in ((other `shiftL` 64) + self, rng'')
buildBasicGenerator :: (Integer -> Bool) ->
(Integer -> Integer -> Maybe Integer) ->
Generator ()
buildBasicGenerator filter f g () =
let (x, g') = randomVal filter g
(y, g'') = randomVal filter g'
in case f x y of
Nothing ->
(Nothing, (), g'')
Just z ->
(Just [("x", x), ("y", y), ("z", z)], (), g'')
buildBasicLimitingGenerator :: (Integer -> Bool) ->
(Integer -> Integer -> Maybe Integer) ->
Generator (Map.Map Integer Int)
buildBasicLimitingGenerator filter f g m =
let (x, g') = randomVal filter g
(y, g'') = randomVal filter g'
in case f x y of
Nothing -> (Nothing, m, g'')
Just z ->
case Map.lookup z m of
Nothing ->
(Just [("x",x),("y",y),("z",z)], Map.insert z 1 m, g'')
Just c | c >= 100 ->
(Nothing, m, g'')
Just c ->
(Just [("x",x),("y",y),("z",z)], Map.insert z (c + 1) m, g'')
buildBasicAccGenerator :: (Integer -> Bool) ->
(Integer -> Integer -> a -> Maybe (Integer, a)) ->
Generator a
buildBasicAccGenerator filter f g acc =
let (x, g') = randomVal filter g
(y, g'') = randomVal filter g'
in case f x y acc of
Nothing ->
(Nothing, acc, g'')
Just (z, acc') ->
(Just [("x", x), ("y", y), ("z", z)], acc', g'')
runGenerator :: forall a. StdGen -> String -> a -> Generator a -> IO StdGen
runGenerator g filename initVal generator =
withFile (filename ++ ".tests") WriteMode $ \ hndl ->
do putStrLn ("Generating " ++ filename ++ ".tests")
go hndl g initVal iterations
where
go :: Handle -> StdGen -> a -> Int -> IO StdGen
go _ g _ 0 = return g
go hndl g acc iterations =
case generator g acc of
(Nothing, acc', g') ->
go hndl g' acc' iterations
(Just res, acc', g') ->
do let sorted = sort res
forM_ sorted $ \ (key, val) ->
do let neg = if val < 0 then "-" else ""
val' = abs val
hPutStrLn hndl (key ++ ": " ++ neg ++ showHex val' "")
go hndl g' acc' (iterations - 1)
main :: IO ()
main =
do g0 <- newStdGen
g1 <- runGenerator g0 "unsigned_add" () $
buildBasicGenerator (>= 0) $ \ a b -> Just (a + b)
g2 <- runGenerator g1 "signed_add" () $
buildBasicGenerator (const True) $ \ a b -> Just (a + b)
g3 <- runGenerator g2 "unsigned_sub" () $
buildBasicGenerator (>= 0) $ \ a b ->
if a >= b then Just (a - b) else Nothing
g4 <- runGenerator g3 "signed_sub" () $
buildBasicGenerator (const True) $ \ a b -> Just (a - b)
g5 <- runGenerator g4 "unsigned_mul" () $
buildBasicGenerator (>= 0) $ \ a b -> Just (a * b)
g6 <- runGenerator g5 "signed_mul" () $
buildBasicGenerator (const True) $ \ a b -> Just (a * b)
g7 <- runGenerator g6 "unsigned_div" Map.empty $
buildBasicLimitingGenerator (>= 0) $ \ a b ->
if b == 0 then Nothing else Just (a `div` b)
g8 <- runGenerator g7 "signed_div" Map.empty $
buildBasicLimitingGenerator (const True) $ \ a b ->
if b == 0 then Nothing else Just (a `div` b)
g7 <- runGenerator g6 "unsigned_mod" 0 $
buildBasicAccGenerator (>= 0) $ \ a b i ->
case a `mod` b of
_ | b == 0 -> Nothing
x | (a == x) && (i == 100) -> Nothing
x | a == x -> Just (x, i + 1)
x -> Just (x, i)
g8 <- runGenerator g7 "signed_mod" 0 $
buildBasicAccGenerator (const True) $ \ a b i ->
case a `mod` b of
_ | b == 0 -> Nothing
x | (a == x) && (i == 100) -> Nothing
x | a == x -> Just (x, i + 1)
x -> Just (x, i)
g9 <- runGenerator g8 "modexp" () $ \ g () ->
let (a, g') = randomVal (>= 0) g
(b, g'') = randomVal (>= 0) g'
(m, g''') = randomVal (>= 0) g''
z = powModInteger a b m
res = [("a",a),("b",b),("m",m),("z",z)]
in if m == 0
then (Nothing, (), g''')
else (Just res, (), g''')
_ <- runGenerator g9 "barrett" () $ \ g () ->
let (m, g') = randomVal (>= 0) g
(v, g'') = randomVal (>= 0) g'
barrett = barrett_u m
vk = computeK v
in if vk > (2 * (bk barrett))
then (Nothing, (), g'')
else let me = reduce v barrett
standard = v `mod` m
res = [("m", m), ("v", v), ("r", me),
("u", bu barrett), ("k", fromIntegral (bk barrett))]
in if me /= standard
then error "Barrett broken"
else (Just res, (), g'')
return ()
-- Implement Barrett reduction using incredibly simplistic implementations, to
-- be sure we got it right.
--
b :: Integer
b = 2 ^ 64
computeK :: Integer -> Int
computeK v = go 0 1
where
go k acc
| v < acc = k
| otherwise = go (k + 1) (acc * b)
data Barrett = Barrett { bm :: Integer, bu :: Integer, bk :: Int }
deriving (Show)
barrett_u :: Integer -> Barrett
barrett_u x = Barrett {
bm = x,
bu = (b ^ (2 * k)) `div` x,
bk = k
}
where k = computeK x
reduce :: Integer -> Barrett -> Integer
reduce x barrett = result
where
k = bk barrett
u = bu barrett
m = bm barrett
--
q1 = x `div` (b ^ (k - 1))
q2 = q1 * u
q3 = q2 `div` (b ^ (k + 1))
r1 = x `mod` (b ^ (k + 1))
r2 = (q3 * m) `mod` (b ^ (k + 1))
r = r1 - r2
r' = if r < 0 then r + (b ^ (k + 1)) else r
result = minimize r' m
minimize :: Integer -> Integer -> Integer
minimize r m | r < 0 = error "BLECH"
| r >= m = minimize (r - m) m
| otherwise = r
-- runOperation :: Handle -> IO ()
-- runOperation hndl =
-- do m <- randomVal =<< randomRIO (1,size)
-- v <- randomVal =<< randomRIO (1,size)
-- let barrett = barrett_u m
-- let vk = computeK v
-- if vk > (2 * (bk barrett))
-- then runOperation hndl
-- else do hPutStrLn hndl ("m: " ++ showHex m "")
-- hPutStrLn hndl ("k: " ++ show (bk barrett))
-- hPutStrLn hndl ("u: " ++ show (bu barrett))
-- let me = reduce v barrett
-- standard = v `mod` m
-- unless (me == standard) $
-- fail "Barrett messed up."
-- hPutStrLn hndl ("v: " ++ showHex v "")
-- hPutStrLn hndl ("r: " ++ showHex me "")
-- hFlush hndl
--
-- generateFile :: String ->
-- IO ()
-- generateFile file =
-- withFile (file ++ "_tests.txt") WriteMode $ \ hndl ->
-- forM_ [0..2000] $ \ _ ->
-- runOperation hndl
--
-- main :: IO ()
-- main =
-- do generateFile "add" $ \ x y ->
-- (x, y, x + y)
-- generateFile "sub" $ \ x y ->
-- let x' = max x y
-- y' = min x y
-- in (x', y', x' - y')
-- generateFile "mul" $ \ x y ->
-- (x, y, x * y)
-- generateFile "div" $ \ x y ->
-- let y' = if y == 0 then 1 else y
-- in (x, y', x / y')
-- generateFile "mod" $ \ x y ->
-- let y' = if y == 0 then 1 else y
-- in (x, y', x / y')
-- generateFile "barrett"

25000
tests/math/barrett.tests Normal file

File diff suppressed because it is too large Load Diff

20000
tests/math/modexp.tests Normal file

File diff suppressed because it is too large Load Diff

15000
tests/math/signed_add.tests Normal file

File diff suppressed because it is too large Load Diff

15000
tests/math/signed_div.tests Normal file

File diff suppressed because it is too large Load Diff

15000
tests/math/signed_mod.tests Normal file

File diff suppressed because it is too large Load Diff

15000
tests/math/signed_mul.tests Normal file

File diff suppressed because it is too large Load Diff

15000
tests/math/signed_sub.tests Normal file

File diff suppressed because it is too large Load Diff

15000
tests/math/unsigned_add.tests Normal file

File diff suppressed because it is too large Load Diff

15000
tests/math/unsigned_div.tests Normal file

File diff suppressed because it is too large Load Diff

15000
tests/math/unsigned_mod.tests Normal file

File diff suppressed because it is too large Load Diff

15000
tests/math/unsigned_mul.tests Normal file

File diff suppressed because it is too large Load Diff

15000
tests/math/unsigned_sub.tests Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff