9 Commits

Author SHA1 Message Date
84f6925f60 Infrastructure for generating test data. 2019-08-17 21:37:17 -07:00
4ae957aac6 Preliminary support for generating test vectors. 2019-08-15 20:24:04 -07:00
f4a3cf69ad Add some more serialization quickchecks. 2019-08-04 16:54:34 -07:00
d7665acf64 Add a bunch of QuickCheck properties for binary operators. 2019-07-30 18:24:49 -07:00
1d8907539d Get back to basics, with some basic tests working. 2019-07-30 16:23:14 -07:00
203c23e277 Add from_bytes() and to_bytes() to CryptoNum, and do a basic implementation of from_bytes(). 2019-07-23 21:05:20 -07:00
aff88eb2f0 Support a more complete (and simple) requirements gathering mechanism,
and add support for binary operations.

This version of requirements generation simply generates every numeric
size within a provided range, and then will reject trait implementations
that rely on values outside this range. It should be a little more easy
to reason about, and easier to make local changes as I (inevitably) need
to modify rules.
2019-07-22 08:14:40 -07:00
ab465296f2 The basic binary operations. 2019-07-15 21:00:12 -07:00
fa872c951a Start experimenting with full generation of all of the numeric types.
Previously, we used a little bit of generation to drive a lot of Rust
macros. This works, but it's a little confusing to read and write. In
addition, we used a lot of implementations with variable timings based
on their input, which isn't great for crypto. This is the start of an
attempt to just generate all of the relevant Rust code directly, and to
use timing-channel resistant implementations for most of the routines.
2019-07-15 17:39:06 -07:00
52 changed files with 966 additions and 106 deletions

View File

@@ -2,7 +2,8 @@
name = "cryptonum" name = "cryptonum"
version = "0.1.0" version = "0.1.0"
authors = ["awick"] authors = ["awick"]
edition = "2018"
[dependencies] [dependencies]
quickcheck = "^0.7.2" quickcheck = "^0.8.5"
rand = "^0.6.0" # rand = "^0.6.0"

2
generation/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.ghc.environment*
dist-newstyle/

13
generation/LICENSE Normal file
View File

@@ -0,0 +1,13 @@
Copyright (c) 2019 Adam Wick
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

2
generation/Setup.hs Normal file
View File

@@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

View File

@@ -0,0 +1,31 @@
cabal-version: 2.0
-- Initial package description 'generation.cabal' generated by 'cabal
-- init'. For further documentation, see
-- http://haskell.org/cabal/users-guide/
name: generation
version: 0.1.0.0
synopsis: Generates the cryptonum Rust library, based on requirements.
homepage: http://github.com/acw/cryptonum
license: ISC
license-file: LICENSE
author: Adam Wick
maintainer: awick@uhsure.com
copyright: 2019
category: Math
build-type: Simple
extra-source-files: CHANGELOG.md
executable generation
main-is: Main.hs
other-modules: Base, BinaryOps, Compare, Conversions, CryptoNum, File, Gen, Testing
-- other-extensions:
build-depends: base ^>=4.12.0.0,
containers,
directory,
filepath,
mtl,
random
hs-source-dirs: src
default-language: Haskell2010
ghc-options: -Wall

57
generation/src/Base.hs Normal file
View File

@@ -0,0 +1,57 @@
module Base(
base
)
where
import Control.Monad(forM_)
import File
import Gen
base :: File
base = File {
predicate = \ _ _ -> True,
outputName = "base",
generator = declareBaseStructure,
testGenerator = Nothing
}
declareBaseStructure :: Word -> Gen ()
declareBaseStructure bitsize =
do let name = "U" ++ show bitsize
entries = bitsize `div` 64
top = entries - 1
out "use core::fmt;"
out "use quickcheck::{Arbitrary,Gen};"
blank
out "#[derive(Clone)]"
wrapIndent ("pub struct " ++ name) $
out ("pub(crate) value: [u64; " ++ show entries ++ "]")
blank
implFor "fmt::Debug" name $
wrapIndent "fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result" $
do out ("f.debug_tuple(" ++ show name ++ ")")
forM_ [0..top] $ \ i ->
out (" .field(&self.value[" ++ show i ++ "])")
out " .finish()"
blank
implFor "fmt::UpperHex" name $
wrapIndent "fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result" $
do forM_ (reverse [1..top]) $ \ i ->
out ("write!(f, \"{:X}\", self.value[" ++ show i ++ "])?;")
out "write!(f, \"{:X}\", self.value[0])"
blank
implFor "fmt::LowerHex" name $
wrapIndent "fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result" $
do forM_ (reverse [1..top]) $ \ i ->
out ("write!(f, \"{:x}\", self.value[" ++ show i ++ "])?;")
out "write!(f, \"{:x}\", self.value[0])"
blank
implFor "Arbitrary" name $
wrapIndent "fn arbitrary<G: Gen>(g: &mut G) -> Self" $
do out (name ++ " {")
indent $
do out ("value: [")
indent $ forM_ [0..top] $ \ _ ->
out ("g.next_u64(),")
out ("]")
out ("}")

171
generation/src/BinaryOps.hs Normal file
View File

@@ -0,0 +1,171 @@
module BinaryOps(
binaryOps
)
where
import Control.Monad(forM_,replicateM_)
import Data.Bits((.&.), (.|.), shiftL, xor)
import File
import Gen
binaryTestCount :: Int
binaryTestCount = 3000
binaryOps :: File
binaryOps = File {
predicate = \ _ _ -> True,
outputName = "binary",
generator = declareBinaryOperators,
testGenerator = Just testVectors
}
declareBinaryOperators :: Word -> Gen ()
declareBinaryOperators bitsize =
do let name = "U" ++ show bitsize
entries = bitsize `div` 64
out "use core::ops::{BitAnd,BitAndAssign};"
out "use core::ops::{BitOr,BitOrAssign};"
out "use core::ops::{BitXor,BitXorAssign};"
out "use core::ops::Not;"
out "#[cfg(test)]"
out "use crate::CryptoNum;"
out "#[cfg(test)]"
out "use quickcheck::quickcheck;"
out ("use super::U" ++ show bitsize ++ ";")
blank
generateBinOps "BitAnd" name "bitand" "&=" entries
blank
generateBinOps "BitOr" name "bitor" "|=" entries
blank
generateBinOps "BitXor" name "bitxor" "^=" entries
blank
implFor "Not" name $
do out "type Output = Self;"
blank
wrapIndent "fn not(mut self) -> Self" $
do forM_ [0..entries-1] $ \ i ->
out ("self.value[" ++ show i ++ "] = !self.value[" ++ show i ++ "];")
out "self"
blank
implFor' "Not" ("&'a " ++ name) $
do out ("type Output = " ++ name ++ ";")
blank
wrapIndent ("fn not(self) -> " ++ name) $
do out "let mut output = self.clone();"
forM_ [0..entries-1] $ \ i ->
out ("output.value[" ++ show i ++ "] = !self.value[" ++ show i ++ "];")
out "output"
blank
addBinaryLaws name entries
generateBinOps :: String -> String -> String -> String -> Word -> Gen ()
generateBinOps trait name fun op entries =
do implFor (trait ++ "Assign") name $
wrapIndent ("fn " ++ fun ++ "_assign(&mut self, rhs: Self)") $
forM_ [0..entries-1] $ \ i ->
out ("self.value[" ++ show i ++ "] "++op++" rhs.value[" ++ show i ++ "];")
blank
implFor' (trait ++ "Assign<&'a " ++ name ++ ">") name $
wrapIndent ("fn " ++ fun ++ "_assign(&mut self, rhs: &Self)") $
forM_ [0..entries-1] $ \ i ->
out ("self.value[" ++ show i ++ "] "++op++" rhs.value[" ++ show i ++ "];")
blank
generateBinOpsFromAssigns trait name fun op
generateBinOpsFromAssigns :: String -> String -> String -> String -> Gen ()
generateBinOpsFromAssigns trait name fun op =
do implFor trait name $
do out "type Output = Self;"
blank
wrapIndent ("fn " ++ fun ++ "(mut self, rhs: Self) -> Self") $
do out ("self " ++ op ++ " rhs;")
out "self"
blank
implFor' (trait ++ "<&'a " ++ name ++ ">") name $
do out "type Output = Self;"
blank
wrapIndent ("fn " ++ fun ++ "(mut self, rhs: &Self) -> Self") $
do out ("self " ++ op ++ " rhs;")
out "self"
blank
implFor' (trait ++ "<" ++ name ++ ">") ("&'a " ++ name) $
do out ("type Output = " ++ name ++ ";")
blank
wrapIndent ("fn " ++ fun ++ "(self, mut rhs: " ++ name ++ ") -> " ++ name) $
do out ("rhs " ++ op ++ " self;")
out "rhs"
blank
implFor'' (trait ++ "<&'a " ++ name ++ ">") ("&'b " ++ name) $
do out ("type Output = " ++ name ++ ";")
blank
wrapIndent ("fn " ++ fun ++ "(self, rhs: &" ++ name ++ ") -> " ++ name) $
do out "let mut output = self.clone();"
out ("output " ++ op ++ " rhs;")
out "output"
addBinaryLaws :: String -> Word -> Gen ()
addBinaryLaws name entries =
do let args3 = "(a: " ++ name ++ ", b: " ++ name ++ ", c: " ++ name ++ ")"
args2 = "(a: " ++ name ++ ", b: " ++ name ++ ")"
out "#[cfg(test)]"
wrapIndent "quickcheck!" $
do wrapIndent ("fn and_associative" ++ args3 ++ " -> bool") $
out ("((&a & &b) & &c) == (&a & (&b & &c))")
blank
wrapIndent ("fn and_commutative" ++ args2 ++ " -> bool") $
out ("(&a & &b) == (&b & &a)")
blank
wrapIndent ("fn and_idempotent" ++ args2 ++ " -> bool") $
out ("(&a & &b) == (&a & &b & &a)")
blank
wrapIndent ("fn xor_associative" ++ args3 ++ " -> bool") $
out ("((&a ^ &b) ^ &c) == (&a ^ (&b ^ &c))")
blank
wrapIndent ("fn xor_commutative" ++ args2 ++ " -> bool") $
out ("(&a ^ &b) == (&b ^ &a)")
blank
wrapIndent ("fn or_associative" ++ args3 ++ " -> bool") $
out ("((&a | &b) | &c) == (&a | (&b | &c))")
blank
wrapIndent ("fn or_commutative" ++ args2 ++ " -> bool") $
out ("(&a | &b) == (&b | &a)")
blank
wrapIndent ("fn or_idempotent" ++ args2 ++ " -> bool") $
out ("(&a | &b) == (&a | &b | &a)")
blank
wrapIndent ("fn and_or_distribution" ++ args3 ++ "-> bool") $
out ("(&a & (&b | &c)) == ((&a & &b) | (&a & &c))")
blank
wrapIndent ("fn xor_clears(a: " ++ name ++ ") -> bool") $
out (name ++ "::zero() == (&a ^ &a)")
blank
wrapIndent ("fn double_neg_ident(a: " ++ name ++ ") -> bool") $
out ("a == !!&a")
blank
wrapIndent ("fn and_ident(a: " ++ name ++ ") -> bool") $
do out ("let ones = !" ++ name ++ "::zero();")
out ("(&a & &ones) == a")
blank
wrapIndent ("fn or_ident(a: " ++ name ++ ") -> bool") $
out ("(&a | " ++ name ++ "::zero()) == a")
wrapIndent ("fn neg_as_xor(a: " ++ name ++ ") -> bool") $
do out ("let ones = " ++ name ++ "{ value: [0xFFFFFFFFFFFFFFFFu64; "
++ show entries ++ "] };")
out ("!&a == (&ones ^ &a)")
testVectors :: Word -> Gen ()
testVectors bitsize = replicateM_ binaryTestCount $
do a <- newNum False bitsize
b <- newNum False bitsize
let o = a .|. b
c = a .&. b
n = a `xor` ((1 `shiftL` fromIntegral bitsize) - 1)
x = a `xor` b
emitTestVariable 'a' a
emitTestVariable 'b' b
emitTestVariable 'c' c
emitTestVariable 'o' o
emitTestVariable 'n' n
emitTestVariable 'x' x

61
generation/src/Compare.hs Normal file
View File

@@ -0,0 +1,61 @@
module Compare(comparisons)
where
import Control.Monad(forM_)
import File
import Gen
comparisons :: File
comparisons = File {
predicate = \ _ _ -> True,
outputName = "compare",
generator = declareComparators,
testGenerator = Nothing
}
declareComparators :: Word -> Gen ()
declareComparators bitsize =
do let name = "U" ++ show bitsize
entries = bitsize `div` 64
top = entries - 1
out "use core::cmp::{Eq,Ordering,PartialEq};"
out "#[cfg(test)]"
out "use quickcheck::quickcheck;"
out ("use super::" ++ name ++ ";")
blank
implFor "PartialEq" name $
wrapIndent "fn eq(&self, other: &Self) -> bool" $
do forM_ (reverse [1..top]) $ \ i ->
out ("self.value[" ++ show i ++ "] == other.value[" ++ show i ++ "] && ")
out "self.value[0] == other.value[0]"
blank
implFor "Eq" name $ return ()
blank
implFor "Ord" name $
wrapIndent "fn cmp(&self, other: &Self) -> Ordering" $
do out ("self.value[" ++ show top ++ "].cmp(&other.value[" ++ show top ++ "])")
forM_ (reverse [0..top-1]) $ \ i ->
out (" .then(self.value[" ++ show i ++ "].cmp(&other.value[" ++ show i ++ "]))")
blank
implFor "PartialOrd" name $
wrapIndent "fn partial_cmp(&self, other: &Self) -> Option<Ordering>" $
out "Some(self.cmp(other))"
blank
out "#[cfg(test)]"
wrapIndent "quickcheck!" $
do let transFun n = "fn " ++ n ++ "(a: " ++ name ++ ", b: " ++ name ++
", c: " ++ name ++ ") -> bool"
wrapIndent (transFun "eq_is_transitive") $
out ("if a == c { a == b && b == c } else { a != b || b != c }")
blank
wrapIndent (transFun "gt_is_transitive") $
out ("if a > b && b > c { a > c } else { true }")
blank
wrapIndent (transFun "ge_is_transitive") $
out ("if a >= b && b >= c { a >= c } else { true }")
blank
wrapIndent (transFun "lt_is_transitive") $
out ("if a < b && b < c { a < c } else { true }")
blank
wrapIndent (transFun "le_is_transitive") $
out ("if a <= b && b <= c { a <= c } else { true }")

View File

@@ -0,0 +1,99 @@
module Conversions(
conversions
)
where
import Data.List(intercalate)
import File
import Gen
conversions :: File
conversions = File {
predicate = \ _ _ -> True,
outputName = "conversions",
generator = declareConversions,
testGenerator = Nothing
}
declareConversions :: Word -> Gen ()
declareConversions bitsize =
do let name = "U" ++ show bitsize
entries = bitsize `div` 64
out "use core::convert::{From,TryFrom};"
out "#[cfg(test)]"
out "use quickcheck::quickcheck;"
out ("use super::" ++ name ++ ";")
blank
buildUnsignedPrimConversions name entries "u8" >> blank
buildUnsignedPrimConversions name entries "u16" >> blank
buildUnsignedPrimConversions name entries "u32" >> blank
buildUnsignedPrimConversions name entries "u64" >> blank
buildUnsignedPrimConversions name entries "usize" >> blank
buildSignedPrimConversions name entries "i8" >> blank
buildSignedPrimConversions name entries "i16" >> blank
buildSignedPrimConversions name entries "i32" >> blank
buildSignedPrimConversions name entries "i64" >> blank
buildSignedPrimConversions name entries "isize"
blank
out ("#[cfg(test)]")
wrapIndent "quickcheck!" $
do roundTripTest name "u8" >> blank
roundTripTest name "u16" >> blank
roundTripTest name "u32" >> blank
roundTripTest name "u64" >> blank
roundTripTest name "usize"
buildUnsignedPrimConversions :: String -> Word -> String -> Gen ()
buildUnsignedPrimConversions name entries primtype =
do implFor ("From<" ++ primtype ++ ">") name $
wrapIndent ("fn from(x: " ++ primtype ++ ") -> Self") $
do let zeroes = replicate (fromIntegral (entries - 1)) "0,"
values = ("x as u64," : zeroes)
out (name ++ " { value: [ ")
indent $ printBy 8 values
out ("] }")
blank
implFor ("From<" ++ name ++ ">") primtype $
wrapIndent ("fn from(x: " ++ name ++ ") -> Self") $
out ("x.value[0] as " ++ primtype)
blank
implFor' ("From<&'a " ++ name ++ ">") primtype $
wrapIndent ("fn from(x: &" ++ name ++ ") -> Self") $
out ("x.value[0] as " ++ primtype)
buildSignedPrimConversions :: String -> Word -> String -> Gen ()
buildSignedPrimConversions name entries primtype =
do implFor ("TryFrom<" ++ primtype ++ ">") name $
do out ("type Error = &'static str;")
blank
wrapIndent ("fn try_from(x: " ++ primtype ++ ") -> Result<Self,Self::Error>") $
do wrapIndent ("if x < 0") $
out ("return Err(\"Attempt to convert negative number to " ++
name ++ ".\");")
blank
let zeroes = replicate (fromIntegral (entries - 1)) "0,"
values = ("x as u64," : zeroes)
out ("Ok(" ++ name ++ " { value: [ ")
indent $ printBy 8 values
out ("] })")
blank
implFor ("From<" ++ name ++ ">") primtype $
wrapIndent ("fn from(x: " ++ name ++ ") -> Self") $
out ("x.value[0] as " ++ primtype)
blank
implFor' ("From<&'a " ++ name ++ ">") primtype $
wrapIndent ("fn from(x: &" ++ name ++ ") -> Self") $
out ("x.value[0] as " ++ primtype)
roundTripTest :: String -> String -> Gen ()
roundTripTest name primtype =
wrapIndent ("fn " ++ primtype ++ "_roundtrips(x: " ++ primtype ++ ") -> bool") $
do out ("let big = " ++ name ++ "::from(x);");
out ("let small = " ++ primtype ++ "::from(big);")
out ("x == small")
printBy :: Int -> [String] -> Gen ()
printBy amt xs
| length xs <= amt = out (intercalate " " xs)
| otherwise = printBy amt (take amt xs) >>
printBy amt (drop amt xs)

179
generation/src/CryptoNum.hs Normal file
View File

@@ -0,0 +1,179 @@
module CryptoNum(
cryptoNum
)
where
import Control.Monad(forM_)
import File
import Gen
cryptoNum :: File
cryptoNum = File {
predicate = \ _ _ -> True,
outputName = "cryptonum",
generator = declareCryptoNumInstance,
testGenerator = Nothing
}
declareCryptoNumInstance :: Word -> Gen ()
declareCryptoNumInstance bitsize =
do let name = "U" ++ show bitsize
entries = bitsize `div` 64
top = entries - 1
out "use core::cmp::min;"
out "use crate::CryptoNum;"
out "#[cfg(test)]"
out "use crate::testing::{build_test_path,run_test};"
out "#[cfg(test)]"
out "use quickcheck::{Arbitrary,Gen,quickcheck};"
out "#[cfg(test)]"
out "use std::fmt;"
out ("use super::" ++ name ++ ";")
blank
implFor "CryptoNum" name $
do wrapIndent ("fn zero() -> Self") $
out (name ++ "{ value: [0; " ++ show entries ++ "] }")
blank
wrapIndent ("fn is_zero(&self) -> bool") $
do forM_ (reverse [1..top]) $ \ i ->
out ("self.value[" ++ show i ++ "] == 0 &&")
out "self.value[0] == 0"
blank
wrapIndent ("fn is_even(&self) -> bool") $
out "self.value[0] & 0x1 == 0"
blank
wrapIndent ("fn is_odd(&self) -> bool") $
out "self.value[0] & 0x1 == 1"
blank
wrapIndent ("fn bit_length() -> usize") $
out (show bitsize)
blank
wrapIndent ("fn mask(&mut self, len: usize)") $
do out ("let dellen = min(len, " ++ show entries ++ ");")
wrapIndent ("for i in dellen.." ++ show entries) $
out ("self.value[i] = 0;")
blank
wrapIndent ("fn testbit(&self, bit: usize) -> bool") $
do out "let idx = bit / 64;"
out "let offset = bit % 64;"
wrapIndent ("if idx >= " ++ show entries) $
out "return false;"
out "(self.value[idx] & (1u64 << offset)) != 0"
blank
wrapIndent ("fn from_bytes(bytes: &[u8]) -> Self") $
do out ("let biggest = min(" ++ show (bitsize `div` 8) ++ ", " ++
"bytes.len()) - 1;")
out ("let mut idx = biggest / 8;")
out ("let mut shift = (biggest % 8) * 8;")
out ("let mut i = 0;")
out ("let mut res = " ++ name ++ "::zero();")
blank
wrapIndent ("while i <= biggest") $
do out ("res.value[idx] |= (bytes[i] as u64) << shift;")
out ("i += 1;")
out ("if shift == 0 {")
indent $
do out "shift = 56;"
out "if idx > 0 { idx -= 1; }"
out ("} else {")
indent $
out "shift -= 8;"
out "}"
blank
out "res"
blank
wrapIndent ("fn to_bytes(&self, bytes: &mut [u8])") $
do let bytes = bitsize `div` 8
out ("if bytes.len() == 0 { return; }")
blank
forM_ [0..bytes-1] $ \ idx ->
do let (validx, shift) = byteShiftInfo idx
out ("let byte" ++ show idx ++ " = (self.value[" ++
show validx ++ "] >> " ++ show shift ++ ")" ++
" as u8;")
blank
out ("let mut idx = min(bytes.len() - 1, " ++ show (bytes - 1) ++ ");")
forM_ [0..bytes-2] $ \ i ->
do out ("bytes[idx] = byte" ++ show i ++ ";")
out ("if idx == 0 { return; }")
out ("idx -= 1;")
out ("bytes[idx] = byte" ++ show (bytes-1) ++ ";")
blank
let bytes = bitsize `div` 8
struct = "Bytes" ++ show bytes
out "#[cfg(test)]"
out "#[derive(Clone)]"
wrapIndent ("struct " ++ struct) $
out ("value: [u8; " ++ show bytes ++ "]")
blank
out "#[cfg(test)]"
implFor "PartialEq" struct $
wrapIndent ("fn eq(&self, other: &Self) -> bool") $
out "self.value.iter().zip(other.value.iter()).all(|(a,b)| a == b)"
blank
out "#[cfg(test)]"
implFor "fmt::Debug" struct $
wrapIndent ("fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result") $
out "f.debug_list().entries(self.value.iter()).finish()"
blank
out "#[cfg(test)]"
implFor "Arbitrary" struct $
wrapIndent ("fn arbitrary<G: Gen>(g: &mut G) -> Self") $
do out ("let mut res = " ++ struct ++ "{ value: [0; " ++ show bytes ++ "] };")
out ("g.fill_bytes(&mut res.value);")
out ("res")
blank
out "#[cfg(test)]"
wrapIndent "quickcheck!" $
do wrapIndent ("fn to_from_ident(x: " ++ name ++ ") -> bool") $
do out ("let mut buffer = [0; " ++ show bytes ++ "];")
out ("x.to_bytes(&mut buffer);");
out ("let y = " ++ name ++ "::from_bytes(&buffer);")
out ("x == y")
blank
wrapIndent ("fn from_to_ident(x: " ++ struct ++ ") -> bool") $
do out ("let val = " ++ name ++ "::from_bytes(&x.value);")
out ("let mut buffer = [0; " ++ show bytes ++ "];")
out ("val.to_bytes(&mut buffer);")
out ("buffer.iter().zip(x.value.iter()).all(|(a,b)| a == b)")
blank
out "#[cfg(test)]"
out "#[allow(non_snake_case)]"
out "#[test]"
wrapIndent "fn KATs()" $
do let name' = pad 5 '0' (show bitsize)
out ("run_test(build_test_path(\"base\",\"" ++ name' ++ "\"), 8, |case| {")
indent $
do out ("let (neg0, xbytes) = case.get(\"x\").unwrap();")
out ("let (neg1, mbytes) = case.get(\"m\").unwrap();")
out ("let (neg2, zbytes) = case.get(\"z\").unwrap();")
out ("let (neg3, ebytes) = case.get(\"e\").unwrap();")
out ("let (neg4, obytes) = case.get(\"o\").unwrap();")
out ("let (neg5, rbytes) = case.get(\"r\").unwrap();")
out ("let (neg6, bbytes) = case.get(\"b\").unwrap();")
out ("let (neg7, tbytes) = case.get(\"t\").unwrap();")
out ("assert!(!neg0&&!neg1&&!neg2&&!neg3&&!neg4&&!neg5&&!neg6&&!neg7);")
out ("let mut x = "++name++"::from_bytes(xbytes);")
out ("let m = "++name++"::from_bytes(mbytes);")
out ("let z = 1 == zbytes[0];")
out ("let e = 1 == ebytes[0];")
out ("let o = 1 == obytes[0];")
out ("let r = "++name++"::from_bytes(rbytes);")
out ("let b = usize::from("++name++"::from_bytes(bbytes));")
out ("let t = 1 == tbytes[0];")
out ("assert_eq!(x.is_zero(), z);")
out ("assert_eq!(x.is_even(), e);")
out ("assert_eq!(x.is_odd(), o);")
out ("assert_eq!(x.testbit(b), t);")
out ("x.mask(usize::from(&m));")
out ("assert_eq!(x, r);")
out ("});")
byteShiftInfo :: Word -> (Word, Word)
byteShiftInfo idx =
(idx `div` 8, (idx `mod` 8) * 8)
pad :: Int -> Char -> String -> String
pad len c str
| length str >= len = str
| otherwise = pad len c (c:str)

71
generation/src/File.hs Normal file
View File

@@ -0,0 +1,71 @@
module File(
File(..),
Task(..),
addModuleTasks,
makeTasks
)
where
import Control.Monad(forM_)
import Data.Char(toUpper)
import Data.List(isPrefixOf)
import qualified Data.Map.Strict as Map
import Gen(Gen,blank,out)
import System.FilePath(takeBaseName,takeDirectory,takeFileName,(</>))
data File = File {
predicate :: Word -> [Word] -> Bool,
outputName :: FilePath,
generator :: Word -> Gen (),
testGenerator :: Maybe (Word -> Gen ())
}
data Task = Task {
outputFile :: FilePath,
fileGenerator :: Gen ()
}
makeTasks :: FilePath -> FilePath ->
Word -> [Word] ->
File ->
[Task]
makeTasks srcBase testBase size allSizes file
| predicate file size allSizes =
let base = Task (srcBase </> ("u" ++ show size) </> outputName file <> ".rs") (generator file size)
in case testGenerator file of
Nothing -> [base]
Just x ->
[base, Task (testBase </> outputName file </> ("U" ++ show size ++ ".test")) (x size)]
| otherwise = []
addModuleTasks :: FilePath -> [Task] -> [Task]
addModuleTasks base baseTasks = unsignedTask : (baseTasks ++ moduleTasks)
where
moduleMap = foldr addModuleInfo Map.empty baseTasks
addModuleInfo task map
| base `isPrefixOf` outputFile task =
Map.insertWith (++) (takeDirectory (outputFile task))
[takeBaseName (outputFile task)]
map
| otherwise = map
moduleTasks = Map.foldrWithKey generateModuleTask [] moduleMap
generateModuleTask directory mods acc = acc ++ [Task {
outputFile = directory </> "mod.rs",
fileGenerator =
do forM_ mods $ \ modle -> out ("mod " ++ modle ++ ";")
blank
out ("pub use base::" ++ upcase (takeFileName directory) ++ ";")
}]
unsignedTask = Task {
outputFile = base </> "unsigned" </> "mod.rs",
fileGenerator =
do forM_ (Map.keys moduleMap) $ \ key ->
out ("mod " ++ takeFileName key ++ ";")
blank
forM_ (Map.keys moduleMap) $ \ key ->
out ("pub use " ++ takeFileName key ++ "::" ++
upcase (takeFileName key) ++ ";")
}
upcase :: String -> String
upcase = map toUpper

115
generation/src/Gen.hs Normal file
View File

@@ -0,0 +1,115 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Gen(
Gen(Gen),
runGen,
gensym,
indent,
blank,
out,
wrapIndent,
implFor,
implFor',
implFor'',
newNum,
TestVariable(..),
)
where
import Control.Monad.RWS.Strict(RWS,evalRWS)
import Control.Monad.State.Class(MonadState,get,put)
import Control.Monad.Writer.Class(MonadWriter,tell)
import Data.Bits(shiftL)
import Data.List(replicate)
import Data.Word(Word)
import Numeric(showHex)
import System.Random(StdGen, newStdGen, random, randomR)
newtype Gen a = Gen { unGen :: RWS () String GenState a}
deriving (Applicative, Functor, Monad, MonadState GenState, MonadWriter String)
tabAmount :: Word
tabAmount = 4
data GenState = GenState {
indentAmount :: Word,
gensymIndex :: Word,
rng :: StdGen
}
initGenState :: IO GenState
initGenState =
do rng0 <- newStdGen
return GenState { indentAmount = 0, gensymIndex = 0, rng = rng0 }
runGen :: FilePath -> Gen a -> IO a
runGen path action =
do state0 <- initGenState
let (res, contents) = evalRWS (unGen action) () state0
writeFile path contents
return res
gensym :: String -> Gen String
gensym prefix =
do gs <- get
let gs' = gs{ gensymIndex = gensymIndex gs + 1 }
put gs'
return (prefix ++ show (gensymIndex gs))
indent :: Gen a -> Gen a
indent action =
do gs <- get
put gs{ indentAmount = indentAmount gs + tabAmount }
res <- action
put gs
return res
blank :: Gen ()
blank = tell "\n"
out :: String -> Gen ()
out val =
do gs <- get
tell (replicate (fromIntegral (indentAmount gs)) ' ')
tell val
tell "\n"
wrapIndent :: String -> Gen a -> Gen a
wrapIndent val middle =
do gs <- get
tell (replicate (fromIntegral (indentAmount gs)) ' ')
tell val
tell " {\n"
res <- indent middle
tell (replicate (fromIntegral (indentAmount gs)) ' ')
tell "}\n"
return res
implFor :: String -> String -> Gen a -> Gen a
implFor trait name middle =
wrapIndent ("impl " ++ trait ++ " for " ++ name) middle
implFor' :: String -> String -> Gen a -> Gen a
implFor' trait name middle =
wrapIndent ("impl<'a> " ++ trait ++ " for " ++ name) middle
implFor'' :: String -> String -> Gen a -> Gen a
implFor'' trait name middle =
wrapIndent ("impl<'a,'b> " ++ trait ++ " for " ++ name) middle
newNum :: Bool -> Word -> Gen Integer
newNum signed bits =
do gs <- get
let rng0 = rng gs
let high = (1 `shiftL` fromIntegral bits) - 1
let (v, rng1) = randomR (0, high) rng0
let (sign, rng2) = random rng1
let v' = if signed && sign then -v else v
put gs{ rng = rng2 }
return v'
class TestVariable a where
emitTestVariable :: Char -> a -> Gen ()
instance TestVariable Integer where
emitTestVariable c v =
out ([c] ++ ": " ++ showHex v "")

62
generation/src/Main.hs Normal file
View File

@@ -0,0 +1,62 @@
module Main
where
import Base(base)
import BinaryOps(binaryOps)
import Compare(comparisons)
import Conversions(conversions)
import CryptoNum(cryptoNum)
import Control.Monad(forM_,unless)
import Data.Word(Word)
import File(File,Task(..),addModuleTasks,makeTasks)
import Gen(runGen)
import System.Directory(createDirectoryIfMissing)
import System.Environment(getArgs)
import System.Exit(die)
import System.FilePath(takeDirectory,(</>))
lowestBitsize :: Word
lowestBitsize = 192
highestBitsize :: Word
highestBitsize = 512
bitsizes :: [Word]
bitsizes = [lowestBitsize,lowestBitsize+64..highestBitsize]
unsignedFiles :: [File]
unsignedFiles = [
base
, binaryOps
, comparisons
, conversions
, cryptoNum
]
signedFiles :: [File]
signedFiles = [
]
makeTasks' :: FilePath -> FilePath -> [File] -> [Task]
makeTasks' srcPath testPath files =
concatMap (\ sz -> concatMap (makeTasks srcPath testPath sz bitsizes) files) bitsizes
makeAllTasks :: FilePath -> FilePath -> [Task]
makeAllTasks srcPath testPath = addModuleTasks srcPath $
makeTasks' (srcPath </> "unsigned") testPath unsignedFiles ++
makeTasks' (srcPath </> "signed") testPath signedFiles
main :: IO ()
main =
do args <- getArgs
unless (length args == 1) $
die ("generation takes exactly one argument, the target directory")
let topLevel = head args
srcPath = topLevel </> "src"
testPath = topLevel </> "testdata"
tasks = makeAllTasks srcPath testPath
total = length tasks
forM_ (zip [(1::Word)..] tasks) $ \ (i, task) ->
do putStrLn ("[" ++ show i ++ "/" ++ show total ++ "] " ++ outputFile task)
createDirectoryIfMissing True (takeDirectory (outputFile task))
runGen (outputFile task) (fileGenerator task)

View File

@@ -0,0 +1,4 @@
module Testing(
)
where

52
old/signed/mod.rs Normal file
View File

@@ -0,0 +1,52 @@
//! This module includes a large number of signed integer types for very
//! large integers, designed to try to match good performance with a high
//! assurance threshold.
//!
//! The types provided in this module, and the functions available for each
//! of those types, is derived from standard bit widths for RSA, DSA, and
//! Elliptic Curve encryption schemes. If this library does not include a
//! function you would like for another cryptographic scheme, please reach
//! out to the authors; in many cases, the relevant code can be automatically
//! generated.
//!
#[macro_use]
mod add;
#[macro_use]
mod base;
#[macro_use]
mod compare;
#[macro_use]
mod conversion;
#[macro_use]
mod div;
#[macro_use]
mod egcd;
#[macro_use]
mod moddiv;
#[macro_use]
mod modinv;
#[macro_use]
mod mul;
#[macro_use]
mod scale;
#[macro_use]
mod shift;
#[macro_use]
mod subtraction;
use quickcheck::{Arbitrary,Gen};
use std::cmp::{Ord,Ordering,PartialOrd};
use std::fmt;
use std::ops::{Add,AddAssign};
use std::ops::{Div,DivAssign};
use std::ops::{Mul,MulAssign};
use std::ops::{Rem,RemAssign};
use std::ops::{Shl,ShlAssign,Shr,ShrAssign};
use std::ops::{Sub,SubAssign};
use unsigned::*;
pub use self::egcd::EGCD;
pub use self::moddiv::ModDiv;
pub use self::modinv::ModInv;
include!("invoc.rs");

View File

@@ -1,49 +1,38 @@
extern crate quickcheck; #![cfg_attr(not(test),no_std)]
extern crate rand;
pub mod signed; pub mod signed;
pub mod unsigned; pub mod unsigned;
#[cfg(test)] #[cfg(test)]
pub mod testing; mod testing;
#[cfg(test)] /// A trait definition for large numbers.
mod arithmetic { pub trait CryptoNum {
use super::signed::*; /// Generate a new value of the given type.
use super::unsigned::*; fn zero() -> Self;
/// Test if the number is zero.
quickcheck! { fn is_zero(&self) -> bool;
fn commutivity_signed_add(x: I576, y: I576) -> bool { /// Test if the number is even.
(&x + &y) == (&y + &x) fn is_even(&self) -> bool;
} /// Test if the number is odd.
fn commutivity_unsigned_add(x: U576, y: U576) -> bool { fn is_odd(&self) -> bool;
(&x + &y) == (&y + &x) /// The size of this number in bits.
} fn bit_length() -> usize;
fn commutivity_unsigned_mul(x: U192, y: U192) -> bool { /// Mask off the high parts of the number. In particular, it
(&x * &y) == (&y * &x) /// zeros the bits above (len * 64).
fn mask(&mut self, len: usize);
/// Test if the given bit is zero, where bits are numbered in
/// least-significant order (0 is the LSB, etc.).
fn testbit(&self, bit: usize) -> bool;
/// Convert a slice into a CryptoNum, assuming big-endian memory layout.
/// If you pass in a slice bigger than the bit size of the numeric type,
/// this will assume that the number is in the first `n` bits of the
/// memory layout. If you pass in a smaller buffer, it will use the bits
/// available as the low `n` bits of the number.
fn from_bytes(bytes: &[u8]) -> Self;
/// Write the cryptonum into the provide slice. If the provided slice
/// is greater than or equal to `n` bits in length, `to_bytes` will
/// write to the first `n` bits. If the slice is less than `n` bits
/// in length, `to_bytes` will write the lower-order bits that fit
/// into the provided slice.
fn to_bytes(&self, bytes: &mut [u8]);
} }
fn associativity_unsigned_add(x: U192, y: U192, z: U192) -> bool {
(U256::from(&x) + (&y + &z)) == ((&x + &y) + U256::from(&z))
}
fn associativity_unsigned_mul(x: U192, y: U192, z: U192) -> bool {
(U384::from(&x) * (&y * &z)) == ((&x * &y) * U384::from(&z))
}
fn identity_signed_add(x: I576) -> bool {
(&x + I576::zero()) == I640::from(&x)
}
fn identity_unsigned_add(x: U576) -> bool {
(&x + U576::zero()) == U640::from(&x)
}
fn identity_unsigned_mul(x: U192) -> bool {
(&x * U192::from(1u64)) == U384::from(&x)
}
fn additive_inverse(x: I576) -> bool {
(&x + x.negate()) == I640::zero()
}
fn distribution(x: U192, y: U192, z: U192) -> bool {
(U256::from(&x) * (&y + &z)) == U512::from((&x * &y) + (&x * &z))
}
}
}

View File

@@ -1,52 +0,0 @@
//! This module includes a large number of signed integer types for very
//! large integers, designed to try to match good performance with a high
//! assurance threshold.
//!
//! The types provided in this module, and the functions available for each
//! of those types, is derived from standard bit widths for RSA, DSA, and
//! Elliptic Curve encryption schemes. If this library does not include a
//! function you would like for another cryptographic scheme, please reach
//! out to the authors; in many cases, the relevant code can be automatically
//! generated.
//!
#[macro_use]
mod add;
#[macro_use]
mod base;
#[macro_use]
mod compare;
#[macro_use]
mod conversion;
#[macro_use]
mod div;
#[macro_use]
mod egcd;
#[macro_use]
mod moddiv;
#[macro_use]
mod modinv;
#[macro_use]
mod mul;
#[macro_use]
mod scale;
#[macro_use]
mod shift;
#[macro_use]
mod subtraction;
use quickcheck::{Arbitrary,Gen};
use std::cmp::{Ord,Ordering,PartialOrd};
use std::fmt;
use std::ops::{Add,AddAssign};
use std::ops::{Div,DivAssign};
use std::ops::{Mul,MulAssign};
use std::ops::{Rem,RemAssign};
use std::ops::{Shl,ShlAssign,Shr,ShrAssign};
use std::ops::{Sub,SubAssign};
use unsigned::*;
pub use self::egcd::EGCD;
pub use self::moddiv::ModDiv;
pub use self::modinv::ModInv;
include!("invoc.rs");

View File

@@ -1,16 +1,18 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::Read;
use std::path::PathBuf;
use std::str::Lines; use std::str::Lines;
pub fn build_test_path(dir: &str, typename: &str) -> String pub fn build_test_path(dir: &str, typename: &str) -> PathBuf
{ {
let mut name = typename.to_string(); let mut res = PathBuf::new();
name.remove(0); res.push(".");
while name.len() < 5 { res.push("testdata");
name.insert(0, '0'); res.push(dir);
} res.push(typename);
format!("testdata/{}/{}.test", dir, name) res.set_extension("test");
res
} }
fn next_value_set(line: &str) -> (String, bool, Vec<u8>) fn next_value_set(line: &str) -> (String, bool, Vec<u8>)
@@ -58,9 +60,10 @@ fn next_test_case(contents: &mut Lines, lines: usize) ->
Some(res) Some(res)
} }
pub fn run_test<F>(fname: String, i: usize, f: F) pub fn run_test<F>(fname: PathBuf, i: usize, f: F)
where F: Fn(HashMap<String,(bool,Vec<u8>)>) where F: Fn(HashMap<String,(bool,Vec<u8>)>)
{ {
println!("fname: {:?}", fname);
let mut file = File::open(fname).unwrap(); let mut file = File::open(fname).unwrap();
let mut contents = String::new(); let mut contents = String::new();
file.read_to_string(&mut contents).unwrap(); file.read_to_string(&mut contents).unwrap();