packages feed

crypto-rng-0.2.0.0: src/Crypto/RNG.hs

{-# LANGUAGE BangPatterns               #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase                 #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE UndecidableInstances       #-}

-- | Support for generation of cryptographically secure random
-- numbers, based on the DRBG package.
--
-- This is a convenience layer on top of DRBG, which allows you to
-- pull random values by means of the method 'random', while keeping
-- the state of the random number generator (RNG) inside a monad.  The
-- state is protected by an MVar, which means that concurrent
-- generation of random values from several threads works straight out
-- of the box.
--
-- The access to the RNG state is captured by a class.  By making
-- instances of this class, client code can enjoy RNG generation from
-- their own monads.
module Crypto.RNG
  ( -- * CryptoRNG class
    module Crypto.RNG.Class
    -- * Generation of strings and numbers
  , CryptoRNGState
  , newCryptoRNGState
  , newCryptoRNGStateSized
  , unsafeCryptoRNGState
  , randomBytesIO
  , randomIO
  , randomRIO
    -- * Monad transformer for carrying rng state
  , CryptoRNGT
  , mapCryptoRNGT
  , runCryptoRNGT
  , withCryptoRNGState
  ) where

import Control.Applicative
import Control.Concurrent
import Control.Monad.Base
import Control.Monad.Catch
import Control.Monad.Cont
import Control.Monad.Except
import Control.Monad.Reader
import Control.Monad.Trans.Control
import Crypto.Random
import Crypto.Random.DRBG
import Data.Bits
import Data.ByteString (ByteString)
import Data.Either
import Data.Hashable
import Data.Primitive.SmallArray
import qualified Data.ByteString as BS
import qualified System.Random as R

import Crypto.RNG.Class

-- | The random number generator state.
newtype CryptoRNGState = CryptoRNGState (SmallArray (MVar RNG))

-- | The random number generator.
newtype RNG = RNG (GenBuffered (GenAutoReseed HashDRBG HashDRBG))

instance R.RandomGen RNG where
  split = error "split"
  genWord32 (RNG g) = case genBytes 4 g of
    Left err       -> error $ "genBytes failed: " ++ show err
    Right (bs, g') -> (mkWord bs, RNG g')
  genWord64 (RNG g) = case genBytes 8 g of
    Left err       -> error $ "genBytes failed: " ++ show err
    Right (bs, g') -> (mkWord bs, RNG g')

mkWord :: (Bits a, Integral a) => ByteString -> a
mkWord bs = BS.foldl' (\acc w -> shiftL acc 8 .|. fromIntegral w) 0 bs

-- | Work with one of the RNGs from the pool.
withRNG :: CryptoRNGState -> (RNG -> (a, RNG)) -> IO a
withRNG (CryptoRNGState pool) f = liftIO $ do
  tid <- hash <$> myThreadId
  let mrng = pool `indexSmallArray` (tid `rem` sizeofSmallArray pool)
  modifyMVar mrng $ \rng -> do
    (a, newRng) <- pure $ f rng
    newRng `seq` pure (newRng, a)

----------------------------------------

-- | Create a new 'CryptoRNGState', based on system entropy.
newCryptoRNGState :: MonadIO m => m CryptoRNGState
newCryptoRNGState = newCryptoRNGStateSized =<< liftIO getNumCapabilities

-- | Create a new 'CryptoRNGState', based on system entropy with the pool of a
-- specific size.
newCryptoRNGStateSized
  :: MonadIO m
  => Int -- ^ Pool size.
  -> m CryptoRNGState
newCryptoRNGStateSized n = liftIO $ do
  pool <- replicateM n $ newMVar . RNG =<< newGenIO
  pure . CryptoRNGState $ smallArrayFromListN n pool

-- | Create a new 'CryptoRNGState', based on a bytestring seed.
-- Should only be used for testing.
unsafeCryptoRNGState
  :: MonadIO m
  => [ByteString]
  -- ^ Seeds for each generator from the pool.
  -> m CryptoRNGState
unsafeCryptoRNGState ss = liftIO $ do
  case partitionEithers $ map newGen ss of
    ([], gens) -> do
      pool <- mapM (newMVar . RNG) gens
      pure . CryptoRNGState $ smallArrayFromList pool
    (errs, _)  -> error $ show errs

-- | Generate given number of cryptographically secure random bytes.
randomBytesIO :: ByteLength -- ^ number of bytes to generate
              -> CryptoRNGState
              -> IO ByteString
randomBytesIO n pool = withRNG pool $ \(RNG g) ->
  case genBytes n g of
    Left err       -> error $ "genBytes failed: " ++ show err
    Right (bs, g') -> (bs, RNG g')

randomIO :: R.Uniform a => CryptoRNGState -> IO a
randomIO pool = withRNG pool $ \g -> R.uniform g

randomRIO :: R.UniformRange a => (a, a) -> CryptoRNGState -> IO a
randomRIO bounds pool = withRNG pool $ \g -> R.uniformR bounds g

type InnerCryptoRNGT = ReaderT CryptoRNGState

-- | Monad transformer with RNG state.
newtype CryptoRNGT m a = CryptoRNGT { unCryptoRNGT :: InnerCryptoRNGT m a }
  deriving ( Alternative, Applicative, Functor, Monad
           , MonadBase b, MonadCatch, MonadError e, MonadIO, MonadMask, MonadPlus
           , MonadThrow, MonadTrans, MonadFail )

mapCryptoRNGT :: (m a -> n b) -> CryptoRNGT m a -> CryptoRNGT n b
mapCryptoRNGT f m = withCryptoRNGState $ \s -> f (runCryptoRNGT s m)

runCryptoRNGT :: CryptoRNGState -> CryptoRNGT m a -> m a
runCryptoRNGT pool m = runReaderT (unCryptoRNGT m) pool

withCryptoRNGState :: (CryptoRNGState -> m a) -> CryptoRNGT m a
withCryptoRNGState = CryptoRNGT . ReaderT

instance MonadTransControl CryptoRNGT where
  type StT CryptoRNGT a = StT InnerCryptoRNGT a
  liftWith = defaultLiftWith CryptoRNGT unCryptoRNGT
  restoreT = defaultRestoreT CryptoRNGT

instance MonadBaseControl b m => MonadBaseControl b (CryptoRNGT m) where
  type StM (CryptoRNGT m) a = ComposeSt CryptoRNGT m a
  liftBaseWith = defaultLiftBaseWith
  restoreM     = defaultRestoreM

instance {-# OVERLAPPABLE #-} MonadIO m => CryptoRNG (CryptoRNGT m) where
  randomBytes n  = CryptoRNGT ask >>= liftIO . randomBytesIO n
  random         = CryptoRNGT ask >>= liftIO . randomIO
  randomR bounds = CryptoRNGT ask >>= liftIO . randomRIO bounds