-- |
-- Module      : Crypto.Random.Probabilistic
-- License     : BSD-style
-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
-- Stability   : experimental
-- Portability : Good
module Crypto.Random.Probabilistic (
    probabilisticFrom,
) where

import Crypto.Hash (SHA512 (..), hashWith)
import Crypto.Internal.ByteArray (ByteArrayAccess, ScrubbedBytes)
import qualified Crypto.Internal.ByteArray as B
import Crypto.Internal.Compat
import Crypto.Random
import Crypto.Random.ChaChaDRG (initialize)

-- | Run a probabilistic algorithm on a generator derived from the value it is
-- about to work on, and from a secret this process drew once.
--
-- This is useful for a probabilistic algorithm like the Miller-Rabin primality
-- test, where the caller is a pure function and has to behave like one: the
-- same value has to give the same answer for as long as the process lives.
-- Deriving the generator from the value gives that much, and it keeps the
-- draws made for two different values independent of each other -- one
-- generator made once and shared by every call would make the witnesses drawn
-- for one value the witnesses for every value.
--
-- The process secret is what makes the derivation unpredictable.  The values
-- worked on may come from wherever the caller's input comes from, so the
-- generator must not be something that can be worked out from them.
--
-- The IO is not exposed and the result is not reproducible between processes.
-- Generally, it's advised not to use this function.
probabilisticFrom
    :: ByteArrayAccess seed
    => seed
    -- ^ the value being worked on, as bytes
    -> MonadPseudoRandom ChaChaDRG a
    -> a
probabilisticFrom :: forall seed a.
ByteArrayAccess seed =>
seed -> MonadPseudoRandom ChaChaDRG a -> a
probabilisticFrom seed
material MonadPseudoRandom ChaChaDRG a
f = (a, ChaChaDRG) -> a
forall a b. (a, b) -> a
fst ((a, ChaChaDRG) -> a) -> (a, ChaChaDRG) -> a
forall a b. (a -> b) -> a -> b
$ ChaChaDRG -> MonadPseudoRandom ChaChaDRG a -> (a, ChaChaDRG)
forall gen a. DRG gen => gen -> MonadPseudoRandom gen a -> (a, gen)
withDRG ChaChaDRG
drg MonadPseudoRandom ChaChaDRG a
f
  where
    drg :: ChaChaDRG
drg = ScrubbedBytes -> ChaChaDRG
forall seed. ByteArrayAccess seed => seed -> ChaChaDRG
initialize (Int -> ScrubbedBytes -> ScrubbedBytes
forall bs. ByteArray bs => Int -> bs -> bs
B.take Int
seedLength (Digest SHA512 -> ScrubbedBytes
forall bin bout.
(ByteArrayAccess bin, ByteArray bout) =>
bin -> bout
B.convert Digest SHA512
digest :: ScrubbedBytes))
    digest :: Digest SHA512
digest = SHA512 -> ScrubbedBytes -> Digest SHA512
forall ba alg.
(ByteArrayAccess ba, HashAlgorithm alg) =>
alg -> ba -> Digest alg
hashWith SHA512
SHA512 (ScrubbedBytes -> ScrubbedBytes -> ScrubbedBytes
forall bs. ByteArray bs => bs -> bs -> bs
B.append ScrubbedBytes
secret (seed -> ScrubbedBytes
forall bin bout.
(ByteArrayAccess bin, ByteArray bout) =>
bin -> bout
B.convert seed
material) :: ScrubbedBytes)
    -- what Crypto.Random.ChaChaDRG.initialize wants, and no more than SHA-512
    -- produces
    seedLength :: Int
seedLength = Int
40

-- | Drawn once, for the lifetime of the process: it is the only part of the
-- derivation above that an attacker supplying values cannot see.
secret :: ScrubbedBytes
secret :: ScrubbedBytes
secret = IO ScrubbedBytes -> ScrubbedBytes
forall a. IO a -> a
unsafeDoIO (Int -> IO ScrubbedBytes
forall byteArray. ByteArray byteArray => Int -> IO byteArray
forall (m :: * -> *) byteArray.
(MonadRandom m, ByteArray byteArray) =>
Int -> m byteArray
getRandomBytes Int
32)
{-# NOINLINE secret #-}