{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE Trustworthy #-}
{-# OPTIONS_HADDOCK prune #-}
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
data RNGError = BadDiversifierLen | BadMaxLen | BadReqLen deriving (Show)
data Context = Context { ctxBuffer :: BS.ByteString
, ctxBufferPos :: Word64
, ctxLengthRemaining :: Word64
, ctxKey :: RNG.Key
, ctxCounter :: RNG.V
} deriving (Eq)
newtype Diversifier = Diversifier BS.ByteString deriving Show
createDiversifier :: (MonadError RNGError m) => BS.ByteString -> m Diversifier
createDiversifier bs
| BS.length bs /= 8 = throwError BadDiversifierLen
| otherwise = return $ Diversifier bs
newtype MaxLen = MaxLen Word64
maxLen :: (MonadError RNGError m) => Word64 -> m MaxLen
maxLen n
| n < 0 || n > 0x100000000 = throwError BadMaxLen
| otherwise = return $ MaxLen n
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
, ctxBufferPos = 16
, ctxLengthRemaining = maxLength
, ctxKey = RNG.createKey seed
, ctxCounter = createCounter
}
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)))
seedexpander :: (MonadError RNGError m) => Context -> Word64 -> m (BS.ByteString, Context)
seedexpander ctx xlen
| xlen >= ctxLengthRemaining ctx = throwError BadReqLen
| xlen < 0 = return (BS.pack [], ctx)
| xlen <= (16 - bufferPos) = return (existingBufferResult, ctx'0)
| otherwise = do
(result', ctx') <- seedexpander ctx'1 xlen'
return (restOfExistingBuffer <> result', ctx')
where
bufferPos = ctxBufferPos ctx
bytesUsedOfBuffer = min (16 - bufferPos) (min xlen 16)
restOfExistingBuffer = BS.drop (fromIntegral bufferPos) (ctxBuffer ctx)
existingBufferResult = BS.take (fromIntegral bytesUsedOfBuffer) restOfExistingBuffer
lengthRemaining = ctxLengthRemaining ctx - fromIntegral bytesUsedOfBuffer
ctx'0 = ctx { ctxBufferPos = bufferPos + xlen,
ctxLengthRemaining = lengthRemaining
}
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
xlen' = xlen - bytesUsedOfBuffer