newhope-0.1.0.0: src/Crypto/NewHope/Internal/SeedExpander.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE Trustworthy #-}
{-# OPTIONS_HADDOCK prune #-}
{-|
Module : Crypto.NewHope.Internal.SeedExpander
Description : Seed expander for NewHope.
Copyright : © Jeremy Bornstein 2019
License : Apache 2.0
Maintainer : jeremy@bornstein.org
Stability : experimental
Portability : portable
The "seed expander" is a facility specified by NIST for generating
pseudorandom data given a seed. It is not used in the actual NewHope
key exchange and is provided here for completeness/isomorphism with
the NewHope C reference library.
This module contains the actual implementation. Exposed definitions
are in the 'Crypto.NewHope.SeedExpander' module.
* Sample usage
@
let maxLen' = case maxLen 256 of Right value -> value
Left x -> error (show x)
let diversifier = case createDiversifier (BSC.pack "12345678") of Right value -> value
Left x -> error (show x)
let seed = (Internals.makeSeed "32 bytes of seed data go here...")
let ctx = case seedexpanderInit seed diversifier maxLen' of Right value -> value
Left x -> error (show x)
let (ctx', buf) = case seedexpander ctx 16 of Right value -> value
Left x r -> error (show x)
@
-}
module Crypto.NewHope.Internal.SeedExpander where
import Codec.Crypto.AES
import Control.Monad.Except
import Data.Bits
import Data.Semigroup ((<>))
import qualified Data.ByteString as BS
import qualified Data.ByteString.Builder as Builder
import qualified Data.ByteString.Lazy as BSL
import Data.Word
import qualified Crypto.NewHope.Internals as Internals
import qualified Crypto.NewHope.Internal.RNG as RNG
-- | Error conditions detected in creation and use of 'Context' data
data RNGError = BadDiversifierLen | BadMaxLen | BadReqLen deriving (Show)
-- | Maintains state for a series of calls to generate pseudorandom data via 'seedexpander'.
data Context = Context { ctxBuffer :: BS.ByteString
, ctxBufferPos :: Word64
, ctxLengthRemaining :: Word64
, ctxKey :: RNG.Key
, ctxCounter :: RNG.V
} deriving (Eq)
-- | Contains extra seed material for initializing SeedExpander
newtype Diversifier = Diversifier BS.ByteString deriving Show
-- | Specifies eight bytes of data for use as part of the seed material to be expanded.
createDiversifier :: (MonadError RNGError m) => BS.ByteString -> m Diversifier
createDiversifier bs
| BS.length bs /= 8 = throwError BadDiversifierLen
| otherwise = return $ Diversifier bs
-- | Contains the maximum number of bytes that a 'Context' will generate.
newtype MaxLen = MaxLen Word64
-- | Specifies the maximum number of bytes that a 'Context' will generate.
maxLen :: (MonadError RNGError m) => Word64 -> m MaxLen
maxLen n
| n < 0 || n > 0x100000000 = throwError BadMaxLen
| otherwise = return $ MaxLen n
-- | Create a 'Context' for generation of data.
seedexpanderInit :: (MonadError RNGError m) => Internals.Seed -> Diversifier -> MaxLen -> m Context
seedexpanderInit (Internals.Seed seed) (Diversifier diversifier) (MaxLen maxLength) = return ctx
where
ctx = Context { ctxBuffer = BS.pack $ replicate 16 0 -- irrelevant; will be discarded on first update
, ctxBufferPos = 16 -- at the end, so the first call will fill it
, ctxLengthRemaining = maxLength
, ctxKey = RNG.createKey seed
, ctxCounter = createCounter
}
-- Structure of counter:
-- 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
-- DIVERSIFIER-----------| maxlen----| 00 00 00 00
-- MSB LSB
createCounter :: RNG.V
createCounter = RNG.createV value
where
maxlenFourBytes = fromIntegral $ maxLength .&. 0xFFffFFff
zeroCounter = 0
value = BS.pack (BS.unpack diversifier
++ BSL.unpack (Builder.toLazyByteString (Builder.word32BE maxlenFourBytes))
++ BSL.unpack (Builder.toLazyByteString (Builder.word32BE zeroCounter)))
-- | Generate pseudorandom data from the given 'Context'. The returned
-- pair contains the requested data and the next 'Context' to use.
seedexpander :: (MonadError RNGError m) => Context -> Word64 -> m (BS.ByteString, Context)
seedexpander ctx xlen
| xlen >= ctxLengthRemaining ctx = throwError BadReqLen -- i'd think it would be > and not >= but reference source' uses >=
| xlen < 0 = return (BS.pack [], ctx)
| xlen <= (16 - bufferPos) = return (existingBufferResult, ctx'0)
| otherwise = do -- more than a single buffer's worth, or more than we have left in buffer
(result', ctx') <- seedexpander ctx'1 xlen'
return (restOfExistingBuffer <> result', ctx')
where
bufferPos = ctxBufferPos ctx
bytesUsedOfBuffer = min (16 - bufferPos) (min xlen 16) -- how much from current buffer we'll use
restOfExistingBuffer = BS.drop (fromIntegral bufferPos) (ctxBuffer ctx) -- all of the unused bytes in the current buffer
existingBufferResult = BS.take (fromIntegral bytesUsedOfBuffer) restOfExistingBuffer -- the actual bytes we'll use from the buffer.
-- (only calculated if we don't need more bytes)
-- For when we are returning bytes from our existing buffer.
lengthRemaining = ctxLengthRemaining ctx - fromIntegral bytesUsedOfBuffer -- how many bytes will be left in the Context?
ctx'0 = ctx { ctxBufferPos = bufferPos + xlen,
ctxLengthRemaining = lengthRemaining
}
-- When the existing buffer does not satisfy our appetite, prepare the next buffer and context.
ctx'1 = ctx { ctxCounter = RNG.incrementV $ ctxCounter ctx
, ctxBufferPos = 0
, ctxBuffer = nextBuffer
, ctxLengthRemaining = lengthRemaining
}
where
nextBuffer = crypt' ECB keyValue ecbModeDoesNotUseIV Encrypt payloadValue
where
RNG.Key keyValue = ctxKey ctx
RNG.V payloadValue = ctxCounter ctx
ecbModeDoesNotUseIV = BS.pack $ replicate 16 0 -- we'd use ⊥ but the AES library evaluates it, even for ECB
xlen' = xlen - bytesUsedOfBuffer