{-|
Module      : Botan.RNG
Description : Random number generators
Copyright   : (c) Leo D, 2023
License     : BSD-3-Clause
Maintainer  : leo@apotheca.io
Stability   : experimental
Portability : POSIX

A module for the common task of random number generation.
-}

{-# LANGUAGE    KindSignatures
            ,   RankNTypes
            ,   TypeSynonymInstances
            ,   FlexibleInstances
            ,   MagicHash
            ,   TypeFamilies
            ,   MultiParamTypeClasses
            ,   CPP
            ,   DerivingStrategies
            ,   FunctionalDependencies
            ,   GeneralizedNewtypeDeriving
#-}

module Botan.RNG
( 

-- * Random Number Generators
-- $introduction

-- * Usage
-- $usage

-- * The RNG data type
  RNG(..)

-- * Available RNGs
, RNGType(..)

-- * Initializing a random number generator
, newRNG
, systemRNG

-- * Getting random bytes directly
, getRandomBytesRNG
, unsafeGetRandomBytesRNG

-- * Adding entropy directly
, addEntropyRNG
, reseedRNG
, reseedRNGFrom

-- * IO with implicit RNG
, MonadRandomIO(..)

-- * Getting random bytes
, getRandomBytes
, getSystemRandomBytes

-- * Adding entropy
, reseed
, reseedFrom
, addEntropy

-- * RandomIO monad
, RandomIO(..)
, runRandomIO

-- * RandomT monad transformer
, RandomT(..)
, runRandomT

) where

import Control.Concurrent.MVar

import Data.Bifunctor
import Data.Tuple

import qualified Data.ByteString as ByteString

import qualified Botan.Low.RNG as Low

import Control.Monad.Reader

import Botan.Prelude

import System.Random.Stateful

{- $introduction

Random number generators (RNG) are applicable to a wide variety of fields, including cryptography
and statistics. They tend to come in two basic varieties: /"true"/ random generators (TRNG) that
obtain entropy by measuring some physical random process, and /pseudo-/ random generators (PRNG)
that produce long sequences of unpredictable values based on permutations of a much shorter
initial key.

Most practical systems take a hybrid approach that involves reseeding a cryptographically
secure pseudo-random generator (CSPRNG) periodically from some a source of true entropy, which
is the approach that the Botan C++ library takes.

NOTE: Be forewarned that virtual machines usually lack access to a source of true entropy.

-}

{- $usage

This module provides two methods of using the random number generator, which are repeated
throughout other modules in the rest of the library, thus affecting their design:

- Directly using an `RNG` context
- Implicit access to an `RNG` context using `MonadRandomIO`

Use of `MonadRandomIO` is preferred.

=== Directly using an RNG context

Direct usage is very simple: an `RNG` context is created, and must be passed around manually.

> main = do
>     rng <- newRNG Autoseeded
>     addEntropyRNG "Fee fi fo fum!" rng
>     reseedRNG 32 rng
>     x <- getRandomBytesRNG 12 rng
>     print x

=== Implicit access to an RNG context using MonadRandomIO

Monadic usage is very simple: `IO` is itself a convenient instance of `MonadRandomIO` that
uses the `systemRNG`:

> main = do
>     x <- getRandomBytes 12
>     print x

The `runRandomIO` function is used to run a `MonadRandomIO` action in `RandomIO` with a specific `RNG`:

> main = do
>     rng <- newRNG Autoseeded
>     flip runRandomIO rng $ do
>         addEntropy "Fee fi fo fum!"
>         x <- getRandomBytes 12
>         liftIO $ print x

The `RandomT` transformer and `runRandomT` functions can also be run with any `MonadIO`,
since `RandomIO` and `RandomT` are both instances of `MonadRandomIO`.

> getSomeBytes :: (MonadIO m) => RandomT (ReaderT Int m) ByteString
> getSomeBytes = do
>     n <- lift ask
>     getRandomBytes n

-}

-- NOTE: Since botan 2.x User and User-threadsafe are the same thing:
--   ... if(rng_type_s == "user" || rng_type_s == "user-threadsafe") ...

{- |
The random generator type.

Custom RNG are not yet supported at this time.
-}
data RNGType
    = System        -- ^ System random `System_RNG`
    | Autoseeded    -- ^ User-threadsafe autoseeded random `AutoSeeded_RNG`
    | RDRand        -- ^ Hardware random `Processor_RNG`, may be unavailable

    -- CustomRNG ...
    deriving (RNGType -> RNGType -> Bool
(RNGType -> RNGType -> Bool)
-> (RNGType -> RNGType -> Bool) -> Eq RNGType
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: RNGType -> RNGType -> Bool
== :: RNGType -> RNGType -> Bool
$c/= :: RNGType -> RNGType -> Bool
/= :: RNGType -> RNGType -> Bool
Eq, Int -> RNGType -> ShowS
[RNGType] -> ShowS
RNGType -> String
(Int -> RNGType -> ShowS)
-> (RNGType -> String) -> ([RNGType] -> ShowS) -> Show RNGType
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> RNGType -> ShowS
showsPrec :: Int -> RNGType -> ShowS
$cshow :: RNGType -> String
show :: RNGType -> String
$cshowList :: [RNGType] -> ShowS
showList :: [RNGType] -> ShowS
Show)

type RNGName = Low.RNGType

rngName :: RNGType -> RNGName
rngName :: RNGType -> RNGName
rngName RNGType
System            = RNGName
Low.SystemRNG
rngName RNGType
Autoseeded        = RNGName
Low.UserRNG
rngName RNGType
RDRand            = RNGName
Low.RDRandRNG

{- |
The random number generator context.

NOTE: This data type is an instance of `System.Random.Stateful.Stateful`
-}
type RNG = Low.RNG

-- TODO: Move this to a System.Random.Stateful.Botan module that is imported conditionally
#if defined(HS_BOTAN_HAS_RANDOM)

instance (MonadIO m) => StatefulGen RNG m where

    -- NOTE: Going through ByteString first is inefficient
    uniformWord32 :: RNG -> m Word32
    uniformWord32 :: RNG -> m Word32
uniformWord32 RNG
rng = RNGName -> Word32
forall i. (Bits i, Integral i) => RNGName -> i
packIntegral (RNGName -> Word32) -> m RNGName -> m Word32
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Int -> RNG -> m RNGName
forall (m :: * -> *). MonadIO m => Int -> RNG -> m RNGName
getRandomBytesRNG Int
4 RNG
rng

packIntegral :: (Bits i, Integral i) => ByteString -> i
packIntegral :: forall i. (Bits i, Integral i) => RNGName -> i
packIntegral = (i -> Word8 -> i) -> i -> RNGName -> i
forall a. (a -> Word8 -> a) -> a -> RNGName -> a
ByteString.foldl i -> Word8 -> i
forall i w.
(Bits i, Integral i, FiniteBits w, Integral w) =>
i -> w -> i
packIntegralWord i
0

packIntegralWord :: (Bits i, Integral i, FiniteBits w, Integral w) => i -> w -> i
packIntegralWord :: forall i w.
(Bits i, Integral i, FiniteBits w, Integral w) =>
i -> w -> i
packIntegralWord i
i w
w = i -> Int -> i
forall a. Bits a => a -> Int -> a
shiftL i
i (w -> Int
forall b. FiniteBits b => b -> Int
finiteBitSize w
w) i -> i -> i
forall a. Bits a => a -> a -> a
.|. w -> i
forall a b. (Integral a, Num b) => a -> b
fromIntegral w
w

-- TODO: Adapters between MonadRandomIO and StatefulGen?
-- NOTE: Requires demoting
--      instance (MonadIO m) => StatefulGen RNG m where
--          ...
--  to:
--      instance (MonadIO m) => StatefulGen RNG (RandomT m) where
--          uniformWord32 :: RNG -> RandomT m Word32
--          uniformWord32 rng = packIntegral <$> getRandomBytesRNG 4 rng
-- Eg, something like:
{-
newtype WrappedMonadRandomIO m a = WrapMonadRandomIO { unwrapMonadRandomIO :: m a }
    deriving newtype (Functor, Applicative, Monad, MonadIO)

instance (Monad m, MonadRandomIO m) => StatefulGen RNG (WrappedMonadRandomIO m) where
    uniformWord32 rng = packIntegral <$> getRandomBytesRNG 4 rng

instance (Monad m,  MonadRandomIO m) => MonadRandomIO (WrappedMonadRandomIO m) where
    getRNG = WrapMonadRandomIO getRNG
-}

#endif

{- |
Initialize a random number generator object

NOTE: This is not `Botan.Low.RNG.newRNG`
-}
newRNG :: (MonadIO m) => RNGType -> m RNG
newRNG :: forall (m :: * -> *). MonadIO m => RNGType -> m RNG
newRNG RNGType
rngtyp = IO RNG -> m RNG
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO RNG -> m RNG) -> IO RNG -> m RNG
forall a b. (a -> b) -> a -> b
$ RNGName -> IO RNG
Low.rngInit (RNGType -> RNGName
rngName RNGType
rngtyp)
-- Maybe rename initRNG

-- | The `System_RNG` generator
systemRNG :: RNG
systemRNG :: RNG
systemRNG = IO RNG -> RNG
forall a. IO a -> a
unsafePerformIO (IO RNG -> RNG) -> IO RNG -> RNG
forall a b. (a -> b) -> a -> b
$ RNGType -> IO RNG
forall (m :: * -> *). MonadIO m => RNGType -> m RNG
newRNG RNGType
System
{-# NOINLINE systemRNG #-}

-- | Get random bytes from a random number generator
getRandomBytesRNG
    :: (MonadIO m)
    => Int              -- ^ n number of bytes
    -> RNG              -- ^ rng random generator
    -> m ByteString     -- ^ A random bytestring of length n
getRandomBytesRNG :: forall (m :: * -> *). MonadIO m => Int -> RNG -> m RNGName
getRandomBytesRNG Int
n RNG
rng = IO RNGName -> m RNGName
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO RNGName -> m RNGName) -> IO RNGName -> m RNGName
forall a b. (a -> b) -> a -> b
$ RNG -> Int -> IO RNGName
Low.rngGet RNG
rng Int
n

{- |
Get random bytes from a random number generator, unsafely.

This uses `unsafePerformIO`, and thus requires caution.
-}
unsafeGetRandomBytesRNG
    :: Int          -- ^ n number of bytes
    -> RNG          -- ^ rng random generator
    -> ByteString   -- ^ A random bytestring of length n
unsafeGetRandomBytesRNG :: Int -> RNG -> RNGName
unsafeGetRandomBytesRNG Int
n RNG
rng = IO RNGName -> RNGName
forall a. IO a -> a
unsafePerformIO (IO RNGName -> RNGName) -> IO RNGName -> RNGName
forall a b. (a -> b) -> a -> b
$ Int -> RNG -> IO RNGName
forall (m :: * -> *). MonadIO m => Int -> RNG -> m RNGName
getRandomBytesRNG Int
n RNG
rng
{-# NOINLINE unsafeGetRandomBytesRNG #-}


-- | Get random bytes from system random number generator
getSystemRandomBytes
    :: (MonadIO m)
    => Int              -- ^ n number of bytes
     -> m ByteString    -- ^ A random bytestring of length n
getSystemRandomBytes :: forall (m :: * -> *). MonadIO m => Int -> m RNGName
getSystemRandomBytes = IO RNGName -> m RNGName
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO RNGName -> m RNGName)
-> (Int -> IO RNGName) -> Int -> m RNGName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> IO RNGName
Low.systemRNGGet

{- |
Reseed a random number generator.

Uses the System_RNG as a seed generator.
-}
reseedRNG
    :: (MonadIO m)
    => Int      -- ^ n number of bits
    -> RNG      -- ^ rng random generator
    -> m ()
reseedRNG :: forall (m :: * -> *). MonadIO m => Int -> RNG -> m ()
reseedRNG Int
n RNG
rng = IO () -> m ()
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ RNG -> Int -> IO ()
Low.rngReseed RNG
rng Int
n

{- |
Reseed a random number generator using another generator.

NOTE: The arguments are in a different order than Botan.Low.RNG.rngReseedFromRNG
-}
reseedRNGFrom
    :: (MonadIO m)
    => Int      -- ^ n
    -> RNG      -- ^ src
    -> RNG      -- ^ rng
    -> m ()
reseedRNGFrom :: forall (m :: * -> *). MonadIO m => Int -> RNG -> RNG -> m ()
reseedRNGFrom Int
n RNG
src RNG
rng = IO () -> m ()
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ RNG -> RNG -> Int -> IO ()
Low.rngReseedFromRNG RNG
rng RNG
src Int
n

-- | Add some seed material to a random number generator
addEntropyRNG
    :: (MonadIO m)
    => ByteString
    -> RNG
    -> m ()
addEntropyRNG :: forall (m :: * -> *). MonadIO m => RNGName -> RNG -> m ()
addEntropyRNG RNGName
entropy RNG
gen = IO () -> m ()
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ RNG -> RNGName -> IO ()
Low.rngAddEntropy RNG
gen RNGName
entropy

-- newtype Random a = Random { unRandom :: RNG -> (# RNG, a #) }

-- NOTE: MonadRandom and RandomT might be ill-advised terminology.
-- What about MonadSample and SampleT for values that sample random distributions
-- that may or may not be uniform

-- | A typeclass for any monad that has access to a hidden `RNG` context
class MonadIO m => MonadRandomIO m where

    {- |
    Access the hidden `RNG` context

    This can be used to turn any direct `RNG` function into a `MonadRandomIO` function

    > getRandomBytes :: MonadRandomIO m => Int -> m ByteString
    > getRandomBytes n = getRNG >>= getRandomBytesRNG n
    -}
    getRNG :: m RNG

-- | Random generator monad transformer
type RandomT m = ReaderT RNG m
-- TODO: Turn RandomT into a newtype?
{-
newtype RandomT m a = MkRandomT { unRandomT :: ReaderT RNG m a }
-}

instance MonadRandomIO IO where
    getRNG :: IO RNG
getRNG = RNG -> IO RNG
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return RNG
systemRNG

instance (MonadIO m) => MonadRandomIO (ReaderT RNG m) where
    getRNG :: ReaderT RNG m RNG
getRNG = ReaderT RNG m RNG
forall r (m :: * -> *). MonadReader r m => m r
ask

-- | Runs a `MonadRandomIO` action in `MonadIO` using the specified generator.
runRandomT :: (MonadIO m) => RandomT m a -> RNG -> m a
runRandomT :: forall (m :: * -> *) a. MonadIO m => RandomT m a -> RNG -> m a
runRandomT = ReaderT RNG m a -> RNG -> m a
forall r (m :: * -> *) a. ReaderT r m a -> r -> m a
runReaderT

-- | Random generator monad
type RandomIO = ReaderT RNG IO

-- | Runs a `RandomIO` action in `IO` using the specified generator.
runRandomIO :: RandomIO a -> RNG -> IO a
runRandomIO :: forall a. RandomIO a -> RNG -> IO a
runRandomIO = RandomT IO a -> RNG -> IO a
forall (m :: * -> *) a. MonadIO m => RandomT m a -> RNG -> m a
runRandomT

-- | Get random bytes from the current random number generator
getRandomBytes :: MonadRandomIO m => Int -> m ByteString
getRandomBytes :: forall (m :: * -> *). MonadRandomIO m => Int -> m RNGName
getRandomBytes Int
n = m RNG
forall (m :: * -> *). MonadRandomIO m => m RNG
getRNG m RNG -> (RNG -> m RNGName) -> m RNGName
forall a b. m a -> (a -> m b) -> m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Int -> RNG -> m RNGName
forall (m :: * -> *). MonadIO m => Int -> RNG -> m RNGName
getRandomBytesRNG Int
n

{- |
Reseed the current random number generator.

Uses the System_RNG as a seed generator.
-}
reseed :: MonadRandomIO m => Int -> m ()
reseed :: forall (m :: * -> *). MonadRandomIO m => Int -> m ()
reseed Int
n = m RNG
forall (m :: * -> *). MonadRandomIO m => m RNG
getRNG m RNG -> (RNG -> m ()) -> m ()
forall a b. m a -> (a -> m b) -> m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Int -> RNG -> m ()
forall (m :: * -> *). MonadIO m => Int -> RNG -> m ()
reseedRNG Int
n

-- | Reseed the current random number generator using another generator.
reseedFrom
    :: (MonadRandomIO m)
    => Int      -- ^ n number of bits
    -> RNG      -- ^ src
    -> m ()
reseedFrom :: forall (m :: * -> *). MonadRandomIO m => Int -> RNG -> m ()
reseedFrom Int
n RNG
src = m RNG
forall (m :: * -> *). MonadRandomIO m => m RNG
getRNG m RNG -> (RNG -> m ()) -> m ()
forall a b. m a -> (a -> m b) -> m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Int -> RNG -> RNG -> m ()
forall (m :: * -> *). MonadIO m => Int -> RNG -> RNG -> m ()
reseedRNGFrom Int
n RNG
src

-- | Add some seed material to the current random number generator
addEntropy
    :: (MonadRandomIO m)
    => ByteString   -- ^ entropy
    -> m ()
addEntropy :: forall (m :: * -> *). MonadRandomIO m => RNGName -> m ()
addEntropy RNGName
entropy = m RNG
forall (m :: * -> *). MonadRandomIO m => m RNG
getRNG m RNG -> (RNG -> m ()) -> m ()
forall a b. m a -> (a -> m b) -> m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= RNGName -> RNG -> m ()
forall (m :: * -> *). MonadIO m => RNGName -> RNG -> m ()
addEntropyRNG RNGName
entropy

--
-- We can make RNG conform to the experimental typeclasses
--

instance RNG' RNG where

    generateRandomBytes' :: Int -> RNG -> IO ByteString
    generateRandomBytes' :: Int -> RNG -> IO RNGName
generateRandomBytes' = Int -> RNG -> IO RNGName
forall (m :: * -> *). MonadIO m => Int -> RNG -> m RNGName
getRandomBytesRNG

    addEntropyRNG' :: ByteString -> RNG -> IO ()
    addEntropyRNG' :: RNGName -> RNG -> IO ()
addEntropyRNG' = RNGName -> RNG -> IO ()
forall (m :: * -> *). MonadIO m => RNGName -> RNG -> m ()
addEntropyRNG

--
-- EXPERIMENTS TO BE MOVED TO CRYPTO-SCHEMES
--

-- TODO: More stringently define difference between:
--      - seeding (initializing or setting the RNG to a specific state)
--      - reseeding (adding entropy that gets mixed with the curent state)
-- NOTE: NIST DRBG standards support this use of terminology
--  https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-90Ar1.pdf
--  - Seed = "Verb: To acquire bits with sufficient entropy for the desired security strength. 
--    These bits will be used as input to a DRBG mechanism to determine a portion of the initial internal state."
--  - Reseed = "Verb: To acquire additional bits that will affect the internal state of the DRBG mechanism."

-- NOTE: RNG' and PRNG' are almost, but not exactly, equivalent to System.Random and System.Random.Stateful
-- PRNG' and RandomGen are equivalent aside from split, and RNG' matches StatefulGen (and Botan.Low.RNG.RNG
-- also can be an instance of both RNG' and StatefulGen)
-- However, RNG' is explicitly IO (for better or worse), and / as it is intended to represent TRNGs
-- To counter, RNG' partly has an IO constraint because of Botan.Low.RNG.RNG, and I do
-- understand the use intent of the m in Stateful g m as it allows you to swap between
-- IO and ST (and MonadIO)
-- They could be aligned more completely a la `class RNG' gen m where` to match StatefulGen
-- but I'd also like to keep the implications of (T)RNG' vs PRNG', and especially with
-- CSPRNG' specifically implying 'a PRNG indistinguishable from (T)RNG'
-- LATER: We can keep the T implication if we "require" that the RNG pass tests for uniform
-- distribution; otherwise we should reduce RNG from True TRNG to one that is explicitly
-- Unknown URNG - a slightly more relaxed constraint than true entropy. Note that
-- it is *not* 'stateful', it could be true random, *we're not supposed to be able to tell*.
-- Both System.Random and System.Random.Stateful are still both quite explicitly about PRNG,
-- and they even state it everywhere. We want to disambiguate RNG-in-general from specifically
-- PRNG or TRNG.
-- MINOR NOTE: If we reduce RNG from TRNG to URNG, we should also acknowledge that true random
-- is unaffected by reseeding, and so that anything that can be reseeded is also at least URNG
-- if not PRNG. This is a question of chaos vs random - chaos can be affected / reseeded but
-- still not predicted.

-- RandomGenerator
class RNG' gen where

    -- generateRandomWord8 :: gen -> IO Word8
    -- generateRandomWord16 :: gen -> IO Word16
    -- generateRandomWord32 :: gen -> IO Word32
    -- generateRandomWord64 :: gen -> IO Word64
    -- generateRandomShortByteString :: gen -> IO ShortByteString

    generateRandomBytes' :: Int -> gen -> IO ByteString

-- class RNG' gen => EntropyPool' gen where

    addEntropyRNG' :: ByteString -> gen -> IO ()

    -- Convenience

    -- NOTE: Omitted:
    -- addSystemEntropy' :: Int -> gen -> IO ()
    -- addSystemEntropy' n = addRandomEntropy' n systemRNG'

    addRandomEntropy'        :: (RNG' seed) => Int -> seed -> gen -> IO ()
    addRandomEntropy' Int
nbytes seed
seed gen
gen = do
        RNGName
entropy <- Int -> seed -> IO RNGName
forall gen. RNG' gen => Int -> gen -> IO RNGName
generateRandomBytes' Int
nbytes seed
seed
        RNGName -> gen -> IO ()
forall gen. RNG' gen => RNGName -> gen -> IO ()
addEntropyRNG' RNGName
entropy gen
gen

    addPseudoRandomEntropy'  :: (PRNG' seed) => Int -> seed -> gen -> IO seed
    addPseudoRandomEntropy' Int
nbytes seed
seed gen
gen = do
        RNGName -> gen -> IO ()
forall gen. RNG' gen => RNGName -> gen -> IO ()
addEntropyRNG' RNGName
entropy gen
gen
        seed -> IO seed
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return seed
seed'
        where
            (RNGName
entropy, seed
seed') = Int -> seed -> (RNGName, seed)
forall gen. PRNG' gen => Int -> gen -> (RNGName, gen)
generatePseudoRandomBytes' Int
nbytes seed
seed


-- PseudoRandomGenerator
-- NOTE: Where possible, prefer `gen -> (a, gen)` as with MonadState `s -> (a, s)`
class PRNG' gen where

    generatePseudoRandomBytes'   :: Int -> gen -> (ByteString, gen) 

-- class PRNG' gen => Reseedable' gen where

    reseedEntropy'               :: ByteString -> gen -> gen

    -- Convenience

    -- NOTE: Omitted:
    -- reseedSystem'                :: Int -> gen -> IO gen
    -- reseedSystem' n = reseedRandom' n systemRNG'

    reseedRandom'                :: (RNG' seed) => Int -> seed -> gen -> IO gen
    reseedRandom' Int
nbytes seed
seed gen
gen = do
        RNGName
entropy <- Int -> seed -> IO RNGName
forall gen. RNG' gen => Int -> gen -> IO RNGName
generateRandomBytes' Int
nbytes seed
seed
        gen -> IO gen
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (RNGName -> gen -> gen
forall gen. PRNG' gen => RNGName -> gen -> gen
reseedEntropy' RNGName
entropy gen
gen)

    reseedPseudoRandom'          :: (PRNG' seed) => Int -> seed -> gen -> (seed, gen)
    reseedPseudoRandom' Int
nbytes seed
seed gen
gen = (seed
seed', gen
gen') where
        (RNGName
entropy, seed
seed') = Int -> seed -> (RNGName, seed)
forall gen. PRNG' gen => Int -> gen -> (RNGName, gen)
generatePseudoRandomBytes' Int
nbytes seed
seed
        gen' :: gen
gen'             = RNGName -> gen -> gen
forall gen. PRNG' gen => RNGName -> gen -> gen
reseedEntropy' RNGName
entropy gen
gen

-- AsRandomGenerator
newtype CSPRNG' gen
    = MkCSPRNG' 
    -- { runCSPRNG' :: PRNG' gen => MVar gen
    { forall gen. CSPRNG' gen -> MVar gen
runCSPRNG' :: MVar gen
    }

modifyCSPRNG' :: CSPRNG' gen -> (gen -> IO (gen, a)) -> IO a
modifyCSPRNG' :: forall gen a. CSPRNG' gen -> (gen -> IO (gen, a)) -> IO a
modifyCSPRNG' (MkCSPRNG' MVar gen
mgen) = MVar gen -> (gen -> IO (gen, a)) -> IO a
forall a b. MVar a -> (a -> IO (a, b)) -> IO b
modifyMVar MVar gen
mgen

modifyCSPRNG_' :: CSPRNG' gen -> (gen -> IO gen) -> IO ()
modifyCSPRNG_' :: forall gen. CSPRNG' gen -> (gen -> IO gen) -> IO ()
modifyCSPRNG_' (MkCSPRNG' MVar gen
mgen) = MVar gen -> (gen -> IO gen) -> IO ()
forall a. MVar a -> (a -> IO a) -> IO ()
modifyMVar_ MVar gen
mgen

instance (PRNG' gen) => RNG' (CSPRNG' gen) where

    generateRandomBytes' :: Int -> CSPRNG' gen -> IO ByteString
    generateRandomBytes' :: Int -> CSPRNG' gen -> IO RNGName
generateRandomBytes' Int
nbytes CSPRNG' gen
csprng = CSPRNG' gen -> (gen -> IO (gen, RNGName)) -> IO RNGName
forall gen a. CSPRNG' gen -> (gen -> IO (gen, a)) -> IO a
modifyCSPRNG' CSPRNG' gen
csprng ((gen -> IO (gen, RNGName)) -> IO RNGName)
-> (gen -> IO (gen, RNGName)) -> IO RNGName
forall a b. (a -> b) -> a -> b
$ \ gen
gen -> do
        (gen, RNGName) -> IO (gen, RNGName)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ((gen, RNGName) -> IO (gen, RNGName))
-> (gen, RNGName) -> IO (gen, RNGName)
forall a b. (a -> b) -> a -> b
$ (RNGName, gen) -> (gen, RNGName)
forall a b. (a, b) -> (b, a)
swap ((RNGName, gen) -> (gen, RNGName))
-> (RNGName, gen) -> (gen, RNGName)
forall a b. (a -> b) -> a -> b
$ Int -> gen -> (RNGName, gen)
forall gen. PRNG' gen => Int -> gen -> (RNGName, gen)
generatePseudoRandomBytes' Int
nbytes gen
gen

    addEntropyRNG' :: ByteString -> CSPRNG' gen -> IO ()
    addEntropyRNG' :: RNGName -> CSPRNG' gen -> IO ()
addEntropyRNG' RNGName
entropy CSPRNG' gen
csprng = CSPRNG' gen -> (gen -> IO gen) -> IO ()
forall gen. CSPRNG' gen -> (gen -> IO gen) -> IO ()
modifyCSPRNG_' CSPRNG' gen
csprng ((gen -> IO gen) -> IO ()) -> (gen -> IO gen) -> IO ()
forall a b. (a -> b) -> a -> b
$ \ gen
gen -> do
        gen -> IO gen
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (RNGName -> gen -> gen
forall gen. PRNG' gen => RNGName -> gen -> gen
reseedEntropy' RNGName
entropy gen
gen)

-- NOTE: System.Random.Stateful uses a typeclass to represent 'frozen' PRNG
-- with an associated data type pointing back to the original.
-- Notably, it uses fundeps in the associated type,
{-
class StatefulGen (MutableGen f m) m => FrozenGen f m where
    type MutableGen f m = (g :: Type) | g -> f
    freezeGen :: MutableGen f m -> m f
    thawGen :: f -> m (MutableGen f m)
-}

class PRNG' gen => Seedable' gen where

    type Seed' gen

    -- Seed the state of an existing generator
    seed :: Seed' gen -> gen -> gen

    freezeSeed :: gen -> Seed' gen

    thawSeed :: Seed' gen -> gen

-- TODO: NIST DRBG standards
--  https://github.com/ANSSI-FR/libdrbg
--  https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-90Ar1.pdf
-- The three NIST SP 800-90A DRBG mechanisms
-- NOTE: NIST terminology:
--  - DRGB = Deterministic Random Bit Generator = PRNG
--  - NRGB = Non-deterministic Random Bit Generator = (True) RNG
{-
data HashType
data BlockCipherType

data HashDerivationFunction

data HashDRBG = MkHashDRBG
    { hashDRBGValue     :: ByteString       -- ^ v
    , hashDRBGConstant  :: ByteString       -- ^ c
    -- Administrative
    , hashDRBGSeedLen               :: Int  -- ^ seedlen
    , hashDRBGSecurityStrength      :: Int  -- ^ security_strength
    , hashDRBGPredictionResistant   :: Bool -- ^ prediction_resistance_flag
    }

hashDRBGInstantiate :: Int -> Bool -> ByteString -> HashDRBG
hashDRBGInstantiate requestedStrength predictionResistant personalizationString = undefined
-}