{-# LANGUAGE OverloadedStrings #-} module Network.Haskoin.Crypto.Mnemonic.Tests (tests) where import Test.QuickCheck (Arbitrary, Property, arbitrary, choose, (==>)) import Test.Framework (Test, testGroup) import Test.Framework.Providers.QuickCheck2 (testProperty) import Data.Bits ((.&.), shiftR) import Data.Binary (Binary) import Data.Word (Word32, Word64) import qualified Data.ByteString as BS ( ByteString , empty , append , concat , length , last ) import qualified Data.ByteString.Char8 as C (words) import Network.Haskoin.Test import Network.Haskoin.Crypto import Network.Haskoin.Util import Network.Haskoin.Internals (fromMnemonic, getBits) tests :: [Test] tests = [ testGroup "Encode mnemonic" [ testProperty "128-bit entropy -> 12 words" toMnemonic128 , testProperty "160-bit entropy -> 18 words" toMnemonic160 , testProperty "256-bit entropy -> 24 words" toMnemonic256 , testProperty "512-bit entropy -> 48 words" toMnemonic512 , testProperty "n-bit entropy -> m words" toMnemonicVar ] , testGroup "Encode/Decode Mnemonic" [ testProperty "128-bit entropy" fromToMnemonic128 , testProperty "160-bit entropy" fromToMnemonic160 , testProperty "256-bit entropy" fromToMnemonic256 , testProperty "512-bit entropy" fromToMnemonic512 , testProperty "n-bit entropy" fromToMnemonicVar ] , testGroup "Mnemonic to seed" [ testProperty "128-bit entropy" mnemonicToSeed128 , testProperty "160-bit entropy" mnemonicToSeed160 , testProperty "256-bit entropy" mnemonicToSeed256 , testProperty "512-bit entropy" mnemonicToSeed512 , testProperty "n-bit entropy" mnemonicToSeedVar ] , testGroup "Get bits from ByteString" [ testProperty "Byte count" getBitsByteCount , testProperty "End bits" getBitsEndBits ] ] binWordsToBS :: Binary a => [a] -> BS.ByteString binWordsToBS = foldr f BS.empty where f b a = a `BS.append` encode' b {- Encode mnemonic -} toMnemonic128 :: (Word64, Word64) -> Bool toMnemonic128 (a, b) = l == 12 where bs = encode' a `BS.append` encode' b l = length . C.words . fromRight $ toMnemonic bs toMnemonic160 :: (Word32, Word64, Word64) -> Bool toMnemonic160 (a, b, c) = l == 15 where bs = BS.concat [encode' a, encode' b, encode' c] l = length . C.words . fromRight $ toMnemonic bs toMnemonic256 :: (Word64, Word64, Word64, Word64) -> Bool toMnemonic256 (a, b, c, d) = l == 24 where bs = BS.concat [encode' a, encode' b, encode' c, encode' d] l = length . C.words . fromRight $ toMnemonic bs toMnemonic512 :: ((Word64, Word64, Word64, Word64), (Word64, Word64, Word64, Word64)) -> Bool toMnemonic512 ((a, b, c, d), (e, f, g, h)) = l == 48 where bs = BS.concat [ encode' a, encode' b, encode' c, encode' d , encode' e, encode' f, encode' g, encode' h ] l = length . C.words . fromRight $ toMnemonic bs toMnemonicVar :: [Word32] -> Property toMnemonicVar ls = not (length ls > 8) ==> l == wc where bs = binWordsToBS ls bl = BS.length bs cb = bl `div` 4 wc = (cb + bl * 8) `div` 11 l = length . C.words . fromRight $ toMnemonic bs {- Encode/Decode -} fromToMnemonic128 :: (Word64, Word64) -> Bool fromToMnemonic128 (a, b) = bs == bs' where bs = encode' a `BS.append` encode' b bs' = fromRight (fromMnemonic =<< toMnemonic bs) fromToMnemonic160 :: (Word32, Word64, Word64) -> Bool fromToMnemonic160 (a, b, c) = bs == bs' where bs = BS.concat [encode' a, encode' b, encode' c] bs' = fromRight (fromMnemonic =<< toMnemonic bs) fromToMnemonic256 :: (Word64, Word64, Word64, Word64) -> Bool fromToMnemonic256 (a, b, c, d) = bs == bs' where bs = BS.concat [encode' a, encode' b, encode' c, encode' d] bs' = fromRight (fromMnemonic =<< toMnemonic bs) fromToMnemonic512 :: ((Word64, Word64, Word64, Word64), (Word64, Word64, Word64, Word64)) -> Bool fromToMnemonic512 ((a, b, c, d), (e, f, g, h)) = bs == bs' where bs = BS.concat [ encode' a, encode' b, encode' c, encode' d , encode' e, encode' f, encode' g, encode' h ] bs' = fromRight (fromMnemonic =<< toMnemonic bs) fromToMnemonicVar :: [Word32] -> Property fromToMnemonicVar ls = not (length ls > 8) ==> bs == bs' where bs = binWordsToBS ls bs' = fromRight (fromMnemonic =<< toMnemonic bs) {- Mnemonic to seed -} mnemonicToSeed128 :: (Word64, Word64) -> Bool mnemonicToSeed128 (a, b) = l == 64 where bs = encode' a `BS.append` encode' b seed = fromRight (mnemonicToSeed "" =<< toMnemonic bs) l = BS.length seed mnemonicToSeed160 :: (Word32, Word64, Word64) -> Bool mnemonicToSeed160 (a, b, c) = l == 64 where bs = BS.concat [encode' a, encode' b, encode' c] seed = fromRight (mnemonicToSeed "" =<< toMnemonic bs) l = BS.length seed mnemonicToSeed256 :: (Word64, Word64, Word64, Word64) -> Bool mnemonicToSeed256 (a, b, c, d) = l == 64 where bs = BS.concat [encode' a, encode' b, encode' c, encode' d] seed = fromRight (mnemonicToSeed "" =<< toMnemonic bs) l = BS.length seed mnemonicToSeed512 :: ((Word64, Word64, Word64, Word64), (Word64, Word64, Word64, Word64)) -> Bool mnemonicToSeed512 ((a, b, c, d), (e, f, g, h)) = l == 64 where bs = BS.concat [ encode' a, encode' b, encode' c, encode' d , encode' e, encode' f, encode' g, encode' h ] seed = fromRight (mnemonicToSeed "" =<< toMnemonic bs) l = BS.length seed mnemonicToSeedVar :: [Word32] -> Property mnemonicToSeedVar ls = not (length ls > 16) ==> l == 64 where bs = binWordsToBS ls seed = fromRight (mnemonicToSeed "" =<< toMnemonic bs) l = BS.length seed {- Get bits from ByteString -} data ByteCountGen = ByteCountGen BS.ByteString Int deriving Show instance Arbitrary ByteCountGen where arbitrary = do ArbitraryByteString bs <- arbitrary i <- choose (0, BS.length bs * 8) return $ ByteCountGen bs i getBitsByteCount :: ByteCountGen -> Bool getBitsByteCount (ByteCountGen bs i) = BS.length bits == l where (q, r) = i `quotRem` 8 bits = getBits i bs l = if r == 0 then q else q + 1 getBitsEndBits :: ByteCountGen -> Bool getBitsEndBits (ByteCountGen bs i) = mask where r = i `mod` 8 bits = getBits i bs mask = if r == 0 then True else BS.last bits .&. (0xff `shiftR` r) == 0x00