File: Test.hs

package info (click to toggle)
haskell-crypto-random 0.0.9-10
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 96 kB
  • sloc: haskell: 401; ansic: 52; makefile: 2
file content (90 lines) | stat: -rw-r--r-- 3,244 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
-- |
-- Module      : Crypto.Random.Test
-- License     : BSD-style
-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
-- Stability   : experimental
-- Portability : Good
--
-- Provide way to test usual simple statisticals test for randomness
--
{-# LANGUAGE GADTs #-}

module Crypto.Random.Test
    ( RandomTestState
    , RandomTestResult(..)
    , randomTestInitialize
    , randomTestAppend
    , randomTestFinalize
    ) where

import Data.Word
import Data.Int (Int64)
import qualified Data.ByteString.Lazy as L
import Control.Applicative
import Data.List (foldl')

import qualified Data.Vector.Mutable as M
import qualified Data.Vector as V

-- | Randomness various result relative to random bytes
data RandomTestResult = RandomTestResult
    { res_totalChars         :: Word64 -- ^ Total number of characters
    , res_entropy            :: Double -- ^ Entropy per byte
    , res_chi_square         :: Double -- ^ Chi Square
    , res_mean               :: Double -- ^ Arithmetic Mean
    , res_compressionPercent :: Double -- ^ Theorical Compression percent
    , res_probs              :: [Double] -- ^ Probability of every bucket
    } deriving (Show,Eq)

-- | Mutable random test State
newtype RandomTestState = RandomTestState (M.IOVector Word64)

-- | Initialize new state to run tests
randomTestInitialize :: IO RandomTestState
randomTestInitialize = RandomTestState <$> M.replicate 256 0

-- | Append random data to the test state
randomTestAppend :: RandomTestState -> L.ByteString -> IO ()
randomTestAppend (RandomTestState buckets) = loop
  where loop bs
            | L.null bs = return ()
            | otherwise = do
                let (b1,b2) = L.splitAt monteN bs
                mapM_ (addVec 1 . fromIntegral) $ L.unpack b1
                loop b2
        addVec :: Word64 -> Int -> IO ()
        addVec a i = M.read buckets i >>= \d -> M.write buckets i $! d+a

-- | Finalize random test state into some result
randomTestFinalize :: RandomTestState -> IO RandomTestResult
randomTestFinalize (RandomTestState buckets) = (calculate . V.toList) `fmap` V.freeze buckets

monteN :: Int64
monteN = 6

calculate :: [Word64] -> RandomTestResult
calculate buckets = RandomTestResult
    { res_totalChars = totalChars
    , res_entropy    = entropy
    , res_chi_square = chisq
    , res_mean       = fromIntegral datasum / fromIntegral totalChars
    , res_compressionPercent = 100.0 * (8 - entropy) / 8.0
    , res_probs      = probs
    }
  where totalChars = sum buckets
        probs = map (\v -> fromIntegral v / fromIntegral totalChars :: Double) buckets
        entropy = foldl' accEnt 0.0 probs
        cexp    = fromIntegral totalChars / 256.0 :: Double
        (datasum, chisq) = foldl' accMeanChi (0, 0.0) [0..255]
        --chip' = abs (sqrt (2.0 * chisq) - sqrt (2.0 * 255.0 - 1.0))

        accEnt ent pr
            | pr > 0.0  = ent + (pr * xlog (1 / pr))
            | otherwise = ent
        xlog v = logBase 10 v * (log 10 / log 2)

        accMeanChi :: (Word64, Double) -> Int -> (Word64, Double)
        accMeanChi (dataSum, chiSq) i =
            let ccount = buckets !! i
                a      = fromIntegral ccount - cexp
             in (dataSum + fromIntegral i * ccount, chiSq + (a * a / cexp))