packages feed

crypto-rng 0.1.2.0 → 0.2.0.0

raw patch · 5 files changed

+122/−111 lines, 5 filesdep +hashabledep +primitivedep +randomdep ~basedep ~bytestringPVP ok

version bump matches the API change (PVP)

Dependencies added: hashable, primitive, random

Dependency ranges changed: base, bytestring

API changes (from Hackage documentation)

- Crypto.RNG: boundedIntegralRandom :: forall m a. (CryptoRNG m, Integral a, Bounded a) => m a
- Crypto.RNG: class Random a
- Crypto.RNG: instance Crypto.RNG.Random GHC.Int.Int16
- Crypto.RNG: instance Crypto.RNG.Random GHC.Int.Int32
- Crypto.RNG: instance Crypto.RNG.Random GHC.Int.Int64
- Crypto.RNG: instance Crypto.RNG.Random GHC.Types.Int
- Crypto.RNG: instance Crypto.RNG.Random GHC.Types.Word
- Crypto.RNG: instance Crypto.RNG.Random GHC.Word.Word16
- Crypto.RNG: instance Crypto.RNG.Random GHC.Word.Word32
- Crypto.RNG: instance Crypto.RNG.Random GHC.Word.Word64
- Crypto.RNG: instance Crypto.RNG.Random GHC.Word.Word8
- Crypto.RNG: random :: (Random a, CryptoRNG m) => m a
- Crypto.RNG: randomR :: (CryptoRNG m, Integral a) => (a, a) -> m a
+ Crypto.RNG: instance System.Random.Internal.RandomGen Crypto.RNG.RNG
+ Crypto.RNG: newCryptoRNGStateSized :: MonadIO m => Int -> m CryptoRNGState
+ Crypto.RNG: randomIO :: Uniform a => CryptoRNGState -> IO a
+ Crypto.RNG: randomRIO :: UniformRange a => (a, a) -> CryptoRNGState -> IO a
+ Crypto.RNG.Class: random :: (CryptoRNG m, Uniform a) => m a
+ Crypto.RNG.Class: randomR :: (CryptoRNG m, UniformRange a) => (a, a) -> m a
- Crypto.RNG: unsafeCryptoRNGState :: MonadIO m => ByteString -> m CryptoRNGState
+ Crypto.RNG: unsafeCryptoRNGState :: MonadIO m => [ByteString] -> m CryptoRNGState

Files

ChangeLog.md view
@@ -1,5 +1,14 @@ # Revision history for crypto-rng +## 0.2.0.0  -- 2022-02-16++* Drop support for GHC < 8.8+* Fix a space leak in randomBytesIO.+* Use a buffered generator.+* Remove modulo bias from randomRIO.+* Improve performance of randomString.+* Add support for a pool of generators for less contention.+ ## 0.1.2.0  -- 2020-05-05  * GHC-8.8 support (MonadFail) and ghc 8.10.1 support.@@ -7,7 +16,6 @@ ## 0.1.1.0  -- 2019-10-08  * Added a 'MonadError' instance for 'CryptoRNGT'.-  ## 0.1.0.2  -- 2018-03-14 
crypto-rng.cabal view
@@ -1,5 +1,5 @@ name:                crypto-rng-version:             0.1.2.0+version:             0.2.0.0 synopsis:            Cryptographic random number generator.  description:         Convenient wrapper for the cryptographic random generator@@ -14,7 +14,7 @@ copyright:           Scrive AB category:            Crypto build-type:          Simple-tested-with:         GHC ==8.0.2 || ==8.2.2 || ==8.4.4 || ==8.6.5 || ==8.8.3 || ==8.10.1+tested-with:         GHC ==8.8.4 || ==8.10.7 || ==9.0.2 extra-source-files:  ChangeLog.md cabal-version:       >=1.10 @@ -23,16 +23,24 @@   location: https://github.com/scrive/crypto-rng.git  library+  ghc-options:        -Wall -Wcompat+   exposed-modules:     Crypto.RNG                        Crypto.RNG.Class                        Crypto.RNG.Utils-  build-depends:       base              >= 4.9    && < 5,++  build-depends:       base              >= 4.13    && < 5,                        DRBG              >= 0.5.5  && < 0.6,-                       bytestring        >= 0.10.8 && < 0.11,+                       bytestring        >= 0.10.8 && < 0.12,                        crypto-api        >= 0.13.2 && < 0.14,+                       hashable          >= 1.1,                        mtl               >= 2.2.1  && < 2.3,                        exceptions        >= 0.8.3  && < 0.11,                        monad-control     >= 1.0.1  && < 1.1,+                       primitive         >= 0.7,+                       random            >= 1.2,                        transformers-base >= 0.4.4  && < 0.5+   hs-source-dirs:      src+   default-language:    Haskell2010
src/Crypto/RNG.hs view
@@ -1,16 +1,12 @@-{-# LANGUAGE CPP                        #-}-{-# LANGUAGE ExplicitForAll             #-}+{-# LANGUAGE BangPatterns               #-} {-# LANGUAGE FlexibleInstances          #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase                 #-} {-# LANGUAGE MultiParamTypeClasses      #-} {-# LANGUAGE ScopedTypeVariables        #-} {-# LANGUAGE TypeFamilies               #-} {-# LANGUAGE UndecidableInstances       #-} -#if __GLASGOW_HASKELL__ < 710-{-# LANGUAGE OverlappingInstances #-}-#endif- -- | Support for generation of cryptographically secure random -- numbers, based on the DRBG package. --@@ -24,118 +20,115 @@ -- 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 (-  -- * CryproRNG class+module Crypto.RNG+  ( -- * CryptoRNG class     module Crypto.RNG.Class-  -- * Generation of strings and numbers+    -- * Generation of strings and numbers   , CryptoRNGState   , newCryptoRNGState+  , newCryptoRNGStateSized   , unsafeCryptoRNGState   , randomBytesIO-  , randomR-  -- * Generation of values in other types-  , Random(..)-  , boundedIntegralRandom-  -- * Monad transformer for carrying rng state+  , randomIO+  , randomRIO+    -- * Monad transformer for carrying rng state   , CryptoRNGT   , mapCryptoRNGT   , runCryptoRNGT   , withCryptoRNGState   ) where -import Prelude hiding (fail) import Control.Applicative import Control.Concurrent import Control.Monad.Base-import Control.Monad.Catch hiding (fail)-import Control.Monad.Cont hiding (fail)-import Control.Monad.Except hiding (fail)-import Control.Monad.Fail (MonadFail(..))-import Control.Monad.Reader hiding (fail)+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, unpack)-import Data.Int-import Data.List-import Data.Word+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.  It sits inside an MVar to--- support concurrent thread access.-newtype CryptoRNGState = CryptoRNGState (MVar (GenAutoReseed HashDRBG HashDRBG))+-- | 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 = liftIO $ newGenIO >>= fmap CryptoRNGState . newMVar+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 -> m CryptoRNGState-unsafeCryptoRNGState s = liftIO $-  either (fail . show) (fmap CryptoRNGState . newMVar) (newGen s)+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 (CryptoRNGState gv) = do-  liftIO $ modifyMVar gv $ \g -> do-    (bs, g') <- either (const (fail "Crypto.GlobalRandom.genBytes")) return $-                genBytes n g-    return (g', bs)---- | Generate a cryptographically secure random number in given,--- closed range.-randomR :: (CryptoRNG m, Integral a) => (a, a) -> m a-randomR (minb', maxb') = do-  bs <- randomBytes byteLen-  return . fromIntegral $-    minb + foldl1' (\r a -> shiftL r 8 .|. a) (map toInteger (unpack bs))-            `mod` range-    where-      minb, maxb, range :: Integer-      minb = fromIntegral minb'-      maxb = fromIntegral maxb'-      range = maxb - minb + 1-      byteLen = ceiling $ logBase 2 (fromIntegral range) / (8 :: Double)---- | Helper function for making Random instances.-boundedIntegralRandom :: forall m a. (CryptoRNG m, Integral a, Bounded a) => m a-boundedIntegralRandom = randomR (minBound :: a, maxBound :: a)---- | Class for generating cryptographically secure random values.-class Random a where-  random :: CryptoRNG m => m a--instance Random Int16 where-  random = boundedIntegralRandom--instance Random Int32 where-  random = boundedIntegralRandom--instance Random Int64 where-  random = boundedIntegralRandom--instance Random Int where-  random = boundedIntegralRandom--instance Random Word8 where-  random = boundedIntegralRandom--instance Random Word16 where-  random = boundedIntegralRandom--instance Random Word32 where-  random = boundedIntegralRandom+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') -instance Random Word64 where-  random = boundedIntegralRandom+randomIO :: R.Uniform a => CryptoRNGState -> IO a+randomIO pool = withRNG pool $ \g -> R.uniform g -instance Random Word where-  random = boundedIntegralRandom+randomRIO :: R.UniformRange a => (a, a) -> CryptoRNGState -> IO a+randomRIO bounds pool = withRNG pool $ \g -> R.uniformR bounds g  type InnerCryptoRNGT = ReaderT CryptoRNGState @@ -149,7 +142,7 @@ mapCryptoRNGT f m = withCryptoRNGState $ \s -> f (runCryptoRNGT s m)  runCryptoRNGT :: CryptoRNGState -> CryptoRNGT m a -> m a-runCryptoRNGT gv m = runReaderT (unCryptoRNGT m) gv+runCryptoRNGT pool m = runReaderT (unCryptoRNGT m) pool  withCryptoRNGState :: (CryptoRNGState -> m a) -> CryptoRNGT m a withCryptoRNGState = CryptoRNGT . ReaderT@@ -158,15 +151,13 @@   type StT CryptoRNGT a = StT InnerCryptoRNGT a   liftWith = defaultLiftWith CryptoRNGT unCryptoRNGT   restoreT = defaultRestoreT CryptoRNGT-  {-# INLINE liftWith #-}-  {-# INLINE restoreT #-}  instance MonadBaseControl b m => MonadBaseControl b (CryptoRNGT m) where   type StM (CryptoRNGT m) a = ComposeSt CryptoRNGT m a   liftBaseWith = defaultLiftBaseWith   restoreM     = defaultRestoreM-  {-# INLINE liftBaseWith #-}-  {-# INLINE restoreM #-}  instance {-# OVERLAPPABLE #-} MonadIO m => CryptoRNG (CryptoRNGT m) where-  randomBytes n = CryptoRNGT ask >>= liftIO . randomBytesIO n+  randomBytes n  = CryptoRNGT ask >>= liftIO . randomBytesIO n+  random         = CryptoRNGT ask >>= liftIO . randomIO+  randomR bounds = CryptoRNGT ask >>= liftIO . randomRIO bounds
src/Crypto/RNG/Class.hs view
@@ -1,29 +1,32 @@-{-# LANGUAGE CPP                     #-}-{-# LANGUAGE ConstrainedClassMethods #-}-{-# LANGUAGE FlexibleInstances       #-}-{-# LANGUAGE UndecidableInstances    #-}--#if __GLASGOW_HASKELL__ < 710-{-# LANGUAGE OverlappingInstances #-}-#endif-+{-# LANGUAGE FlexibleInstances    #-}+{-# LANGUAGE UndecidableInstances #-} module Crypto.RNG.Class where  import Control.Monad.Trans import Crypto.Random.DRBG import Data.ByteString (ByteString)+import System.Random (Uniform, UniformRange)  -- | Monads carrying around the RNG state. class Monad m => CryptoRNG m where-  -- | Generate given number of cryptographically secure random bytes.-  randomBytes :: ByteLength -- ^ number of bytes to generate-              -> m ByteString+  -- | Generate a given number of cryptographically secure random bytes.+  randomBytes+    :: ByteLength -- ^ A number of bytes to generate.+    -> m ByteString --- | Generic, overlapping instance.+  -- | Generate a cryptographically secure value uniformly distributed over all+  -- possible values of that type.+  random :: Uniform a => m a +  -- | Generate a cryptographically secure value in a given, closed range.+  randomR :: UniformRange a => (a, a) -> m a++-- | Generic, overlapping instance. instance {-# OVERLAPPABLE #-} (     Monad (t m)   , MonadTrans t   , CryptoRNG m   ) => CryptoRNG (t m) where     randomBytes = lift . randomBytes+    random      = lift random+    randomR     = lift . randomR
src/Crypto/RNG/Utils.hs view
@@ -1,13 +1,14 @@ module Crypto.RNG.Utils where  import Control.Monad+import Data.Primitive.SmallArray  import Crypto.RNG  -- | Generate random string of specified length that contains allowed -- chars. randomString :: CryptoRNG m => Int -> [Char] -> m String-randomString n allowed_chars =-  sequence $ replicate n $ ((!!) allowed_chars `liftM` randomR (0, len))+randomString n allowedList = map (indexSmallArray allowed)+  <$> replicateM n (randomR (0, sizeofSmallArray allowed - 1))   where-    len = length allowed_chars - 1+    allowed = smallArrayFromList allowedList