packages feed

hashes-0.1.0.0: src/Data/Hash/SipHash.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE UnboxedTuples #-}

-- |
-- Module: Data.Hash.SipHash
-- Copyright: Copyright © 2021 Lars Kuhtz <lakuhtz@gmail.com>
-- License: MIT
-- Maintainer: Lars Kuhtz <lakuhtz@gmail.com>
-- Stability: experimental
--
module Data.Hash.SipHash
( sipHash
, sipHash13
, sipHash24
, sipHash48
, sipHashCD

-- * Utils
, module Data.Hash.Utils
) where

import Control.Monad

import Data.Bits
import Data.Function
import Data.Word

import Foreign.Marshal.Utils
import Foreign.Storable

import GHC.Ptr

import Prelude hiding (drop, length, null, splitAt, take)

-- internal modules

import Data.Hash.Utils

-- -------------------------------------------------------------------------- --
-- SipHash

-- | SipHash, with recommended default parameters of c=2 and c=4.
--
-- The first and second argument is the 128 bit key, represented as two 64 bit
-- words.
--
sipHash
    :: Word64
    -> Word64
    -> Ptr Word8
    -> Int
    -> IO Word64
sipHash = sipHash24
{-# INLINE sipHash #-}

-- | SipHash-2-4
--
-- The first and second argument is the 128 bit key, represented as two 64 bit
-- words.
--
sipHash24
    :: Word64
    -> Word64
    -> Ptr Word8
    -> Int
    -> IO Word64
sipHash24 = sipHashInternal rounds2 rounds4
{-# INLINE sipHash24 #-}

-- | SipHash-1-3
--
-- The first and second argument is the 128 bit key, represented as two 64 bit
-- words.
--
sipHash13
    :: Word64
    -> Word64
    -> Ptr Word8
    -> Int
    -> IO Word64
sipHash13 = sipHashInternal rounds1 rounds3
{-# INLINE sipHash13 #-}

-- | SipHash-4-8
--
-- The first and second argument is the 128 bit key, represented as two 64 bit
-- words.
--
sipHash48
    :: Word64
    -> Word64
    -> Ptr Word8
    -> Int
    -> IO Word64
sipHash48 = sipHashInternal rounds4 rounds8
{-# INLINE sipHash48 #-}

-- | Generic SipHash with c rounds per block and d finalization rounds.
--
-- The first and second argument is the 128 bit key, represented as two 64 bit
-- words.
--
sipHashCD
    :: Int
    -> Int
    -> Word64
    -> Word64
    -> Ptr Word8
    -> Int
    -> IO Word64
sipHashCD c d = sipHashInternal (rounds c) (rounds d)
{-# INLINE sipHashCD #-}

-- -------------------------------------------------------------------------- --
-- Generic SipHash

data S = S
    {-# UNPACK #-} !Word64
    {-# UNPACK #-} !Word64
    {-# UNPACK #-} !Word64
    {-# UNPACK #-} !Word64

type Round = Word64 -> Word64 -> Word64 -> Word64 -> (# Word64, Word64, Word64, Word64 #)

sipHashInternal
    :: Round
    -> Round
    -> Word64
    -> Word64
    -> Ptr Word8
    -> Int
    -> IO Word64
sipHashInternal cRound dRound !k0 !k1 !ptr !len = do

    -- loop
    (S !v0 !v1 !v2 !v3) <- loop i0 i1 i2 i3 (castPtr ptr) len

    -- end
    let (!off, !r) = quotRem len 8
    w <- ptrToWord64 (plusPtr ptr (off * 8)) r
    let !b = shiftL (fromIntegral len) 56 .|. w
        (# !v0', !v1', !v2', !v3' #) = cRound v0 v1 v2 (v3 `xor` b)
        (# !v0'', !v1'', !v2'', !v3'' #) = dRound (v0' `xor` b) v1' (v2' `xor` 0xff) v3'
    return $! v0'' `xor` v1'' `xor` v2'' `xor` v3''

  where

    loop !v0 !v1 !v2 !v3 !p !l
        | l < 8 = return $ S v0 v1 v2 v3
        | otherwise = do
            !m <- peek p
            let (# v0', v1', v2', v3' #) = cRound v0 v1 v2 (v3 `xor` m)
            loop (v0' `xor` m) v1' v2' v3' (plusPtr p 8) (l - 8)

    !i0 = 0x736f6d6570736575 `xor` k0
    !i1 = 0x646f72616e646f6d `xor` k1
    !i2 = 0x6c7967656e657261 `xor` k0
    !i3 = 0x7465646279746573 `xor` k1
    {-# INLINE i0 #-}
    {-# INLINE i1 #-}
    {-# INLINE i2 #-}
    {-# INLINE i3 #-}
{-# INLINE sipHashInternal #-}

ptrToWord64 :: Ptr Word64 -> Int -> IO Word64
ptrToWord64 _ 0 = pure 0
ptrToWord64 !p 1 = fromIntegral <$!> peek @Word8 (castPtr p)
ptrToWord64 !p 2 = fromIntegral <$!> peek @Word16 (castPtr p)
ptrToWord64 !p 4 = fromIntegral <$!> peek @Word32 (castPtr p)
ptrToWord64 !p !i = with @Word64 0 $ \p' -> do
        -- using 'with' within unsafeDupablePerformIO is probably safe because
        -- with uses 'alloca', which guarantees that the memory is released
        -- when computation is abondended before being terminated.
    copyBytes p' p i
    peek p'
{-# INLINE ptrToWord64 #-}

rounds1 :: Word64 -> Word64 -> Word64 -> Word64 -> (# Word64, Word64, Word64, Word64 #)
rounds1 !v0 !v1 !v2 !v3 = sipRound v0 v1 v2 v3
{-# INLINE rounds1 #-}

rounds2 :: Word64 -> Word64 -> Word64 -> Word64 -> (# Word64, Word64, Word64, Word64 #)
rounds2 !v0 !v1 !v2 !v3 =
    let (# !v0', !v1', !v2', !v3' #) = sipRound v0 v1 v2 v3
    in sipRound v0' v1' v2' v3'
{-# INLINE rounds2 #-}

rounds3 :: Word64 -> Word64 -> Word64 -> Word64 -> (# Word64, Word64, Word64, Word64 #)
rounds3 !v0 !v1 !v2 !v3 =
    let (# !v0', !v1', !v2', !v3' #) = sipRound v0 v1 v2 v3
        (# !v0'', !v1'', !v2'', !v3'' #) = sipRound v0' v1' v2' v3'
    in sipRound v0'' v1'' v2'' v3''
{-# INLINE rounds3 #-}

rounds4 :: Word64 -> Word64 -> Word64 -> Word64 -> (# Word64, Word64, Word64, Word64 #)
rounds4 !v0 !v1 !v2 !v3 =
    let (# !v0', !v1', !v2', !v3' #) = sipRound v0 v1 v2 v3
        (# !v0'', !v1'', !v2'', !v3'' #) = sipRound v0' v1' v2' v3'
        (# !v0''', !v1''', !v2''', !v3''' #) = sipRound v0'' v1'' v2'' v3''
    in sipRound v0''' v1''' v2''' v3'''
{-# INLINE rounds4 #-}

rounds8 :: Word64 -> Word64 -> Word64 -> Word64 -> (# Word64, Word64, Word64, Word64 #)
rounds8 !v0 !v1 !v2 !v3 =
    let (# !v0', !v1', !v2', !v3' #) = sipRound v0 v1 v2 v3
        (# !v0'', !v1'', !v2'', !v3'' #) = sipRound v0' v1' v2' v3'
        (# !v0''', !v1''', !v2''', !v3''' #) = sipRound v0'' v1'' v2'' v3''
        (# !v0'''', !v1'''', !v2'''', !v3'''' #) = sipRound v0''' v1''' v2''' v3'''
        (# !v0''''', !v1''''', !v2''''', !v3''''' #) = sipRound v0'''' v1'''' v2'''' v3''''
        (# !v0'''''', !v1'''''', !v2'''''', !v3'''''' #) = sipRound v0''''' v1''''' v2''''' v3'''''
        (# !v0''''''', !v1''''''', !v2''''''', !v3''''''' #) = sipRound v0'''''' v1'''''' v2'''''' v3''''''
    in sipRound v0''''''' v1''''''' v2''''''' v3'''''''
{-# INLINE rounds8 #-}

rounds :: Int -> Word64 -> Word64 -> Word64 -> Word64 -> (# Word64, Word64, Word64, Word64 #)
rounds 1 !v0 !v1 !v2 !v3 = rounds1 v0 v1 v2 v3
rounds 2 !v0 !v1 !v2 !v3 = rounds2 v0 v1 v2 v3
rounds 3 !v0 !v1 !v2 !v3 = rounds3 v0 v1 v2 v3
rounds 4 !v0 !v1 !v2 !v3 = rounds4 v0 v1 v2 v3
rounds 8 !v0 !v1 !v2 !v3 = rounds8 v0 v1 v2 v3
rounds !c !v0 !v1 !v2 !v3 = case sipRound v0 v1 v2 v3 of
    (# v0', v1', v2', v3' #) -> rounds (c - 1) v0' v1' v2' v3'
{-# INLINE rounds #-}

sipRound :: Word64 -> Word64 -> Word64 -> Word64 -> (# Word64, Word64, Word64, Word64 #)
sipRound !v0 !v1 !v2 !v3 = (# v0''', v1'''', v2''', v3'''' #)
    where
    !v0' = v0 + v1
    !v2' = v2 + v3
    !v1' = v1 `rotateL` 13
    !v3' = v3 `rotateL` 16
    !v1'' = v1' `xor` v0'
    !v3'' = v3' `xor` v2'
    !v0'' = v0' `rotateL` 32
    !v2'' = v2' + v1''
    !v0''' = v0'' + v3''
    !v1''' = v1'' `rotateL` 17
    !v3''' = v3'' `rotateL` 21
    !v1'''' = v1''' `xor` v2''
    !v3'''' = v3''' `xor` v0'''
    !v2''' = v2'' `rotateL` 32
{-# INLINE sipRound #-}