{-|
Module      : Botan.Low.Hash
Description : Hash Functions and Checksums
Copyright   : (c) 2023-2024, Apotheca Labs
              (c) 2024-2025, Haskell Foundation
License     : BSD-3-Clause
Maintainer  : joris@well-typed.com, leo@apotheca.io
Stability   : experimental
Portability : POSIX

Hash functions are one-way functions, which map data of arbitrary size
to a fixed output length. Most of the hash functions in Botan are designed
to be cryptographically secure, which means that it is computationally
infeasible to create a collision (finding two inputs with the same hash)
or preimages (given a hash output, generating an arbitrary input with the
same hash). But note that not all such hash functions meet their goals,
in particular MD4 and MD5 are trivially broken. However they are still
included due to their wide adoption in various protocols.

Using a hash function is typically split into three stages: initialization,
update, and finalization (often referred to as a IUF interface). The
initialization stage is implicit: after creating a hash function object,
it is ready to process data. Then update is called one or more times.
Calling update several times is equivalent to calling it once with all of
the arguments concatenated. After completing a hash computation (eg using
hashFinal), the internal state is reset to begin hashing a new message.
-}

{-# LANGUAGE OverloadedStrings #-}

module Botan.Low.Hash (

  -- * Hashing
  -- $introduction

  -- * Usage
  -- $usage

    Hash(..)
  , HashName
  , HashDigest
  , withHash
  , hashInit
  , hashDestroy
  , hashName
  , hashBlockSize
  , hashOutputLength
  , hashCopyState
  , hashUpdate
  , hashFinal
  , hashUpdateFinalize
  , hashUpdateFinalizeClear
  , hashClear

  -- * Hash algorithms

  , pattern BLAKE2b
  , blake2b
  , pattern Keccak1600
  , keccak1600
  , pattern GOST_34_11
  , pattern MD4
  , pattern MD5
  , pattern RIPEMD160
  , pattern SHA1
  , pattern SHA224
  , pattern SHA256
  , pattern SHA384
  , pattern SHA512
  , pattern SHA512_256
  , pattern SHA3
  , sha3
  , pattern SHAKE128
  , shake128
  , pattern SHAKE256
  , shake256
  , pattern SM3
  , pattern Skein512
  , skein512
  , pattern Streebog256
  , pattern Streebog512
  , pattern Whirlpool
  , pattern Parallel
  , pattern Comb4P
  , pattern Adler32
  , pattern CRC24
  , pattern CRC32

  -- * Convenience

  , cryptohashes
  , checksums
  , allHashes

  ) where

import           Botan.Bindings.Hash
import           Botan.Low.Error.Internal
import           Botan.Low.Internal.ByteString
import           Botan.Low.Internal.String
import           Botan.Low.Make
import           Botan.Low.Remake
import           Data.ByteString (ByteString)
import           Foreign.C.Types
import           Foreign.ForeignPtr
import           Foreign.Ptr

{- $introduction

A `hash` is deterministic, one-way function suitable for producing a
deterministic, fixed-size digest from an arbitrarily-sized message, which is
used to verify the integrity of the data.

-}

{- $usage

Unless you need a specific `hash`, it is strongly recommended that you use the `SHA3` algorithm.

> import Botan.Low.Hash
> hash <- hashInit SHA3
> message = "Fee fi fo fum!"
> hashUpdate hash message
> digest <- hashFinal hash

You can verify a digest by hashing the message a second time, and comparing the two:

> rehash <- hashInit SHA3
> hashUpdate rehash message
> redigest <- hashFinal rehash
> digest == redigest -- True

You can clear a hash's state, leaving it ready for reuse:

> hashClear hash
> -- Process another message
> hashUpdate hash anotherMessage
> anotherDigest <- hashFinal hash

-}

newtype Hash = MkHash { Hash -> ForeignPtr BotanHashStruct
foreignPtr :: ForeignPtr BotanHashStruct }

withHash     :: Hash -> (BotanHash -> IO a) -> IO a
hashDestroy  :: Hash -> IO ()
createHash   :: (Ptr BotanHash -> IO CInt) -> IO Hash
(Hash -> (BotanHash -> IO a) -> IO a
withHash, Hash -> IO ()
hashDestroy, (Ptr BotanHash -> IO CInt) -> IO Hash
createHash)
    = (Ptr BotanHashStruct -> BotanHash)
-> (BotanHash -> Ptr BotanHashStruct)
-> (ForeignPtr BotanHashStruct -> Hash)
-> (Hash -> ForeignPtr BotanHashStruct)
-> FinalizerPtr BotanHashStruct
-> (Hash -> (BotanHash -> IO a) -> IO a, Hash -> IO (),
    (Ptr BotanHash -> IO CInt) -> IO Hash)
forall botan struct object a.
Storable botan =>
(Ptr struct -> botan)
-> (botan -> Ptr struct)
-> (ForeignPtr struct -> object)
-> (object -> ForeignPtr struct)
-> FinalizerPtr struct
-> (object -> (botan -> IO a) -> IO a, object -> IO (),
    (Ptr botan -> IO CInt) -> IO object)
mkBindings
        Ptr BotanHashStruct -> BotanHash
MkBotanHash (.ptr)
        ForeignPtr BotanHashStruct -> Hash
MkHash (.foreignPtr)
        FinalizerPtr BotanHashStruct
botan_hash_destroy

type HashName = ByteString

pattern BLAKE2b
    ,   Keccak1600
    ,   GOST_34_11
    ,   MD4
    ,   MD5
    ,   RIPEMD160
    ,   SHA1
    ,   SHA224
    ,   SHA256
    ,   SHA384
    ,   SHA512
    ,   SHA512_256
    ,   SHA3
    ,   SHAKE128
    ,   SHAKE256
    ,   SM3
    ,   Skein512
    ,   Streebog256
    ,   Streebog512
    ,   Whirlpool
    ,   Parallel
    ,   Comb4P
    ,   Adler32
    ,   CRC24
    ,   CRC32
    :: HashName

pattern $bBLAKE2b :: HashName
$mBLAKE2b :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
BLAKE2b         = BOTAN_HASH_BLAKE2B
-- TODO: function
-- sz is digest size in bits, must be 1-64 bytes, eg: 8-512 in multiples of 8
blake2b :: (Ord a, Num a, Show a) => a -> HashName
blake2b :: forall a. (Ord a, Num a, Show a) => a -> HashName
blake2b a
sz | a
sz a -> a -> Bool
forall a. Ord a => a -> a -> Bool
<= a
512 = HashName
BLAKE2b HashName -> HashName -> HashName
forall a. (IsString a, Semigroup a) => a -> a -> a
/$ a -> HashName
forall a. Show a => a -> HashName
showBytes a
sz
blake2b a
_ = [Char] -> HashName
forall a. HasCallStack => [Char] -> a
error [Char]
"Invalid BLAKE2b variant"
pattern $bKeccak1600 :: HashName
$mKeccak1600 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
Keccak1600     = BOTAN_HASH_KECCAK_1600
-- TODO: function or pattern
keccak1600 :: (Eq a, Num a, Show a) => a -> HashName
keccak1600 :: forall a. (Eq a, Num a, Show a) => a -> HashName
keccak1600 a
n | a
n a -> [a] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [a
224, a
256, a
384, a
512] = HashName
Keccak1600 HashName -> HashName -> HashName
forall a. (IsString a, Semigroup a) => a -> a -> a
/$ a -> HashName
forall a. Show a => a -> HashName
showBytes a
n
keccak1600 a
_ = [Char] -> HashName
forall a. HasCallStack => [Char] -> a
error [Char]
"Invalid Keccak-1600 variant"
-- pattern Keccak1600_224 = "Keccak-1600(224)"
-- pattern Keccak1600_256 = "Keccak-1600(256)"
-- pattern Keccak1600_384 = "Keccak-1600(384)"
-- pattern Keccak1600_512 = "Keccak-1600(512)"
pattern $bGOST_34_11 :: HashName
$mGOST_34_11 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
GOST_34_11  = BOTAN_HASH_GOST_34_11
pattern $bMD4 :: HashName
$mMD4 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
MD4         = BOTAN_HASH_MD4
pattern $bMD5 :: HashName
$mMD5 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
MD5         = BOTAN_HASH_MD5
pattern $bRIPEMD160 :: HashName
$mRIPEMD160 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
RIPEMD160   = BOTAN_HASH_RIPEMD_160
pattern $bSHA1 :: HashName
$mSHA1 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
SHA1        = BOTAN_HASH_SHA1
pattern $bSHA224 :: HashName
$mSHA224 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
SHA224      = BOTAN_HASH_SHA_224
pattern $bSHA256 :: HashName
$mSHA256 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
SHA256      = BOTAN_HASH_SHA_256
pattern $bSHA384 :: HashName
$mSHA384 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
SHA384      = BOTAN_HASH_SHA_384
pattern $bSHA512 :: HashName
$mSHA512 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
SHA512      = BOTAN_HASH_SHA_512
pattern $bSHA512_256 :: HashName
$mSHA512_256 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
SHA512_256  = BOTAN_HASH_SHA_512_256
pattern $bSHA3 :: HashName
$mSHA3 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
SHA3        = BOTAN_HASH_SHA_3
-- TODO: function or pattern
sha3 :: (Eq a, Num a, Show a) => a -> HashName
sha3 :: forall a. (Eq a, Num a, Show a) => a -> HashName
sha3 a
n | a
n a -> [a] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [a
224, a
256, a
384, a
512] = HashName
SHA3 HashName -> HashName -> HashName
forall a. (IsString a, Semigroup a) => a -> a -> a
/$ a -> HashName
forall a. Show a => a -> HashName
showBytes a
n
sha3 a
_ = [Char] -> HashName
forall a. HasCallStack => [Char] -> a
error [Char]
"Invalid SHA-3 variant"
-- pattern SHA3_224 = "SHA-3(224)"
-- pattern SHA3_256 = "SHA-3(256)"
-- pattern SHA3_384 = "SHA-3(384)"
-- pattern SHA3_512 = "SHA-3(512)"
pattern $bSHAKE128 :: HashName
$mSHAKE128 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
SHAKE128       = BOTAN_HASH_SHAKE_128
-- TODO: function
shake128 :: Show a => a -> HashName
shake128 :: forall a. Show a => a -> HashName
shake128 a
sz = HashName
SHAKE128 HashName -> HashName -> HashName
forall a. (IsString a, Semigroup a) => a -> a -> a
/$ a -> HashName
forall a. Show a => a -> HashName
showBytes a
sz
pattern $bSHAKE256 :: HashName
$mSHAKE256 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
SHAKE256       = BOTAN_HASH_SHAKE_256
-- TODO: function
shake256 :: Show a => a -> HashName
shake256 :: forall a. Show a => a -> HashName
shake256 a
sz = HashName
SHAKE256 HashName -> HashName -> HashName
forall a. (IsString a, Semigroup a) => a -> a -> a
/$ a -> HashName
forall a. Show a => a -> HashName
showBytes a
sz
pattern $bSM3 :: HashName
$mSM3 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
SM3             = BOTAN_HASH_SM3
pattern $bSkein512 :: HashName
$mSkein512 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
Skein512       = BOTAN_HASH_SKEIN_512
-- TODO: function
skein512 :: Show a => a -> HashName -> HashName
skein512 :: forall a. Show a => a -> HashName -> HashName
skein512 a
sz HashName
salt = HashName
Skein512 HashName -> HashName -> HashName
forall a. (IsString a, Semigroup a) => a -> a -> a
/$ a -> HashName
forall a. Show a => a -> HashName
showBytes a
sz HashName -> HashName -> HashName
forall a. Semigroup a => a -> a -> a
<> HashName
"," HashName -> HashName -> HashName
forall a. Semigroup a => a -> a -> a
<> HashName
salt
pattern $bStreebog256 :: HashName
$mStreebog256 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
Streebog256    = BOTAN_HASH_STREEBOG_256
pattern $bStreebog512 :: HashName
$mStreebog512 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
Streebog512    = BOTAN_HASH_STREEBOG_512
pattern $bWhirlpool :: HashName
$mWhirlpool :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
Whirlpool       = BOTAN_HASH_WHIRLPOOL
pattern $bParallel :: HashName
$mParallel :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
Parallel        = BOTAN_HASH_STRAT_PARALLEL
-- TODO: function
-- parallel a b =  Parallel /$ a <> "," <> b
-- TODO: function
-- comb4P a b =  Comb4P /$ a <> "," <> b
pattern $bComb4P :: HashName
$mComb4P :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
Comb4P          = BOTAN_HASH_STRAT_COMB4P
pattern $bAdler32 :: HashName
$mAdler32 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
Adler32         = BOTAN_CHECKSUM_ADLER32
pattern $bCRC24 :: HashName
$mCRC24 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
CRC24           = BOTAN_CHECKSUM_CRC24
pattern $bCRC32 :: HashName
$mCRC32 :: forall {r}. HashName -> ((# #) -> r) -> ((# #) -> r) -> r
CRC32           = BOTAN_CHECKSUM_CRC32

cryptohashes :: [HashName]
cryptohashes :: [HashName]
cryptohashes =
    [ HashName
BLAKE2b
    -- , "BLAKE2b(128)"
    -- , "BLAKE2b(256)"
    -- , "BLAKE2b(512)"
    , HashName
GOST_34_11
    , HashName
Keccak1600
    -- , "Keccak-1600(224)"
    -- , "Keccak-1600(256)"
    -- , "Keccak-1600(384)"
    -- , "Keccak-1600(512)"
    , HashName
MD4
    , HashName
MD5
    , HashName
RIPEMD160
    , HashName
SHA1
    , HashName
SHA224
    , HashName
SHA256
    , HashName
SHA384
    , HashName
SHA512
    , HashName
SHA512_256
    , HashName
SHA3
    -- , "SHA-3(224)"
    -- , "SHA-3(256)"
    -- , "SHA-3(384)"
    -- , "SHA-3(512)"
    -- NOTE: SHAKE-128 has no default value, a parameter *MUST* be supplied
    , forall a. Show a => a -> HashName
shake128 @Int Int
128
    -- , "SHAKE-128(128)"
    -- , "SHAKE-128(256)"
    -- , "SHAKE-128(512)"
    -- NOTE: SHAKE-256 has no default value, a parameter *MUST* be supplied
    , forall a. Show a => a -> HashName
shake256 @Int Int
128
    -- , "SHAKE-256(128)"
    -- , "SHAKE-256(256)"
    -- , "SHAKE-256(512)"
    , HashName
SM3
    , HashName
Skein512
    -- , "Skein-512(128)"
    -- , "Skein-512(256)"
    -- , "Skein-512(512)"
    , HashName
Streebog256
    , HashName
Streebog512
    , HashName
Whirlpool
    ]

-- TODO:
-- hashStrategies :: [HashName]
-- hashStrategies = undefined

checksums :: [HashName]
checksums :: [HashName]
checksums =
    [ HashName
Adler32
    , HashName
CRC24
    , HashName
CRC32
    ]

allHashes :: [HashName]
allHashes :: [HashName]
allHashes = [HashName]
cryptohashes [HashName] -> [HashName] -> [HashName]
forall a. [a] -> [a] -> [a]
++ [HashName]
checksums

type HashDigest = ByteString

hashInit
    :: HashName -- ^ __hash_name__: name of the hash function, e.g., "SHA-384"
    -> IO Hash  -- ^ __hash__: hash object
hashInit :: HashName -> IO Hash
hashInit = ((Ptr BotanHash -> IO CInt) -> IO Hash)
-> (Ptr BotanHash -> ConstPtr CChar -> IO CInt)
-> HashName
-> IO Hash
forall botan object.
((Ptr botan -> IO CInt) -> IO object)
-> (Ptr botan -> ConstPtr CChar -> IO CInt)
-> HashName
-> IO object
mkCreateObjectCString (Ptr BotanHash -> IO CInt) -> IO Hash
createHash (\ Ptr BotanHash
out ConstPtr CChar
name -> Ptr BotanHash -> ConstPtr CChar -> Word32 -> IO CInt
botan_hash_init Ptr BotanHash
out ConstPtr CChar
name Word32
0)

hashName
    :: Hash             -- ^ __hash__: the object to read
    -> IO HashDigest    -- ^ __name__: output buffer
hashName :: Hash -> IO HashName
hashName = WithPtr Hash BotanHash
-> GetCString BotanHash CChar -> Hash -> IO HashName
forall typ ptr byte.
WithPtr typ ptr -> GetCString ptr byte -> typ -> IO HashName
mkGetCString Hash -> (BotanHash -> IO a) -> IO a
WithPtr Hash BotanHash
withHash GetCString BotanHash CChar
botan_hash_name

-- NOTE: This does the correct thing - see C++ docs:
--  Return a newly allocated HashFunction object of the same type as this one,
--  whose internal state matches the current state of this.

hashCopyState
    :: Hash     -- ^ __source__: source hash object
    -> IO Hash  -- ^ __dest__: destination hash object
hashCopyState :: Hash -> IO Hash
hashCopyState Hash
source = Hash -> (BotanHash -> IO Hash) -> IO Hash
WithPtr Hash BotanHash
withHash Hash
source ((BotanHash -> IO Hash) -> IO Hash)
-> (BotanHash -> IO Hash) -> IO Hash
forall a b. (a -> b) -> a -> b
$ \ BotanHash
sourcePtr -> do
    (Ptr BotanHash -> IO CInt) -> IO Hash
createHash ((Ptr BotanHash -> IO CInt) -> IO Hash)
-> (Ptr BotanHash -> IO CInt) -> IO Hash
forall a b. (a -> b) -> a -> b
$ \ Ptr BotanHash
dest -> Ptr BotanHash -> BotanHash -> IO CInt
botan_hash_copy_state Ptr BotanHash
dest BotanHash
sourcePtr

hashClear
    :: Hash -- ^ __hash__: hash object
    -> IO ()
hashClear :: Hash -> IO ()
hashClear =  WithPtr Hash BotanHash -> (BotanHash -> IO CInt) -> Hash -> IO ()
forall typ ptr. WithPtr typ ptr -> Action ptr -> typ -> IO ()
mkAction Hash -> (BotanHash -> IO a) -> IO a
WithPtr Hash BotanHash
withHash BotanHash -> IO CInt
botan_hash_clear

hashBlockSize
    :: Hash     -- ^ __hash__: hash object
    -> IO Int   -- ^ __block_size__: output buffer to hold the hash function block size
hashBlockSize :: Hash -> IO Int
hashBlockSize = WithPtr Hash BotanHash -> GetSize BotanHash -> Hash -> IO Int
forall typ ptr. WithPtr typ ptr -> GetSize ptr -> typ -> IO Int
mkGetSize Hash -> (BotanHash -> IO a) -> IO a
WithPtr Hash BotanHash
withHash GetSize BotanHash
botan_hash_block_size

hashOutputLength
    :: Hash     -- ^ __hash__: hash object
    -> IO Int   -- ^ __block_size__: output buffer to hold the hash function output length
hashOutputLength :: Hash -> IO Int
hashOutputLength = WithPtr Hash BotanHash -> GetSize BotanHash -> Hash -> IO Int
forall typ ptr. WithPtr typ ptr -> GetSize ptr -> typ -> IO Int
mkGetSize Hash -> (BotanHash -> IO a) -> IO a
WithPtr Hash BotanHash
withHash GetSize BotanHash
botan_hash_output_length

hashUpdate
    :: Hash         -- ^ __hash__: hash object
    -> ByteString   -- ^ __in__: input buffer
    -> IO ()
hashUpdate :: Hash -> HashName -> IO ()
hashUpdate = WithPtr Hash BotanHash
-> (BotanHash -> ConstPtr Word8 -> CSize -> IO CInt)
-> Hash
-> HashName
-> IO ()
forall object botan.
(forall a. object -> (botan -> IO a) -> IO a)
-> (botan -> ConstPtr Word8 -> CSize -> IO CInt)
-> object
-> HashName
-> IO ()
mkWithObjectSetterCBytesLen Hash -> (BotanHash -> IO a) -> IO a
WithPtr Hash BotanHash
withHash BotanHash -> ConstPtr Word8 -> CSize -> IO CInt
botan_hash_update

hashFinal
    :: Hash             -- ^ __hash__: hash object
    -> IO HashDigest    -- ^ __out[]__: output buffer
hashFinal :: Hash -> IO HashName
hashFinal Hash
hash = Hash -> (BotanHash -> IO HashName) -> IO HashName
WithPtr Hash BotanHash
withHash Hash
hash ((BotanHash -> IO HashName) -> IO HashName)
-> (BotanHash -> IO HashName) -> IO HashName
forall a b. (a -> b) -> a -> b
$ \ BotanHash
hashPtr -> do
    Int
sz <- Hash -> IO Int
hashOutputLength Hash
hash
    Int -> (Ptr Word8 -> IO ()) -> IO HashName
forall byte. Int -> (Ptr byte -> IO ()) -> IO HashName
allocBytes Int
sz ((Ptr Word8 -> IO ()) -> IO HashName)
-> (Ptr Word8 -> IO ()) -> IO HashName
forall a b. (a -> b) -> a -> b
$ \ Ptr Word8
digestPtr -> do
        HasCallStack => IO CInt -> IO ()
IO CInt -> IO ()
throwBotanIfNegative_ (IO CInt -> IO ()) -> IO CInt -> IO ()
forall a b. (a -> b) -> a -> b
$ BotanHash -> Ptr Word8 -> IO CInt
botan_hash_final BotanHash
hashPtr Ptr Word8
digestPtr

-- TODO:
-- pkcsHashId
--     :: ByteString       -- ^ __hash_name__
--     -> IO ByteString    -- ^ __pkcs_id[]__
-- pkcsHashId = undefined


-- Convenience

hashUpdateFinalize :: Hash -> ByteString -> IO HashDigest
hashUpdateFinalize :: Hash -> HashName -> IO HashName
hashUpdateFinalize Hash
ctx HashName
bytes = do
    Hash -> HashName -> IO ()
hashUpdate Hash
ctx HashName
bytes
    Hash -> IO HashName
hashFinal Hash
ctx

hashUpdateFinalizeClear :: Hash -> ByteString -> IO HashDigest
hashUpdateFinalizeClear :: Hash -> HashName -> IO HashName
hashUpdateFinalizeClear Hash
ctx HashName
bytes = do
    HashName
dg <- Hash -> HashName -> IO HashName
hashUpdateFinalize Hash
ctx HashName
bytes
    Hash -> IO ()
hashClear Hash
ctx
    HashName -> IO HashName
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return HashName
dg
-- Or: hashUpdateFinalize ctx bytes <* hashClear ctx