packages feed

ppad-sha512-0.2.5: lib/Crypto/Hash/SHA512/Pure.hs

{-# OPTIONS_HADDOCK hide #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}

-- |
-- Module: Crypto.Hash.SHA512.Pure
-- Copyright: (c) 2024 Jared Tobin
-- License: MIT
-- Maintainer: Jared Tobin <jared@ppad.tech>
--
-- Pure Haskell SHA-512 and HMAC-SHA512 implementations for strict
-- ByteStrings, used when the ARM cryptographic extensions are
-- unavailable.

module Crypto.Hash.SHA512.Pure (
    hash
  , hmac
  , _hmac_rr
  , _hmac_rsb
  ) where

import qualified Data.ByteString as BS
import qualified Data.ByteString.Internal as BI
import qualified Data.ByteString.Unsafe as BU
import Data.Word (Word8, Word64)
import Foreign.Ptr (Ptr)
import qualified GHC.Exts as Exts
import Crypto.Hash.SHA512.Internal

-- utilities ------------------------------------------------------------------

fi :: (Integral a, Num b) => a -> b
fi = fromIntegral
{-# INLINE fi #-}

-- hash -----------------------------------------------------------------------

-- | Compute a condensed representation of a strict bytestring via
--   SHA-512, using the pure Haskell implementation.
hash :: BS.ByteString -> BS.ByteString
hash m = cat (_hash 0 (iv ()) m)
{-# INLINABLE hash #-}

_hash
  :: Word64        -- ^ extra prefix length for padding calculations
  -> Registers     -- ^ register state
  -> BS.ByteString -- ^ input
  -> Registers
_hash el rs m@(BI.PS _ _ l) = do
  let !state = _hash_blocks rs m
      !fin@(BI.PS _ _ ll) = BU.unsafeDrop (l - l `rem` 128) m
      !total = el + fi l
  if   ll < 112
  then
    let !ult = parse_pad1 fin total
    in  update state ult
  else
    let !(# pen, ult #) = parse_pad2 fin total
    in  update (update state pen) ult
{-# INLINABLE _hash #-}

_hash_blocks
  :: Registers     -- ^ state
  -> BS.ByteString -- ^ input
  -> Registers
_hash_blocks rs m@(BI.PS _ _ l) = loop rs 0 where
  loop !acc !j
    | j + 128 > l = acc
    | otherwise   =
        let !nacc = update acc (parse m j)
        in  loop nacc (j + 128)
{-# INLINABLE _hash_blocks #-}

-- hmac ----------------------------------------------------------------------

-- | Produce a message authentication code for a strict bytestring,
--   based on the provided (strict, bytestring) key, via HMAC-SHA512,
--   using the pure Haskell implementation.
hmac :: BS.ByteString -> BS.ByteString -> BS.ByteString
hmac k m = cat (_hmac (prep_key k) m)
{-# INLINABLE hmac #-}

prep_key :: BS.ByteString -> Block
prep_key k@(BI.PS _ _ l)
    | l > 128   = parse_key (hash k)
    | otherwise = parse_key k
{-# INLINABLE prep_key #-}

_hmac
  :: Block          -- ^ padded key
  -> BS.ByteString  -- ^ message
  -> Registers
_hmac k m =
  let !rs0   = update (iv ()) (xor k (Exts.wordToWord64# 0x3636363636363636##))
      !block = pad_registers_with_length (_hash 128 rs0 m)
      !rs1   = update (iv ()) (xor k (Exts.wordToWord64# 0x5C5C5C5C5C5C5C5C##))
  in  update rs1 block
{-# INLINABLE _hmac #-}

-- the following functions are useful when we want to avoid allocating certain
-- components of the HMAC key and message on the heap.

-- Computes hmac(k, v) when k and v are Registers.
--
-- The 64-byte result is written to the destination pointer.
_hmac_rr
  :: Ptr Word64    -- ^ destination (8 Word64s)
  -> Ptr Word64    -- ^ scratch block buffer (unused)
  -> Registers     -- ^ key
  -> Registers     -- ^ message
  -> IO ()
_hmac_rr rp _ k m = do
  let !key   = pad_registers k
      !block = pad_registers_with_length m
      !rs    = _hmac_bb key block
  poke_registers rp rs
{-# INLINABLE _hmac_rr #-}

_hmac_bb
  :: Block     -- ^ key
  -> Block     -- ^ message
  -> Registers
_hmac_bb k m =
  let !rs0   = update (iv ()) (xor k (Exts.wordToWord64# 0x3636363636363636##))
      !rs1   = update rs0 m
      !inner = pad_registers_with_length rs1
      !rs2   = update (iv ()) (xor k (Exts.wordToWord64# 0x5C5C5C5C5C5C5C5C##))
  in  update rs2 inner
{-# INLINABLE _hmac_bb #-}

-- Calculate hmac(k, m) where m is the concatenation of v (registers), a
-- separator byte, and a ByteString. This avoids allocating 'v' on the
-- heap.
--
-- The 64-byte result is written to the destination pointer.
_hmac_rsb
  :: Ptr Word64    -- ^ destination pointer (8 x Word64)
  -> Ptr Word64    -- ^ scratch block pointer (unused)
  -> Registers     -- ^ k
  -> Registers     -- ^ v
  -> Word8         -- ^ separator byte
  -> BS.ByteString -- ^ data
  -> IO ()
_hmac_rsb rp _ k v sep dat = do
  let !key   = pad_registers k
      !rs0   = update (iv ())
                 (xor key (Exts.wordToWord64# 0x3636363636363636##))
      !inner = _hash_vsb 128 rs0 v sep dat
      !block = pad_registers_with_length inner
      !rs1   = update (iv ())
                 (xor key (Exts.wordToWord64# 0x5C5C5C5C5C5C5C5C##))
      !rs    = update rs1 block
  poke_registers rp rs
{-# INLINABLE _hmac_rsb #-}

-- hash(v || sep || dat) with a custom initial state and extra
-- prefix length. used for producing a more specialized hmac.
_hash_vsb
  :: Word64        -- ^ extra prefix length
  -> Registers     -- ^ initial state
  -> Registers     -- ^ v
  -> Word8         -- ^ sep
  -> BS.ByteString -- ^ dat
  -> Registers
_hash_vsb el rs0 v sep dat@(BI.PS _ _ l)
  | l >= 63 =
      -- first block is complete
      let !b0    = parse_vsb v sep dat
          !rs1   = update rs0 b0
          !rest  = BU.unsafeDrop 63 dat
          !rlen  = l - 63
          !rs2   = _hash_blocks rs1 rest
          !flen  = rlen `rem` 128
          !fin   = BU.unsafeDrop (rlen - flen) rest
          !total = el + 65 + fi l
      in  if   flen < 112
          then update rs2 (parse_pad1 fin total)
          else let !(# pen, ult #) = parse_pad2 fin total
               in  update (update rs2 pen) ult
  | otherwise =
      -- message < 128 bytes, goes straight to padding
      let !total = el + 65 + fi l
      in  if   65 + l < 112
          then update rs0 (parse_pad1_vsb v sep dat total)
          else let !(# pen, ult #) = parse_pad2_vsb v sep dat total
               in  update (update rs0 pen) ult
{-# INLINABLE _hash_vsb #-}