Z-Botan-0.3.0.0: Z/Crypto/RNG.hs
{-|
Module : Z.Crypto.RNG
Description : Random Number Generators
Copyright : Dong Han, 2021
License : BSD
Maintainer : winterland1989@gmail.com
Stability : experimental
Portability : non-portable
Several different RNG types are implemented. Some access hardware RNGs, which are only available on certain platforms. Others are mostly useful in specific situations.
-}
module Z.Crypto.RNG
( -- * RNG
RNGType(..), RNG
, newRNG, getRNG, getRandom
, reseedRNG, reseedRNGFromRNG, addEntropyRNG
-- * Internal
, withRNG
) where
import Control.Monad
import Data.IORef
import GHC.Conc
import GHC.Generics
import System.IO.Unsafe
import Z.Botan.Exception
import Z.Botan.FFI
import qualified Z.Data.Array as A
import Z.Data.CBytes
import Z.Data.JSON (JSON)
import qualified Z.Data.Text as T
import qualified Z.Data.Vector as V
import Z.Foreign
-- | RNG types.
data RNGType = SystemRNG | AutoSeededRNG | ProcessorRNG
deriving (Show, Eq, Ord, Generic)
deriving anyclass (T.Print, JSON)
-- | Opaque botan RNG type.
newtype RNG = RNG BotanStruct
deriving (Show, Generic)
deriving anyclass T.Print
-- | Initialize a random number generator object from the given 'RNGType'
newRNG :: HasCallStack => RNGType -> IO RNG
{-# INLINABLE newRNG #-}
newRNG typ = RNG <$> newBotanStruct
(\ bts -> withCBytesUnsafe (rngTypeCBytes typ) (botan_rng_init bts))
botan_rng_destroy
where
rngTypeCBytes SystemRNG = "system"
rngTypeCBytes AutoSeededRNG = "user"
rngTypeCBytes ProcessorRNG = "hwrng"
-- | Use RNG as a `botan_rng_t` object.
withRNG :: RNG -> (BotanStructT -> IO a) -> IO a
{-# INLINABLE withRNG #-}
withRNG (RNG rng) f = withBotanStruct rng f
-- | Get an autoseeded RNG from a global RNG pool divide by haskell capability.
--
-- Botan internal use a lock to protect user-space RNG, which may cause contention if shared.
-- This function will fetch an autoseeded RNG from a global RNG pool, which is recommended under
-- concurrent settings.
getRNG :: HasCallStack => IO RNG
{-# INLINABLE getRNG #-}
getRNG = do
(cap, _) <- threadCapability =<< myThreadId
rngArray <- readIORef rngArrayRef
A.indexArrM rngArray (cap `rem` A.sizeofArr rngArray)
where
rngArrayRef :: IORef (A.SmallArray RNG)
{-# NOINLINE rngArrayRef #-}
rngArrayRef = unsafePerformIO $ do
numCaps <- getNumCapabilities
rngArray <- A.newArr numCaps
forM_ [0..numCaps-1] $ \ i -> do
A.writeArr rngArray i =<< newRNG AutoSeededRNG
irngArray <- A.unsafeFreezeArr rngArray
newIORef irngArray
-- | Get random bytes from a random number generator.
getRandom :: HasCallStack => RNG -> Int -> IO V.Bytes
{-# INLINABLE getRandom #-}
getRandom r siz = withRNG r $ \ rng -> do
(b, _) <- allocPrimVectorUnsafe siz $ \ buf ->
throwBotanIfMinus_ (botan_rng_get rng buf (fromIntegral siz))
return b
-- | Reseeds the random number generator with bits number of bits from the 'SystemRNG'.
reseedRNG :: HasCallStack => RNG -> Int -> IO ()
{-# INLINABLE reseedRNG #-}
reseedRNG r siz = withRNG r $ \ rng -> do
throwBotanIfMinus_ (botan_rng_reseed rng (fromIntegral siz))
-- | Reseeds the random number generator with bits number of bits from the given source RNG.
reseedRNGFromRNG :: HasCallStack => RNG -> RNG -> Int -> IO ()
{-# INLINABLE reseedRNGFromRNG #-}
reseedRNGFromRNG r1 r2 siz =
withRNG r1 $ \ rng1 -> do
withRNG r2 $ \ rng2 -> do
throwBotanIfMinus_ (botan_rng_reseed_from_rng rng1 rng2 (fromIntegral siz))
-- | Adds the provided seed material to the internal RNG state.
--
-- This call may be ignored by certain RNG instances (such as 'ProcessorRNG' or, on some systems, the 'SystemRNG').
addEntropyRNG :: HasCallStack => RNG -> V.Bytes -> IO ()
{-# INLINABLE addEntropyRNG #-}
addEntropyRNG r seed =
withRNG r $ \ rng -> do
withPrimVectorUnsafe seed $ \ pseed offseed lseed -> do
throwBotanIfMinus_ (hs_botan_rng_add_entropy rng pseed
(fromIntegral offseed) (fromIntegral lseed))