packages feed

newhope-0.1.0.0: test/Crypto/NewHope/Test/NTT.hs

{-# LANGUAGE Trustworthy #-}
{-|
  Module        : Crypto.NewHope.Test.NTT
  Description   : Testing code for NTT
  Copyright     : © Jeremy Bornstein 2019
  License       : Apache 2.0
  Maintainer    : jeremy@bornstein.org
  Stability     : experimental
  Portability   : portable

-}

module Crypto.NewHope.Test.NTT where

import           Data.Map
import qualified Data.Vector.Unboxed   as VU
import           Data.Word
import           Test.Tasty
import           Test.Tasty.HUnit
import           Test.Tasty.QuickCheck as QC hiding (output)
import           Text.Trifecta

import ConfigFile
import Crypto.NewHope.NTT


-- all the validation data for these tests
configFile512 :: IO Config
configFile512 = fromFile "NTT_512.cfg"

configFile1024 :: IO Config
configFile1024 = fromFile "NTT_1024.cfg"


-- generate (arbitrary :: Gen Word16Vector512)

newtype WrappedVUUnbox a = WrappedVUUnbox (VU.Vector a) deriving Show

instance (Arbitrary a, VU.Unbox a) => Arbitrary (WrappedVUUnbox a) where
    arbitrary = fmap (WrappedVUUnbox . VU.fromList) arbitrary


newtype Word16Vector512 = Word16Vector512 (VU.Vector Word16) deriving (Eq, Show)
newtype Word16Vector1024 = Word16Vector1024 (VU.Vector Word16) deriving (Eq, Show)

instance Arbitrary Word16Vector512 where
  arbitrary = sized $ \_n -> do
      let notN = 512 :: Int
      a <- sequence [ suchThat arbitrary (< 512) | _ <- [1..notN]]
      return $ Word16Vector512 (VU.fromList a)

instance Arbitrary Word16Vector1024 where
  arbitrary = sized $ \_n -> do
      let notN = 1024 :: Int
      a <- sequence [ suchThat arbitrary (< 1024) | _ <- [1..notN]]
      return $ Word16Vector1024 (VU.fromList a)


propRev512 :: Word16Vector512 -> Bool
propRev512 v = v0 == v2 where
  Word16Vector512 v0 = v
  v1 = bitrev v0
  v2 = bitrev v1

propRev1024 :: Word16Vector1024 -> Bool
propRev1024 v = v0 == v2 where
  Word16Vector1024 v0 = v
  v1 = bitrev v0
  v2 = bitrev v1


data BitReverseValidationParams = BitReverseValidationParams { getBTKeySize :: Int
                                                             , getBTInput   :: VU.Vector Word16
                                                             , getBTOutput  :: VU.Vector Word16
                                                             } deriving Show
btFromConfig :: Config -> BitReverseValidationParams
btFromConfig config = BitReverseValidationParams { getBTKeySize = bits
                                          , getBTInput = inputVector
                                          , getBTOutput = outputVector
                                          }
  where
    section = config `sectionNamed` "bitrev"
    bits = read $ section ! "n" :: Int
    Text.Trifecta.Success input = parseString parseListWordIntegral mempty $ section ! "input"
    inputVector = VU.fromList input
    Text.Trifecta.Success output = parseString parseListWordIntegral mempty $ section ! "output"
    outputVector = VU.fromList output


isBitReverseAccurate :: BitReverseValidationParams -> Bool
isBitReverseAccurate config = expectedOutput == liveResult
  where
    expectedOutput = getBTOutput config
    input = getBTInput config
    liveResult = bitrev input


bitreverseCheck :: BitReverseValidationParams -> TestTree
bitreverseCheck params = testCase ("bitreverse (N=" ++ show n ++ ")")
    (assertEqual "it is" True $ isBitReverseAccurate params)
  where
    n = getBTKeySize params


data NTTValidationParams = NTTValidationParams { getKeySize :: Int
                                               , getInput   :: VU.Vector Word16
                                               , getΩ       :: VU.Vector Word16
                                               , getOutput  :: VU.Vector Word16
                                               } deriving Show


nttFromConfig :: Config -> NTTValidationParams
nttFromConfig config = NTTValidationParams { getKeySize = bits
                                           , getInput = inputVector
                                           , getΩ = ωVector
                                           , getOutput = outputVector
                                           }
  where
    section = config `sectionNamed` "ntt"
    bits = read $ findWithDefault "0" "n" section :: Int
    Text.Trifecta.Success input = parseString parseListWordIntegral mempty $ section ! "input"
    inputVector = VU.fromList input
    Text.Trifecta.Success ω = parseString parseListWordIntegral mempty $ section ! "omega"
    ωVector = VU.fromList ω
    Text.Trifecta.Success output = parseString parseListWordIntegral mempty $ section ! "output"
    outputVector = VU.fromList output


isNTTAccurate :: NTTValidationParams -> Bool
isNTTAccurate config = validResult == liveResult
  where
    validResult = getOutput config
    inputVector = getInput config
    ωVector = getΩ config
    liveResult = ntt inputVector ωVector

nttCheck :: NTTValidationParams -> TestTree
nttCheck params = testCase ("ntt (N=" ++ show n ++ ")")
    (assertEqual "it is" True $ isNTTAccurate params)
  where
    n = getKeySize params


-- the table entries should be symmetrical
checkTableEntry :: VU.Vector Word16 -> Integer -> Bool
checkTableEntry table n = let
    value0 = table VU.! fromIntegral n
    value1 = table VU.! fromIntegral value0
    result = value0 == value1 || value1 == fromIntegral n
  in result


-- check the full table. this should really be sufficient instead of the spot check that the other test provides, iirc
validateBitrevTable :: Int -> Bool
validateBitrevTable bits = and $ checkTableEntry (bitrevTable bits) <$> [0..511]

bitrevTable512Ok :: TestTree
bitrevTable512Ok = testCase "bitreverse table (N=512) is legit"
    (assertEqual "it is" True $ validateBitrevTable 512)

bitrevTable1024Ok :: TestTree
bitrevTable1024Ok = testCase "bitreverse table (N=1024) is legit"
    (assertEqual "it is" True $ validateBitrevTable 1024)


tests :: IO TestTree
tests = do
    config512 <- configFile512
    config1024 <- configFile1024

    let btParams512 :: BitReverseValidationParams
        btParams512 = btFromConfig config512

        nttParams512 :: NTTValidationParams
        nttParams512 = nttFromConfig config512

    let btParams1024 :: BitReverseValidationParams
        btParams1024 = btFromConfig config1024

        nttParams1024 :: NTTValidationParams
        nttParams1024 = nttFromConfig config1024

    return $ testGroup "NTT Tests" [ bitreverseCheck btParams512
                                   , bitreverseCheck btParams1024
                                   , bitrevTable512Ok
                                   , bitrevTable1024Ok
                                   , nttCheck nttParams512
                                   , nttCheck nttParams1024
                                   , QC.testProperty "round trip bitreversal (N=512)" propRev512
                                   , QC.testProperty "round trip bitreversal (N=1024)" propRev1024
                                   ]