packages feed

botan-low-0.2.0.0: src/Botan/Low/RNG.hs

{-|
Module      : Botan.Low.RNG
Description : Random number generators
Copyright   : (c) 2023-2024, Apotheca Labs
              (c) 2024-2025, Haskell Foundation
License     : BSD-3-Clause
Maintainer  : joris@well-typed.com, leo@apotheca.io
Stability   : experimental
Portability : POSIX

Pseudo-random number generation.
-}

module Botan.Low.RNG (

  -- * Random number generators
  -- $introduction

  -- * Usage
  -- $usage

    RNG(..)
  , RNGType
  , withRNG
  , rngInit
  , rngDestroy
  , rngGet
  , systemRNGGet
  , rngReseed
  , rngReseedFromRNG
  , rngAddEntropy

  -- * RNG Types

  , pattern SystemRNG
  , pattern UserRNG
  , pattern UserThreadsafeRNG
  , pattern RDRandRNG

  ) where

import           Botan.Bindings.ConstPtr (ConstPtr (..))
import           Botan.Bindings.RNG
import           Botan.Low.Error.Internal
import           Botan.Low.Internal.ByteString
import           Botan.Low.Remake
import           Data.ByteString (ByteString)
import           Foreign.C.Types
import           Foreign.ForeignPtr
import           Foreign.Ptr

{- $introduction

A `random number generator` is used to generate uniform samples of pseudorandom
bytes.

-}

{- $usage

You can always use the system `RNG`:

> import Botan.Low.RNG
> randomBytes <- systemRNGGet 16

Unless you need a specific `RNG`, it is strongly recommended that you use the
autoseeded `user` RNG.

> import Botan.Low.RNG
> rng <- rngInit "user"
> randomBytes <- rngGet rng 16

You can reseed a generator using the system generator:

> rngReseed rng 64

You can also reseed a generator using a specific generator:

> systemRNG <- rngInit "system"
> rngReseedFromRNG rng systemRNG 64

You can also seed it with your own entropy; this is safe and can never
*decrease* the amount of entropy in the generator.

> rngAddEntropy rng "Fee fi fo fum!"

-}

-- NOTE: Does not take advantage of Remake
-- NOTE: Uses ConstPtr / unConstPtr manually
-- TODO: Take advantage of Remake / better peek / (peekConst or constPeek) functions

newtype RNG = MkRNG { foreignPtr :: ForeignPtr BotanRNGStruct }

withRNG     :: RNG -> (BotanRNG -> IO a) -> IO a
-- | Destroy a random number generator object immediately
rngDestroy  :: RNG -> IO ()
createRNG   :: (Ptr BotanRNG -> IO CInt) -> IO RNG
(withRNG, rngDestroy, createRNG)
    = mkBindings MkBotanRNG (.ptr) MkRNG (.foreignPtr) botan_rng_destroy

type RNGType = ByteString

pattern SystemRNG           -- ^ system RNG
    ,   UserRNG             -- ^ userspace RNG
    ,   UserThreadsafeRNG   -- ^ userspace RNG, with internal locking
    ,   RDRandRNG           -- ^ directly read RDRAND
    ::  RNGType
pattern SystemRNG           = BOTAN_RNG_TYPE_SYSTEM
pattern UserRNG             = BOTAN_RNG_TYPE_USER
pattern UserThreadsafeRNG   = BOTAN_RNG_TYPE_USER_THREADSAFE
pattern RDRandRNG           = BOTAN_RNG_TYPE_RDRAND

{- |
Initialize a random number generator object

rng_type has the possible values:

    - "system": system RNG
    - "user": userspace RNG
    - "user-threadsafe": userspace RNG, with internal locking
    - "rdrand": directly read RDRAND

Set rng_type to null to let the library choose some default.
-}
rngInit
    :: RNGType  -- ^ __rng_type__: type of the rng
    -> IO RNG   -- ^ __rng__
rngInit = mkCreateObjectCString createRNG botan_rng_init

-- | Get random bytes from a random number generator
rngGet
    :: RNG              -- ^ __rng__: rng object
    -> Int              -- ^ __out_len__: number of requested bytes
    -> IO ByteString    -- ^ __out__: output buffer of size out_len
rngGet rng len = withRNG rng $ \ botanRNG -> do
    allocBytes len $ \ bytesPtr -> do
        throwBotanIfNegative_ $ botan_rng_get botanRNG bytesPtr (fromIntegral len)

-- | Get random bytes from system random number generator
systemRNGGet
    :: Int              -- ^ __out_len__: number of requested bytes
    -> IO ByteString    -- ^ __out__: output buffer of size out_len
systemRNGGet len = allocBytes len $ \ bytesPtr -> do
    throwBotanIfNegative_ $ botan_system_rng_get bytesPtr (fromIntegral len)

{- |
Reseed a random number generator

Uses the System_RNG as a seed generator.
-}
rngReseed
    :: RNG  -- ^ __rng__: rng object
    -> Int  -- ^ __bits__: number of bits to reseed with
    -> IO ()
rngReseed rng bits = withRNG rng $ \ botanRNG -> do
    throwBotanIfNegative_ $ botan_rng_reseed botanRNG (fromIntegral bits)

-- | Reseed a random number generator
rngReseedFromRNG
    :: RNG      -- ^ __rng__: rng object
    -> RNG      -- ^ __source_rng__: the rng that will be read from
    -> Int      -- ^ __bits__: number of bits to reseed with
    -> IO ()
rngReseedFromRNG rng source bits = withRNG rng $ \ botanRNG -> do
    withRNG source $ \ sourcePtr -> do
        throwBotanIfNegative_ $ botan_rng_reseed_from_rng botanRNG sourcePtr (fromIntegral bits)

-- | Add some seed material to a random number generator
rngAddEntropy
    :: RNG          -- ^ __rng__: rng object
    -> ByteString   -- ^ __entropy__: the data to add
    -> IO ()
rngAddEntropy rng bytes = withRNG rng $ \ botanRNG -> do
    asBytesLen bytes $ \ bytesPtr bytesLen -> do
        throwBotanIfNegative_ $ botan_rng_add_entropy botanRNG (ConstPtr bytesPtr) bytesLen