{-# LANGUAGE FlexibleInstances, TypeSynonymInstances, FlexibleContexts , GeneralizedNewtypeDeriving, MultiParamTypeClasses, UndecidableInstances #-} {-| Maintainer: Thomas.DuBuisson@gmail.com Stability: beta Portability: portable Much like the "MonadRandom" package ("Control.Monad.Random"), this module provides plumbing for the CryptoRandomGen generators. -} module Control.Monad.CryptoRandom ( CRandom(..) , CRandomR(..) , MonadCRandom(..) , MonadCRandomR(..) , ContainsGenError(..) , CRandT(..) , CRand , runCRandT , evalCRandT , runCRand , evalCRand , newGenCRand , liftCRand , liftCRandT , module Crypto.Random ) where import Control.Applicative import Control.Arrow (right, left, first) import Control.Monad (liftM) import qualified Control.Monad.Catch as C (MonadThrow(..), MonadCatch(..)) import Control.Monad.Cont import Control.Monad.Trans.Except import Control.Monad.Error.Class import Control.Monad.IO.Class import Control.Monad.Identity import Control.Monad.Reader import Control.Monad.RWS.Lazy as Lazy import Control.Monad.RWS.Strict as Strict import Control.Monad.State.Lazy as Lazy import Control.Monad.State.Strict as Strict import Control.Monad.Writer.Class import Control.Monad.Writer.Lazy as Lazy import Control.Monad.Writer.Strict as Strict import Crypto.Random (CryptoRandomGen(..), GenError(..)) import Data.Bits (xor, setBit, shiftR, shiftL, (.&.)) import Data.Int import Data.List (foldl') import Data.Word import Data.Proxy import Data.Tagged import qualified Data.ByteString as B -- |@MonadCRandom m@ represents a monad that can produce -- random values (or fail with a 'GenError'). It is suggested -- you use the 'CRandT' transformer in your monad stack. class (ContainsGenError e, MonadError e m) => MonadCRandom e m where getCRandom :: CRandom a => m a getBytes :: Int -> m B.ByteString getBytesWithEntropy :: Int -> B.ByteString -> m B.ByteString doReseed :: B.ByteString -> m () instance MonadCRandom e m => MonadCRandom e (Lazy.StateT s m) where getCRandom = lift getCRandom {-# INLINE getCRandom #-} getBytes = lift . getBytes {-# INLINE getBytes #-} getBytesWithEntropy i = lift . getBytesWithEntropy i {-# INLINE getBytesWithEntropy #-} doReseed = lift . doReseed {-# INLINE doReseed #-} instance MonadCRandom e m => MonadCRandom e (Strict.StateT s m) where getCRandom = lift getCRandom {-# INLINE getCRandom #-} getBytes = lift . getBytes {-# INLINE getBytes #-} getBytesWithEntropy i = lift . getBytesWithEntropy i {-# INLINE getBytesWithEntropy #-} doReseed = lift . doReseed {-# INLINE doReseed #-} instance (Monoid w, MonadCRandom e m) => MonadCRandom e (Strict.WriterT w m) where getCRandom = lift getCRandom {-# INLINE getCRandom #-} getBytes = lift . getBytes {-# INLINE getBytes #-} getBytesWithEntropy i = lift . getBytesWithEntropy i {-# INLINE getBytesWithEntropy #-} doReseed = lift . doReseed {-# INLINE doReseed #-} instance (Monoid w, MonadCRandom e m) => MonadCRandom e (Lazy.WriterT w m) where getCRandom = lift getCRandom {-# INLINE getCRandom #-} getBytes = lift . getBytes {-# INLINE getBytes #-} getBytesWithEntropy i = lift . getBytesWithEntropy i {-# INLINE getBytesWithEntropy #-} doReseed = lift . doReseed {-# INLINE doReseed #-} instance MonadCRandom e m => MonadCRandom e (ReaderT r m) where getCRandom = lift getCRandom {-# INLINE getCRandom #-} getBytes = lift . getBytes {-# INLINE getBytes #-} getBytesWithEntropy i = lift . getBytesWithEntropy i {-# INLINE getBytesWithEntropy #-} doReseed = lift . doReseed {-# INLINE doReseed #-} instance (Monoid w, MonadCRandom e m) => MonadCRandom e (Strict.RWST r w s m) where getCRandom = lift getCRandom {-# INLINE getCRandom #-} getBytes = lift . getBytes {-# INLINE getBytes #-} getBytesWithEntropy i = lift . getBytesWithEntropy i {-# INLINE getBytesWithEntropy #-} doReseed = lift . doReseed {-# INLINE doReseed #-} instance (Monoid w, MonadCRandom e m) => MonadCRandom e (Lazy.RWST r w s m) where getCRandom = lift getCRandom {-# INLINE getCRandom #-} getBytes = lift . getBytes {-# INLINE getBytes #-} getBytesWithEntropy i = lift . getBytesWithEntropy i {-# INLINE getBytesWithEntropy #-} doReseed = lift . doReseed {-# INLINE doReseed #-} newGenCRand :: (CryptoRandomGen g, MonadCRandom GenError m, Functor m) => m g newGenCRand = go 0 where go 1000 = throwError (GenErrorOther "The generator instance requested by newGenCRand never instantiates.") go i = do let p = Proxy getTypedGen :: (Functor m, CryptoRandomGen g, MonadCRandom GenError m) => Proxy g -> m (Either GenError g) getTypedGen pr = fmap newGen (getBytes $ proxy genSeedLength pr) res <- getTypedGen p case res of Left _ -> go (i+1) Right g -> return (g `asProxyTypeOf` p) class (ContainsGenError e, MonadError e m) => MonadCRandomR e m where getCRandomR :: CRandomR a => (a,a) -> m a class ContainsGenError e where toGenError :: e -> Maybe GenError fromGenError :: GenError -> e instance ContainsGenError GenError where toGenError = Just fromGenError = id -- |@CRandom a@ is much like the 'Random' class from the "System.Random" module in the "random" package. -- The main difference is CRandom builds on "crypto-api"'s 'CryptoRandomGen', so it allows -- explicit failure. -- -- @crandomR (low,high) g@ as typically instantiated will generate a value between -- [low, high] inclusively, swapping the pair if high < low. -- -- Provided instances for @crandom g@ generates randoms between the bounds and between +/- 2^256 -- for Integer. -- -- The 'crandomR' function has degraded (theoretically unbounded, probabilistically decent) performance -- the closer your range size (high - low) is to 2^n (from the top). class CRandom a where crandom :: (CryptoRandomGen g) => g -> Either GenError (a, g) crandoms :: (CryptoRandomGen g) => g -> [a] crandoms g = case crandom g of Left _ -> [] Right (a,g') -> a : crandoms g' class CRandomR a where crandomR :: (CryptoRandomGen g) => (a, a) -> g -> Either GenError (a, g) crandomRs :: (CryptoRandomGen g) => (a, a) -> g -> [a] crandomRs r g = case crandomR r g of Left _ -> [] Right (a,g') -> a : crandomRs r g' instance CRandomR Integer where crandomR = crandomR_Num {-# INLINE crandomR #-} instance CRandom Int where crandom = crandomR (minBound, maxBound) {-# INLINE crandom #-} instance CRandomR Int where crandomR = crandomR_Num {-# INLINE crandomR #-} instance CRandom Word8 where crandom = crandomR (minBound, maxBound) {-# INLINE crandom #-} instance CRandomR Word8 where crandomR = crandomR_Num {-# INLINE crandomR #-} instance CRandom Word16 where crandom = crandomR (minBound, maxBound) {-# INLINE crandom #-} instance CRandomR Word16 where crandomR = crandomR_Num {-# INLINE crandomR #-} instance CRandom Word32 where crandom = crandomR (minBound, maxBound) {-# INLINE crandom #-} instance CRandomR Word32 where crandomR = crandomR_Num {-# INLINE crandomR #-} instance CRandom Word64 where crandom = crandomR (minBound, maxBound) {-# INLINE crandom #-} instance CRandomR Word64 where crandomR = crandomR_Num {-# INLINE crandomR #-} instance CRandom Int8 where crandom = crandomR (minBound, maxBound) {-# INLINE crandom #-} instance CRandomR Int8 where crandomR = crandomR_Num {-# INLINE crandomR #-} instance CRandom Int16 where crandom = crandomR (minBound, maxBound) {-# INLINE crandom #-} instance CRandomR Int16 where crandomR = crandomR_Num {-# INLINE crandomR #-} instance CRandom Int32 where crandom = crandomR (minBound, maxBound) {-# INLINE crandom #-} instance CRandomR Int32 where crandomR = crandomR_Num {-# INLINE crandomR #-} instance CRandom Int64 where crandom = crandomR (minBound, maxBound) {-# INLINE crandom #-} instance CRandomR Int64 where crandomR = crandomR_Num {-# INLINE crandomR #-} instance CRandom Bool where crandom g = first (toEnum . fromIntegral) `fmap` crandomR (0::Word8,1) g crandomR_Num :: (Integral a, CryptoRandomGen g) => (a,a) -> g -> Either GenError (a,g) crandomR_Num (low, high) g | high < low = crandomR_Num (high,low) g | high == low = Right (high, g) | otherwise = go g where mask = foldl' setBit 0 [0 .. fromIntegral nrBits - 1] nrBits = base2Log range range :: Integer range = (fromIntegral high) - (fromIntegral low) + 1 nrBytes = (nrBits + 7) `div` 8 go gen = let offset = genBytes (fromIntegral nrBytes) gen in case offset of Left err -> Left err Right (bs, g') -> let res = fromIntegral low + (bs2i bs .&. mask) in if res > fromIntegral high then go g' else Right (fromIntegral res, g') {-# INLINE crandomR_Num #-} wrap :: (Monad m, ContainsGenError e) => (g -> Either GenError (a,g)) -> CRandT g e m a wrap f = CRandT $ do g <- get case f g of Right (a,g') -> put g' >> return a Left x -> throwError (fromGenError x) {-# INLINE wrap #-} liftCRand :: (g -> Either e (a, g)) -> CRand g e a liftCRand f = CRandT $ Lazy.StateT $ (\g -> ExceptT $ Identity $ f g) {-# INLINE liftCRand #-} liftCRandT :: (Monad m) => (g -> Either e (a, g)) -> CRandT g e m a liftCRandT f = CRandT $ Lazy.StateT $ (\g -> ExceptT $ return $ f g) {-# INLINE liftCRandT #-} -- |CRandT is the transformer suggested for MonadCRandom. newtype CRandT g e m a = CRandT { unCRandT :: Lazy.StateT g (ExceptT e m) a } deriving (Functor, Applicative, Monad, MonadIO, MonadError e, MonadFix) instance MonadTrans (CRandT g e) where lift = CRandT . lift . lift {-# INLINE lift #-} instance (MonadState s m) => MonadState s (CRandT g e m) where get = lift get {-# INLINE get #-} put = lift . put {-# INLINE put #-} instance (MonadReader r m) => MonadReader r (CRandT g e m) where ask = lift ask {-# INLINE ask #-} local f = CRandT . local f . unCRandT {-# INLINE local #-} instance (MonadWriter w m) => MonadWriter w (CRandT g e m) where tell = lift . tell {-# INLINE tell #-} listen = CRandT . listen . unCRandT {-# INLINE listen #-} pass = CRandT . pass . unCRandT {-# INLINE pass #-} instance (MonadCont m) => MonadCont (CRandT g e m) where callCC f = CRandT $ callCC $ \amb -> unCRandT $ f (CRandT . amb) {-# INLINE callCC #-} -- | Throws exceptions into the base monad. -- -- @since 0.7.1 instance C.MonadThrow m => C.MonadThrow (CRandT g e m) where throwM = CRandT . C.throwM -- | Catches exceptions from the base monad. -- -- @since 0.7.1 instance C.MonadCatch m => C.MonadCatch (CRandT g e m) where catch (CRandT m) f = CRandT $ C.catch m (unCRandT . f) -- |Simple users of generators can use CRand for -- quick and easy generation of randoms. See -- below for a simple use of 'newGenIO' (from "crypto-api"), -- 'getCRandom', 'getBytes', and 'runCRandom'. -- -- @ -- getRandPair = do -- int <- getCRandom -- bytes <- getBytes 100 -- return (int, bytes) -- -- func = do -- g <- newGenIO -- case runCRand getRandPair g of -- Right ((int,bytes), g') -> useRandomVals (int,bytes) -- Left x -> handleGenError x -- @ type CRand g e = CRandT g e Identity runCRandT :: ContainsGenError e => CRandT g e m a -> g -> m (Either e (a,g)) runCRandT m g = runExceptT . flip Lazy.runStateT g . unCRandT $ m {-# INLINE runCRandT #-} evalCRandT :: (ContainsGenError e, Monad m) => CRandT g e m a -> g -> m (Either e a) evalCRandT m g = liftM (right fst) (runCRandT m g) {-# INLINE evalCRandT #-} runCRand :: (ContainsGenError e) => CRand g e a -> g -> Either e (a, g) runCRand m = runIdentity . runCRandT m {-# INLINE runCRand #-} evalCRand :: CRand g GenError a -> g -> Either GenError a evalCRand m = runIdentity . evalCRandT m {-# INLINE evalCRand #-} instance (ContainsGenError e, Monad m, CryptoRandomGen g) => MonadCRandom e (CRandT g e m) where getCRandom = wrap crandom {-# INLINE getCRandom #-} getBytes i = wrap (genBytes i) {-# INLINE getBytes #-} getBytesWithEntropy i e = wrap (genBytesWithEntropy i e) {-# INLINE getBytesWithEntropy #-} doReseed bs = CRandT $ do get >>= \g -> case reseed bs g of Right g' -> put g' Left x -> throwError (fromGenError x) {-# INLINE doReseed #-} instance (ContainsGenError e, Monad m, CryptoRandomGen g) => MonadCRandomR e (CRandT g e m) where getCRandomR = wrap . crandomR {-# INLINE getCRandomR #-} instance MonadCRandomR e m => MonadCRandomR e (Lazy.StateT s m) where getCRandomR = lift . getCRandomR {-# INLINE getCRandomR #-} instance MonadCRandomR e m => MonadCRandomR e (Strict.StateT s m) where getCRandomR = lift . getCRandomR {-# INLINE getCRandomR #-} instance (MonadCRandomR e m, Monoid w) => MonadCRandomR e (Lazy.WriterT w m) where getCRandomR = lift . getCRandomR {-# INLINE getCRandomR #-} instance (MonadCRandomR e m, Monoid w) => MonadCRandomR e (Strict.WriterT w m) where getCRandomR = lift . getCRandomR {-# INLINE getCRandomR #-} instance MonadCRandomR e m => MonadCRandomR e (ReaderT r m) where getCRandomR = lift . getCRandomR {-# INLINE getCRandomR #-} instance (MonadCRandomR e m, Monoid w) => MonadCRandomR e (Lazy.RWST r w s m) where getCRandomR = lift . getCRandomR {-# INLINE getCRandomR #-} instance (MonadCRandomR e m, Monoid w) => MonadCRandomR e (Strict.RWST r w s m) where getCRandomR = lift . getCRandomR {-# INLINE getCRandomR #-} base2Log :: Integer -> Integer base2Log i | i >= setBit 0 64 = 64 + base2Log (i `shiftR` 64) | i >= setBit 0 32 = 32 + base2Log (i `shiftR` 32) | i >= setBit 0 16 = 16 + base2Log (i `shiftR` 16) | i >= setBit 0 8 = 8 + base2Log (i `shiftR` 8) | i >= setBit 0 0 = 1 + base2Log (i `shiftR` 1) | otherwise = 0 bs2i :: B.ByteString -> Integer bs2i bs = B.foldl' (\i b -> (i `shiftL` 8) + fromIntegral b) 0 bs {-# INLINE bs2i #-}