packages feed

saltine-quickcheck-0.1.0.1: src/Crypto/Saltine/QuickCheck.hs

-- |
-- Module    : Crypto.Saltine.QuickCheck
-- Copyright : Jeremy Groven
-- License   : BSD3
--
-- This module provides newtype/data wrappers that provide Arbitrary instances
-- for Crypto.Saltine types.
module Crypto.Saltine.QuickCheck
( DeterministicBoxPair(..)
, DeterministicBoxNonce(..)
, DeterministicSignPair(..)
, DeterministicSecretKey(..)
, DeterministicSecretNonce(..)
, toBoxPair
, toSignPair
) where

import qualified Crypto.Saltine.Core.Box           as Box
import qualified Crypto.Saltine.Core.SecretBox     as Secret
import qualified Crypto.Saltine.Core.Sign          as Sign
import qualified Crypto.Saltine.Internal.ByteSizes as Sizes
import qualified Data.ByteString                   as ByteString

import Crypto.Saltine.Class      ( IsEncoding(..) )
import Data.ByteString           ( ByteString )
import Data.ByteString.Arbitrary ( fastRandBs )
import Data.ByteString.Unsafe    ( unsafeUseAsCString )
import Data.Maybe                ( fromJust )
import Data.Hex                  ( hex )
import Foreign.C
import Foreign.Ptr
import System.IO.Unsafe          ( unsafePerformIO )
import Test.QuickCheck           ( Arbitrary(..) )

-- |Wrapper around Saltine's Box.Keypair
data DeterministicBoxPair = DBP
  { boxSecret :: !Box.SecretKey
  , boxPublic :: !Box.PublicKey }

-- |Wrapper around Saltine's Box.Nonce
newtype DeterministicBoxNonce = DBN { fromDBN :: Box.Nonce }

-- |Wrapper around Saltine's Sign.Keypair
data DeterministicSignPair = DSP
  { signSecret :: !Sign.SecretKey
  , signPublic :: !Sign.PublicKey }

-- |Wrapper around Saltine's SecretBox.Key
newtype DeterministicSecretKey   = DSK { fromDSK :: Secret.Key }

-- |Wrapper around Saltine's SecretBox.Nonce
newtype DeterministicSecretNonce = DSN { fromDSN :: Secret.Nonce }

-- |Convenience function for getting a Box.Keypair; can be used in a test as
--
-- @
--     (boxSecret, boxPublic) <- toBoxPair \`fmap\` arbitrary
-- @
toBoxPair :: DeterministicBoxPair -> Box.Keypair
toBoxPair (DBP s p) = (s,p)

-- |Convenience function for getting a Sign.Keypair; can be used in a test as
--
-- @
--     (signSecret, signPublic) <- toSignPair \`fmap\` arbitrary
-- @
toSignPair :: DeterministicSignPair -> Sign.Keypair
toSignPair (DSP s p) = (s,p)

seededBoxPair :: ByteString -> DeterministicBoxPair
seededBoxPair = unsafePerformIO . seededBoxPair'

seededBoxPair' :: ByteString -> IO DeterministicBoxPair
seededBoxPair' seed = 
  use_seed seed boxSeedBytes Sizes.boxPK Sizes.boxSK c_crypto_box_seed_keypair DBP

seededSignPair :: ByteString -> DeterministicSignPair
seededSignPair = unsafePerformIO . seededSignPair'

seededSignPair' :: ByteString -> IO DeterministicSignPair
seededSignPair' seed =
  use_seed seed signSeedBytes Sizes.signPK Sizes.signSK c_crypto_sign_seed_keypair DSP

use_seed :: (IsEncoding s, IsEncoding p)
         => ByteString
         -> Int
         -> Int
         -> Int
         -> (Ptr CChar -> Ptr CChar -> Ptr CChar -> IO CInt)
         -> (s -> p -> a)
         -> IO a
use_seed seed slen _ _ _ _ | ByteString.length seed /= slen
  = fail $ "Seed must be " ++ show slen ++ " bytes long"
use_seed seed _ pklen sklen c_fn ctor = do
  let pkbuf = ByteString.replicate pklen 0
      skbuf = ByteString.replicate sklen 0
  _ <- unsafeUseAsCString pkbuf (\pkcptr ->
    unsafeUseAsCString skbuf (\skcptr ->
      unsafeUseAsCString seed (\seedcptr ->
        c_fn pkcptr skcptr seedcptr )))
  let Just pk = decode pkbuf
      Just sk = decode skbuf
  return $ ctor sk pk

instance Arbitrary DeterministicBoxPair where
  arbitrary = seededBoxPair `fmap` fastRandBs boxSeedBytes

instance Arbitrary DeterministicBoxNonce where
  arbitrary = (DBN . fromJust . decode) `fmap` fastRandBs Sizes.boxNonce

instance Arbitrary DeterministicSignPair where
  arbitrary = seededSignPair `fmap` fastRandBs boxSeedBytes

instance Arbitrary DeterministicSecretKey where
  arbitrary = (DSK . fromJust . decode) `fmap` fastRandBs Sizes.secretBoxKey

instance Arbitrary DeterministicSecretNonce where
  arbitrary = (DSN . fromJust . decode) `fmap` fastRandBs Sizes.secretBoxNonce

instance Show DeterministicBoxPair where
  show (DBP sec pub) = str' "DBP" sec pub

instance Show DeterministicBoxNonce where
  show (DBN nonce) = "<DBN " ++ (show $ hex $ encode nonce) ++ ">"

instance Show DeterministicSignPair where
  show (DSP sec pub) = str' "DSP" sec pub

instance Show DeterministicSecretKey where
  show (DSK key) = "<DSK " ++ (take 17 $ show $ hex $ encode key) ++ "\">"

instance Show DeterministicSecretNonce where
  show (DSN nonce) = "<DSN " ++ (show $ hex $ encode nonce) ++ ">"

str' :: (IsEncoding s, IsEncoding p) => String -> s -> p -> String
str' heading secret public = 
    heading ++ "<" ++ (take 17 $ show $ hex $ encode secret) ++ "\" "
                   ++ (show $ hex $ encode public) ++ ">"

foreign import ccall "crypto_box_seed_keypair"
  c_crypto_box_seed_keypair :: Ptr CChar -> Ptr CChar -> Ptr CChar -> IO CInt

foreign import ccall "crypto_box_seedbytes"
  c_crypto_box_seedbytes :: CSize

foreign import ccall "crypto_sign_seed_keypair"
  c_crypto_sign_seed_keypair :: Ptr CChar -> Ptr CChar -> Ptr CChar -> IO CInt

foreign import ccall "crypto_sign_seedbytes"
  c_crypto_sign_seedbytes :: CSize

boxSeedBytes :: Int
boxSeedBytes = fromIntegral c_crypto_box_seedbytes

signSeedBytes :: Int
signSeedBytes = fromIntegral c_crypto_sign_seedbytes