sel-0.0.1.0: src/Sel/Hashing/Short.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE TypeApplications #-}
-- |
--
-- Module: Sel.Hashing.Short
-- Description: Short input hashing with the SipHash-2-4 algorithm
-- Copyright: (C) Hécate Moonlight 2023
-- License: BSD-3-Clause
-- Maintainer: The Haskell Cryptography Group
-- Portability: GHC only
module Sel.Hashing.Short
( -- ** Introduction
-- $introduction
ShortHash
-- ** Short-input Hashing
, hashByteString
, hashText
-- *** Conversion
, shortHashToBinary
, shortHashToHexText
, shortHashToHexByteString
-- ** Short Hash Key
, ShortHashKey
, newKey
-- *** Conversion
, shortHashKeyToBinary
, shortHashKeyToHexText
, shortHashKeyToHexByteString
, binaryToShortHashKey
, hexTextToShortHashKey
, hexByteStringToShortHashKey
-- ** Errors
, ShortHashingException (..)
)
where
import Control.Exception (throw)
import Control.Monad (void, when)
import Data.ByteString (StrictByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base16 as Base16
import qualified Data.ByteString.Internal as BS
import qualified Data.ByteString.Unsafe as BS
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import qualified Data.Text.Lazy.Builder as Builder
import Foreign hiding (void)
import Foreign.C (CSize, CUChar, CULLong)
import GHC.Exception (Exception)
import GHC.IO.Handle.Text (memcpy)
import System.IO.Unsafe (unsafeDupablePerformIO)
import qualified Data.Base16.Types as Base16
import Data.Text.Display
import LibSodium.Bindings.ShortHashing
( cryptoShortHashSipHashX24Bytes
, cryptoShortHashSipHashX24KeyBytes
, cryptoShortHashX24
, cryptoShortHashX24KeyGen
)
import Sel.Internal
-- $introduction
--
-- This module provides an API for performant short-input hashing,
-- backed by the [SipHash-2-4](https://en.wikipedia.org/wiki/SipHash) algorithm.
--
-- Short-input hashing functions have a variety of use-cases, such as:
--
-- * Hash Tables
-- * Probabilistic data structures, such as Bloom filters
-- * Integrity checking in interactive protocols
-- | A 128-bit hash of a short input, of size 'cryptoShortHashSipHashX24Bytes'
--
-- @since 0.0.1.0
newtype ShortHash = ShortHash (ForeignPtr CUChar)
-- |
--
-- @since 0.0.1.0
instance Eq ShortHash where
(ShortHash sh1) == (ShortHash sh2) =
unsafeDupablePerformIO $
foreignPtrEq sh1 sh2 cryptoShortHashSipHashX24Bytes
-- |
--
-- @since 0.0.1.0
instance Ord ShortHash where
compare (ShortHash sh1) (ShortHash sh2) =
unsafeDupablePerformIO $
foreignPtrOrd sh1 sh2 cryptoShortHashSipHashX24Bytes
-- |
--
-- @since 0.0.1.0
instance Show ShortHash where
show = Text.unpack . shortHashToHexText
-- |
--
-- @since 0.0.1.0
instance Display ShortHash where
displayBuilder = Builder.fromText . shortHashToHexText
-- | Hash a 'StrictByteString'.
--
-- The same message hashed with the same key will always produce the same output.
--
-- The 'ShortHash' is of length 'cryptoShortHashSipHashX24Bytes'
--
-- @since 0.0.1.0
hashByteString
:: ShortHashKey
-- ^ Random key produced by 'newKey'
-> StrictByteString
-- ^ Data to hash
-> IO ShortHash
hashByteString (ShortHashKey keyFPtr) message =
BS.unsafeUseAsCStringLen message $ \(cString, cStringLen) -> do
shortHashFPtr <- Foreign.mallocForeignPtrBytes (fromIntegral cryptoShortHashSipHashX24Bytes)
Foreign.withForeignPtr keyFPtr $ \keyPtr ->
Foreign.withForeignPtr shortHashFPtr $ \shortHashPtr -> do
result <-
cryptoShortHashX24
shortHashPtr
(Foreign.castPtr cString)
(fromIntegral @Int @CULLong cStringLen)
keyPtr
when (result /= 0) $ throw ShortHashingException
pure $ ShortHash shortHashFPtr
-- | Hash a strict 'Text'.
--
-- The same message hashed with the same key will always produce the same output.
--
-- The 'ShortHash' is of length 'cryptoShortHashSipHashX24Bytes'
--
-- @since 0.0.1.0
hashText
:: ShortHashKey
-- ^ Random key produced by 'newKey'
-> Text
-- ^ UTF-8 encoded data to hash
-> IO ShortHash
hashText key message = hashByteString key (Text.encodeUtf8 message)
-- | Convert a 'ShortHash' to a strict binary 'StrictByteString'.
--
-- @since 0.0.1.0
shortHashToBinary :: ShortHash -> StrictByteString
shortHashToBinary (ShortHash hashFPtr) =
BS.fromForeignPtr
(Foreign.castForeignPtr hashFPtr)
0
(fromIntegral @CSize @Int cryptoShortHashSipHashX24Bytes)
-- | Convert a 'ShortHash' to a hexadecimal-encoded 'StrictByteString'.
--
-- @since 0.0.1.0
shortHashToHexByteString :: ShortHash -> StrictByteString
shortHashToHexByteString = Base16.extractBase16 . Base16.encodeBase16' . shortHashToBinary
-- | Convert a 'ShortHash' to a strict hexadecimal-encoded 'Text'.
--
-- @since 0.0.1.0
shortHashToHexText :: ShortHash -> Text
shortHashToHexText = Base16.extractBase16 . Base16.encodeBase16 . shortHashToBinary
-- | A random key used for hashing, of size 'cryptoShortHashSipHashX24KeyBytes'.
--
-- The same message hashed with the same key will always produce the same output.
--
-- @since 0.0.1.0
newtype ShortHashKey = ShortHashKey (ForeignPtr CUChar)
-- |
--
-- @since 0.0.1.0
instance Eq ShortHashKey where
(ShortHashKey sh1) == (ShortHashKey sh2) =
unsafeDupablePerformIO $
foreignPtrEq sh1 sh2 cryptoShortHashSipHashX24Bytes
-- |
--
-- @since 0.0.1.0
instance Ord ShortHashKey where
compare (ShortHashKey sh1) (ShortHashKey sh2) =
unsafeDupablePerformIO $
foreignPtrOrd sh1 sh2 cryptoShortHashSipHashX24Bytes
-- |
--
-- @since 0.0.1.0
instance Show ShortHashKey where
show = Text.unpack . shortHashKeyToHexText
instance Display ShortHashKey where
displayBuilder = Builder.fromText . shortHashKeyToHexText
-- | Generate a random 'ShortHashKey' of size 'cryptoShortHashSipHashX24KeyBytes'
--
-- @since 0.0.1.0
newKey :: IO ShortHashKey
newKey = do
shortHashKeyForeignPtr <-
Foreign.mallocForeignPtrBytes (fromIntegral cryptoShortHashSipHashX24KeyBytes)
Foreign.withForeignPtr shortHashKeyForeignPtr $ \shortHashKeyPtr ->
void $ cryptoShortHashX24KeyGen shortHashKeyPtr
pure $ ShortHashKey shortHashKeyForeignPtr
-- | Convert a 'ShortHash' to a strict binary 'StrictByteString'.
--
-- @since 0.0.1.0
shortHashKeyToBinary :: ShortHashKey -> StrictByteString
shortHashKeyToBinary (ShortHashKey hashKeyFPtr) =
BS.fromForeignPtr
(Foreign.castForeignPtr hashKeyFPtr)
0
(fromIntegral @CSize @Int cryptoShortHashSipHashX24KeyBytes)
-- | Convert a 'ShortHash' to a hexadecimal-encoded 'StrictByteString'.
--
-- @since 0.0.1.0
shortHashKeyToHexByteString :: ShortHashKey -> StrictByteString
shortHashKeyToHexByteString = Base16.extractBase16 . Base16.encodeBase16' . shortHashKeyToBinary
-- | Convert a 'ShortHash' to a strict hexadecimal-encoded 'Text'.
--
-- @since 0.0.1.0
shortHashKeyToHexText :: ShortHashKey -> Text
shortHashKeyToHexText = Base16.extractBase16 . Base16.encodeBase16 . shortHashKeyToBinary
-- | Convert a binary 'StrictByteString' to a 'ShortHashKey'.
--
-- The input key must be of length 'cryptoShortHashSipHashX24KeyBytes'
--
-- @since 0.0.1.0
binaryToShortHashKey :: StrictByteString -> Maybe ShortHashKey
binaryToShortHashKey binaryKey =
if BS.length binaryKey /= fromIntegral cryptoShortHashSipHashX24KeyBytes
then Nothing
else unsafeDupablePerformIO $ do
BS.unsafeUseAsCString binaryKey $ \cString -> do
shortHashKeyFPtr <- Foreign.mallocForeignPtrBytes (fromIntegral cryptoShortHashSipHashX24KeyBytes)
Foreign.withForeignPtr shortHashKeyFPtr $ \shortHashKeyPtr ->
memcpy shortHashKeyPtr (Foreign.castPtr cString) cryptoShortHashSipHashX24KeyBytes
pure $ Just $ ShortHashKey shortHashKeyFPtr
-- | Convert a strict hexadecimal-encoded 'Text' to a 'ShortHashKey'.
--
-- The input key, once decoded from base16, must be of length 'cryptoShortHashSipHashX24KeyBytes'
--
-- @since 0.0.1.0
hexTextToShortHashKey :: Text -> Maybe ShortHashKey
hexTextToShortHashKey = hexByteStringToShortHashKey . Text.encodeUtf8
-- | Convert a hexadecimal-encoded 'StrictByteString' to a 'ShortHashKey'.
--
-- The input key, once decoded from base16, must be of length 'cryptoShortHashSipHashX24KeyBytes'
--
-- @since 0.0.1.0
hexByteStringToShortHashKey :: StrictByteString -> Maybe ShortHashKey
hexByteStringToShortHashKey hexByteString =
case Base16.decodeBase16Untyped hexByteString of
Right binary -> binaryToShortHashKey binary
Left _ -> Nothing
-- | Exception thrown upon error during hashing by
-- 'hashByteString' or 'hashText'.
--
-- @since 0.0.1.0
data ShortHashingException = ShortHashingException
deriving stock
( Show
-- ^ @since 0.0.1.0
, Eq
-- ^ @since 0.0.1.0
, Ord
-- ^ @since 0.0.1.0
)
deriving anyclass
( Exception
-- ^ @since 0.0.1.0
)
deriving
( Display
-- ^ @since 0.0.1.0
)
via (ShowInstance ShortHashingException)