packages feed

blake3-0.3: lib/BLAKE3.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

-- | Haskell bindings to the fast [official BLAKE3 hashing
-- implementation in assembly and C](https://github.com/BLAKE3-team/BLAKE3).
-- With support for AVX-512, AVX2, SSE 2, and SSE 4.1.
--
-- The original assembly and C implementation is released into the public domain with CC0 1.0.
-- Alternatively, it is licensed under the Apache License 2.0, copyright of Jack
-- O'Connor and Samuel Neves. See its [LICENSE](https://github.com/BLAKE3-team/BLAKE3/blob/88dcee7005be962a81516f7863e70009d9caa2c9/LICENSE)
-- for details.
--
-- This Haskell library is the copyright of Renzo Carbonara,
-- licensed under the terms of
-- the [Apache License 2.0](https://github.com/k0001/hs-blake3/blob/master/blake3/LICENSE).
module BLAKE3
  ( -- * Hashing
    hash
  , BIO.Digest(..)
    -- * Keyed hashing
  , BIO.Key
  , BIO.key
    -- * Key derivation
  , derive
    -- * Incremental hashing
  , BIO.Hasher
  , init
  , update
  , finalize
  , finalizeSeek
    -- * Constants
  , BIO.KEY_LEN
  , BIO.BLOCK_SIZE
  , BIO.DEFAULT_DIGEST_LEN
  )
  where

import qualified Data.ByteArray as BA
import qualified Data.ByteArray.Sized as BAS
import Data.Proxy
import Data.Word
import Prelude hiding (init)
import System.IO.Unsafe (unsafeDupablePerformIO)

import qualified BLAKE3.IO as BIO

--------------------------------------------------------------------------------

-- | BLAKE3 hashing.
--
-- For incremental hashing, see 'init', 'update' and 'finalize':
--
-- @
-- 'hash' yk = 'finalize' '.' 'update' ('init' yk)
-- @
hash
  :: forall len digest bin
  .  (BAS.ByteArrayN len digest, BA.ByteArrayAccess bin)
  => Maybe BIO.Key -- ^ Whether to use keyed hashing mode (for MAC, PRF).
  -> [bin]  -- ^ Data to hash.
  -> digest -- ^ The @digest@ type could be @'BIO.Digest' len@.
hash yk = unsafeDupablePerformIO . BIO.hash yk
{-# NOINLINE hash #-}

-- | BLAKE3 key derivation.
--
-- This can be used for KDF (key derivation function) purposes.
--
-- The key derivation @context@ should be hardcoded, globally unique,
-- application-specific well-known string.
--
-- A good format for the context string is:
--
-- @
-- [application] [commit timestamp] [purpose]
-- @
--
-- For example:
--
-- @
-- example.com 2019-12-25 16:18:03 session tokens v1
-- @
derive
  :: forall len okm ikm context
  .  (BAS.ByteArrayN len okm,
      BA.ByteArrayAccess ikm,
      BA.ByteArrayAccess context)
  => context -- ^ Key derivation context.
  -> [ikm]   -- ^ Input key material.
  -> okm     -- ^ Output key material of the specified @len@ght.
derive ctx ikms = unsafeDupablePerformIO $ do
  (dig, _ :: BIO.Hasher) <- BAS.allocRet Proxy $ \ph -> do
    BIO.initDerive ph ctx
    BIO.update ph ikms
    BIO.finalize ph
  pure dig
{-# NOINLINE derive #-}

-- | Initial 'BIO.Hasher' for incremental hashing.
init
  :: Maybe BIO.Key -- ^ Whether to use keyed hashing mode (for MAC, PRF).
  -> BIO.Hasher
init yk =
  BAS.allocAndFreeze $ \ph ->
  BIO.init ph yk

-- | Update 'BIO.Hasher' with new data.
update
  :: forall bin
  .  BA.ByteArrayAccess bin
  => BIO.Hasher
  -> [bin]  -- ^ New data to hash.
  -> BIO.Hasher
update h0 bins =
  BAS.copyAndFreeze h0 $ \ph1 ->
  BIO.update ph1 bins

-- | Finalize incremental hashing and obtain a the BLAKE3 output of the
-- specified @len@gth.
finalize
  :: forall len output
  .  BAS.ByteArrayN len output
  => BIO.Hasher
  -> output -- ^ The @output@ type could be @'BIO.Digest' len@.
finalize h0 = unsafeDupablePerformIO $ do
  (dig, _ :: BIO.Hasher) <- BAS.copyRet h0 BIO.finalize
  pure dig
{-# NOINLINE finalize #-}

-- | Finalize incremental hashing and obtain the specified @len@gth of BLAKE3
-- output starting at the specified offset.
--
-- @
-- 'finalize' h = 'finalizeSeek' h 0
-- @
finalizeSeek
  :: forall len output
  .  BAS.ByteArrayN len output
  => BIO.Hasher
  -> Word64     -- ^ BLAKE3 output offset.
  -> output     -- ^ The @output@ type could be @'BIO.Digest' len@.
finalizeSeek h0 pos = unsafeDupablePerformIO $ do
  (dig, _ :: BIO.Hasher) <- BAS.copyRet h0 $ \ph -> BIO.finalizeSeek ph pos
  pure dig
{-# NOINLINE finalizeSeek #-}