packages feed

blake3 0.1.1 → 0.2

raw patch · 7 files changed

+440/−228 lines, 7 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- BLAKE3.IO: allocRetDigest :: forall len a. KnownNat len => (Ptr Word8 -> IO a) -> IO (a, Digest len)
- BLAKE3.IO: allocRetHasher :: forall a. (Ptr HasherInternal -> IO a) -> IO (a, Hasher)
- BLAKE3.IO: allocRetKey :: forall a. (Ptr Word8 -> IO a) -> IO (a, Key)
- BLAKE3.IO: copyHasher :: Hasher -> IO Hasher
- BLAKE3.IO: data HasherInternal
- BLAKE3.IO: withHasherInternal :: Hasher -> (Ptr HasherInternal -> IO a) -> IO a
+ BLAKE3: digest :: forall len bin. (KnownNat len, ByteArrayAccess bin) => bin -> Maybe (Digest len)
+ BLAKE3: finalizeSeek :: forall len. KnownNat len => Hasher -> Word64 -> Digest len
+ BLAKE3.IO: c_finalize :: Ptr Hasher -> Ptr Word8 -> CSize -> IO ()
+ BLAKE3.IO: c_finalize_seek :: Ptr Hasher -> Word64 -> Ptr Word8 -> CSize -> IO ()
+ BLAKE3.IO: c_init :: Ptr Hasher -> IO ()
+ BLAKE3.IO: c_init_derive_key :: Ptr Hasher -> CString -> IO ()
+ BLAKE3.IO: c_init_keyed :: Ptr Hasher -> Ptr Word8 -> IO ()
+ BLAKE3.IO: c_update :: Ptr Hasher -> Ptr Word8 -> CSize -> IO ()
+ BLAKE3.IO: digest :: forall len bin. (KnownNat len, ByteArrayAccess bin) => bin -> Maybe (Digest len)
+ BLAKE3.IO: finalizeSeek :: forall len. KnownNat len => Ptr Hasher -> Word64 -> IO (Digest len)
+ BLAKE3.IO: hash :: forall len bin. (KnownNat len, ByteArrayAccess bin) => [bin] -> IO (Digest len)
+ BLAKE3.IO: instance Data.ByteArray.Sized.ByteArrayN BLAKE3.IO.HASHER_SIZE BLAKE3.IO.Hasher
+ BLAKE3.IO: instance Data.ByteArray.Sized.ByteArrayN BLAKE3.IO.KEY_LEN BLAKE3.IO.Key
+ BLAKE3.IO: instance Data.ByteArray.Types.ByteArrayAccess BLAKE3.IO.Context
+ BLAKE3.IO: instance Data.ByteArray.Types.ByteArrayAccess BLAKE3.IO.Hasher
+ BLAKE3.IO: instance Foreign.Storable.Storable BLAKE3.IO.Hasher
+ BLAKE3.IO: instance Foreign.Storable.Storable BLAKE3.IO.Key
+ BLAKE3.IO: instance GHC.Classes.Eq BLAKE3.IO.Hasher
+ BLAKE3.IO: instance GHC.Show.Show BLAKE3.IO.Hasher
+ BLAKE3.IO: instance GHC.TypeNats.KnownNat len => Data.ByteArray.Sized.ByteArrayN len (BLAKE3.IO.Digest len)
+ BLAKE3.IO: instance GHC.TypeNats.KnownNat len => Foreign.Storable.Storable (BLAKE3.IO.Digest len)
+ BLAKE3.IO: modifyHasher :: Hasher -> (Ptr Hasher -> IO a) -> IO a
+ BLAKE3.IO: type CHUNK_LEN = 1024
+ BLAKE3.IO: type MAX_DEPTH = 54
+ BLAKE3.IO: type MAX_SIMD_DEGREE = 16
- BLAKE3.IO: finalize :: forall len. KnownNat len => Ptr HasherInternal -> IO (Digest len)
+ BLAKE3.IO: finalize :: forall len. KnownNat len => Ptr Hasher -> IO (Digest len)
- BLAKE3.IO: init :: Ptr HasherInternal -> IO ()
+ BLAKE3.IO: init :: Ptr Hasher -> IO ()
- BLAKE3.IO: initDerive :: Ptr HasherInternal -> Context -> IO ()
+ BLAKE3.IO: initDerive :: Ptr Hasher -> Context -> IO ()
- BLAKE3.IO: initKeyed :: Ptr HasherInternal -> Key -> IO ()
+ BLAKE3.IO: initKeyed :: Ptr Hasher -> Key -> IO ()
- BLAKE3.IO: update :: forall bin. ByteArrayAccess bin => Ptr HasherInternal -> [bin] -> IO ()
+ BLAKE3.IO: update :: forall bin. ByteArrayAccess bin => Ptr Hasher -> [bin] -> IO ()

Files

CHANGELOG.md view
@@ -1,3 +1,26 @@+# Version 0.2++* COMPILER ASSISTED BACKWARDS INCOMPATIBLE CHANGE: Drop the `BLAKE3.Raw` module+  in favor of `BLAKE.IO`. Re-export internals from there.++* COMPILER ASSISTED BACKWARDS INCOMPATIBLE CHANGE: Drop `HasherInternal` and+  related functions in favor of `Ptr Hasher`.++* COMPILER ASSISTED BACKWARDS INCOMPATIBLE CHANGE: Drop `allocRetHasher`,+  `allocRetKey`, `allocRetDigest`.++* Added `Eq`, `Show`, `Storable`, `ByteArrayAccess` and `ByteArrayN` instances+  for `Hasher`, `Key` and `Digest`.++* Added `ByteArrayAccess` instance for `Context`.++* Added `finalizeSeek`, `modifyHasher`, `digest`.++* More tests.++* Documentation improvements.++ # Version 0.1.1  * Enabled AVX-512, AVX2 and SSE-4.1 support.
README.md view
@@ -1,5 +1,14 @@ # Haskell BLAKE3 -Haskell bindings to the official C implementation at-[https://github.com/BLAKE3-team/BLAKE3](https://github.com/BLAKE3-team/BLAKE3).+Bindings to+the [official fast BLAKE3 implementations in assembly and C](https://github.com/BLAKE3-team/BLAKE3),+with support for AVX-512, AVX2 and SSE 4.1. +# Development++* Build with `nix-build -A ghc865` (or `ghc883`).++* Enter into a development environment with `nix-shell -A ghc865` (or `ghc883`).+  From this environment, you can build with `cabal new-build blake3`, test with+  `cabal new-test blake3`, render documentation with `cabal new-haddock blake3`,+  etc.
blake3.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: blake3-version: 0.1.1+version: 0.2 license: Apache-2.0 license-file: LICENSE extra-source-files: README.md CHANGELOG.md@@ -48,7 +48,6 @@   exposed-modules:     BLAKE3     BLAKE3.IO-    BLAKE3.Raw   cc-options: -O3   include-dirs: cbits/   c-sources:@@ -78,7 +77,7 @@   else     cc-options: -DBLAKE3_NO_AVX2 -  -- AVX2 support+  -- SSE 4.1 support   if flag(sse41)     if arch(x86_64) && (os(linux) || os(darwin))       c-sources: cbits/blake3_sse41_x86-64_unix.S
lib/BLAKE3.hs view
@@ -21,6 +21,7 @@   ( -- * Hashing     hash   , BIO.Digest+  , BIO.digest     -- * Keyed hashing   , hashKeyed   , BIO.Key@@ -35,6 +36,7 @@   , hasherKeyed   , update   , finalize+  , finalizeSeek     -- * Constants   , BIO.KEY_LEN   , BIO.BLOCK_SIZE@@ -43,6 +45,9 @@   where  import qualified Data.ByteArray as BA+import qualified Data.ByteArray.Sized as BAS+import Data.Proxy+import Data.Word import GHC.TypeLits import System.IO.Unsafe (unsafeDupablePerformIO) @@ -61,16 +66,17 @@   :: forall len bin   .  (KnownNat len, BA.ByteArrayAccess bin)   => [bin]           -- ^ Data to hash.-  -> BIO.Digest len  -- ^ Default digest length is 'BIO.DEFAULT_DIGEST_LEN'.-hash bins = unsafeDupablePerformIO $ do-  fmap fst $ BIO.allocRetHasher $ \ph -> do-    BIO.init ph-    BIO.update ph bins-    BIO.finalize ph+  -> BIO.Digest len+  -- ^ Default digest length is 'BIO.DEFAULT_DIGEST_LEN'.+  -- The 'Digest' is wiped from memory as soon as the 'Digest' becomes unused.+hash = unsafeDupablePerformIO . BIO.hash {-# NOINLINE hash #-}  -- | BLAKE3 hashing with a 'BIO.Key'. --+-- This can be used for MAC (message authentication code), PRF (pseudo random+-- function) and SHO (stateful hash object) purposes.+-- -- For incremental hashing, see 'hasherKeyed', 'update' and 'finalize': -- -- @@@ -81,40 +87,45 @@   .  (KnownNat len, BA.ByteArrayAccess bin)   => BIO.Key   -> [bin]           -- ^ Data to hash.-  -> BIO.Digest len  -- ^ Default digest length is 'BIO.DEFAULT_DIGEST_LEN'.+  -> BIO.Digest len+  -- ^ Default digest length is 'BIO.DEFAULT_DIGEST_LEN'.+  -- The 'Digest' is wiped from memory as soon as the 'Digest' becomes unused. hashKeyed key0 bins = unsafeDupablePerformIO $ do-  fmap fst $ BIO.allocRetHasher $ \ph -> do+  (dig, _ :: BIO.Hasher) <- BAS.allocRet Proxy $ \ph -> do     BIO.initKeyed ph key0     BIO.update ph bins     BIO.finalize ph+  pure dig {-# NOINLINE hashKeyed #-}  -- | BLAKE3 key derivation.+--+-- This can be used for KDF (key derivation function) purposes. derive   :: forall len ikm   .  (KnownNat len, BA.ByteArrayAccess ikm)   => BIO.Context   -> [ikm]  -- ^ Input key material.-  -> BIO.Digest len  -- ^ Default digest length is 'BIO.DEFAULT_DIGEST_LEN'.-derive ctx ikms = unsafeDupablePerformIO $-  fmap fst $ BIO.allocRetHasher $ \ph -> do+  -> BIO.Digest len+  -- ^ Default digest length is 'BIO.DEFAULT_DIGEST_LEN'.+  -- The 'Digest' is wiped from memory as soon as the 'Digest' becomes unused.+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. hasher :: BIO.Hasher -- ^-hasher = unsafeDupablePerformIO $-  fmap snd $ BIO.allocRetHasher BIO.init-{-# NOINLINE hasher #-}+hasher = BAS.allocAndFreeze BIO.init  -- | Initial 'BIO.Hasher' for incremental /keyed/ hashing. hasherKeyed :: BIO.Key -> BIO.Hasher -- ^-hasherKeyed key0 = unsafeDupablePerformIO $-  fmap snd $ BIO.allocRetHasher $ \ph ->+hasherKeyed key0 =+  BAS.allocAndFreeze $ \ph ->   BIO.initKeyed ph key0-{-# NOINLINE hasherKeyed #-}  -- | Update 'BIO.Hasher' with new data. update@@ -123,21 +134,41 @@   => BIO.Hasher   -> [bin]  -- ^ New data to hash.   -> BIO.Hasher-update h0 bins = unsafeDupablePerformIO $ do-  h1 <- BIO.copyHasher h0-  BIO.withHasherInternal h1 $ \ph1 -> do-    BIO.update ph1 bins-    pure h1-{-# NOINLINE update #-}+update h0 bins =+  BAS.copyAndFreeze h0 $ \ph1 ->+  BIO.update ph1 bins  -- | Finish hashing and obtain a 'BIO.Digest' of the specified @len@gth. finalize   :: forall len   .  KnownNat len-  => BIO.Hasher -- ^+  => BIO.Hasher   -> BIO.Digest len+  -- ^ Default digest length is 'BIO.DEFAULT_DIGEST_LEN'.+  -- The 'Digest' is wiped from memory as soon as the 'Digest' becomes unused. finalize h0 = unsafeDupablePerformIO $ do-  h1 <- BIO.copyHasher h0-  BIO.withHasherInternal h1 $ \ph1 ->-    BIO.finalize ph1+  (dig, _ :: BIO.Hasher) <- BAS.copyRet h0 BIO.finalize+  pure dig {-# NOINLINE finalize #-}++-- | Finalize incremental hashing and obtain a 'Digest' of length @len@ /after/+-- the specified number of bytes of BLAKE3 output.+--+-- @+-- 'finalize' h = 'finalizeSeek' h 0+-- @+finalizeSeek+  :: forall len+  .  KnownNat len+  => BIO.Hasher+  -> Word64     -- ^ Number of bytes to skip before obtaning the digest output.+  -> BIO.Digest len+  -- ^ Default digest length is 'BIO.DEFAULT_DIGEST_LEN'.+  -- The 'Digest' is wiped from memory as soon as the 'Digest' becomes unused.+finalizeSeek h0 pos = unsafeDupablePerformIO $ do+  (dig, _ :: BIO.Hasher) <- BAS.copyRet h0 $ \ph -> BIO.finalizeSeek ph pos+  pure dig+{-# NOINLINE finalizeSeek #-}++--------------------------------------------------------------------------------+
lib/BLAKE3/IO.hs view
@@ -1,113 +1,125 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE KindSignatures #-}+{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} +-- | IO and low level tools. module BLAKE3.IO   ( -- * Hashing-    init+    hash+  , init   , update   , finalize-  , Hasher-  , allocRetHasher+  , finalizeSeek+    -- * Digest   , Digest-  , allocRetDigest-  -- ** Memory-  , Raw.HasherInternal-  , copyHasher-  , withHasherInternal-  -- * Keyed hashing+  , digest+    -- * Keyed hashing   , Key   , key-  , allocRetKey   , initKeyed-  -- * Key derivation+    -- * Key derivation   , Context   , context   , initDerive-  -- * Constants-  , Raw.HASHER_ALIGNMENT-  , Raw.HASHER_SIZE-  , Raw.KEY_LEN-  , Raw.BLOCK_SIZE-  , Raw.DEFAULT_DIGEST_LEN+    -- * Hasher+  , Hasher+  , modifyHasher+    -- * Constants+  , HASHER_ALIGNMENT+  , HASHER_SIZE+  , KEY_LEN+  , BLOCK_SIZE+  , DEFAULT_DIGEST_LEN+  , CHUNK_LEN+  , MAX_DEPTH+  , MAX_SIMD_DEGREE+    -- * Low-level C bindings+  , c_init+  , c_init_keyed+  , c_init_derive_key+  , c_update+  , c_finalize+  , c_finalize_seek   )   where  import Control.Monad (guard)-import qualified Data.ByteArray as BA-import qualified Data.ByteArray.Encoding as BA import Data.Foldable-import qualified Data.Memory.PtrMethods as BA import Data.Proxy import Data.String import Data.Word+import Foreign.C.String+import Foreign.C.Types+import Foreign.Marshal.Array (copyArray) import Foreign.Ptr import Foreign.Storable import GHC.TypeLits import Prelude hiding (init)--import qualified BLAKE3.Raw as Raw+import qualified Data.ByteArray as BA+import qualified Data.ByteArray.Sized as BAS+import qualified Data.ByteArray.Encoding as BA+import qualified Data.Memory.PtrMethods as BA  -------------------------------------------------------------------------------- --- | Immutable BLAKE3 hashing state.+-- | Output from BLAKE3 algorithm, of @len@ bytes. ----- Obtain with 'BLAKE3.hasher' or 'BLAKE3.hasherKeyed'.-newtype Hasher = Hasher BA.ScrubbedBytes-  -- deriving newtype (BA.ByteArrayAccess)+-- The default digest length for BLAKE3 is 'DEFAULT_DIGEST_LEN'.+data Digest (len :: Nat) where+  -- | We store things this way to avoid unnecessary conversions between+  -- different 'BA.ByteArrayAccess' when using 'digest' for reading a 'Digest'+  -- from a third party source.+  --+  -- Digest produced by this library are always allocated with 'BAS.allocRet'.+  Digest :: BA.ByteArrayAccess x => x -> Digest len --- | Allocate 'Hasher'.+-- | Obtain a digest containing bytes from a third-party source. ----- The 'Hasher' is wiped and freed as soon as it becomes unused.-allocRetHasher-  :: forall a-  .  (Ptr Raw.HasherInternal -> IO a)  -- ^ Initialize 'Raw.HASHER_SIZE' bytes.-  -> IO (a, Hasher)-allocRetHasher g = do-  let size = fromIntegral (natVal (Proxy @Raw.HASHER_SIZE))-  (a, bs) <- BA.allocRet size g-  pure (a, Hasher bs)---- | Mutate the given 'Hasher'.-withHasherInternal-  :: Hasher-  -> (Ptr Raw.HasherInternal -> IO a) -- ^ Read or write.-  -> IO a-withHasherInternal (Hasher x) = BA.withByteArray x---- | Copy an inmutable 'Hasher'.-copyHasher :: Hasher -> IO Hasher -- ^-copyHasher (Hasher x) = fmap Hasher $ BA.copy x (const (pure ()))----------------------------------------------------------------------------------+-- This is useful if you want to use the 'Digest' datatype in your programs, but+-- you are loading the pre-calculated digests from a database or similar.+digest+  :: forall len bin+  .  (KnownNat len, BA.ByteArrayAccess bin)+  => bin  -- ^ Raw digest bytes. Must have length @len@.+  -> Maybe (Digest len)  -- ^+digest bin+  | BA.length bin /= fromIntegral (natVal (Proxy @len)) = Nothing+  | otherwise = Just (Digest bin) --- | Output from BLAKE3 algorithm, of @len@ bytes.------ The default digest length for BLAKE3 is 'Raw.DEFAULT_DIGEST_LEN'.-newtype Digest (len :: Nat) = Digest BA.ScrubbedBytes-  deriving newtype ( Eq -- ^ Constant time.-                   , BA.ByteArrayAccess)+-- | Constant time.+instance Eq (Digest len) where+  Digest a == Digest b = BA.constEq a b  -- | Base 16 (hexadecimal). instance Show (Digest len) where   show (Digest x) = showBase16 x +instance BA.ByteArrayAccess (Digest len) where+  length (Digest x) = BA.length x+  withByteArray (Digest x) = BA.withByteArray x+ -- | Allocate a 'Digest'.------ The 'Digest' is wiped and freed as soon as it becomes unused.-allocRetDigest-  :: forall len a-  .  KnownNat len-  => (Ptr Word8 -> IO a)  -- ^ Initialize @len@ bytes.-  -> IO (a, Digest len)-allocRetDigest g = do-  let size = fromIntegral (natVal (Proxy @len))-  (a, bs) <- BA.allocRet size g-  pure (a, Digest bs)+-- The memory is wiped and freed as soon the 'Digest' becomes unused.+instance KnownNat len => BAS.ByteArrayN len (Digest len) where+  allocRet prx g = do+    let size = fromIntegral (natVal prx)+    (a, bs :: BA.ScrubbedBytes) <- BA.allocRet size g+    pure (a, Digest bs) +-- | When allocating a 'Digest', prefer to use 'BAS.alloc', which+-- wipes and releases the memory as soon it becomes unused.+instance forall len. KnownNat len => Storable (Digest len) where+  sizeOf _ = fromIntegral (natVal (Proxy @len))+  alignment _ = 8 -- Not sure.+  peek ps = BAS.alloc $ \pd -> copyArray pd ps 1+  poke pd src = BA.withByteArray src $ \ps -> copyArray pd ps 1+ --------------------------------------------------------------------------------  -- | Key used for keyed hashing mode.@@ -115,51 +127,72 @@ -- Obtain with 'BLAKE3.key'. -- -- See 'BLAKE3.hashKeyed'.-newtype Key = Key BA.ScrubbedBytes-  deriving newtype ( Eq -- ^ Constant time.-                   , BA.ByteArrayAccess)+data Key where+  -- | We store things this way to avoid unnecessary conversions between+  -- different 'BA.ByteArrayAccess' when using 'key' for reading a 'Key'+  -- from a third party source.+  Key :: BA.ByteArrayAccess x => x -> Key +-- | Constant time.+instance Eq Key where+  Key a == Key b = BA.constEq a b+ -- | Base 16 (hexadecimal). instance Show Key where   show (Key x) = showBase16 x -keyLen :: Int-keyLen = fromIntegral (natVal (Proxy @Raw.KEY_LEN))+-- | Length is 'KEY_LEN'.+instance BA.ByteArrayAccess Key where+  length (Key x) = BA.length x+  withByteArray (Key x) = BA.withByteArray x +-- | Allocate a 'Key'.+-- The memory is wiped and freed as soon as the 'Key' becomes unused.+instance BAS.ByteArrayN KEY_LEN Key where+  allocRet _ g = do+    (a, bs :: BA.ScrubbedBytes) <- BA.allocRet keyLen g+    pure (a, Key bs)++-- | When allocating a 'Key', prefer to use 'BAS.alloc', which+-- wipes and releases the memory as soon it becomes unused.+instance Storable Key where+  sizeOf _ = keyLen+  alignment _ = 8 -- Not sure.+  peek ps = BAS.alloc $ \pd -> copyArray pd ps 1+  poke pd src = BA.withByteArray src $ \ps -> copyArray pd ps 1+ -- | Obtain a 'Key' for use in BLAKE3 keyed hashing. -- -- See 'BLAKE3.hashKeyed'. key   :: BA.ByteArrayAccess bin-  => bin -- ^ Key bytes. Must have length 'Raw.KEY_LEN'.+  => bin -- ^ Key bytes. Must have length 'KEY_LEN'.   -> Maybe Key -- ^-key bin | BA.length bin == keyLen = Just (Key (BA.convert bin))-        | otherwise = Nothing---- | Allocate a 'Key'.------ The 'Key' is wiped and freed as soon as it becomes unused.-allocRetKey-  :: forall a-  . (Ptr Word8 -> IO a) -- ^ Initialize 'Raw.KEY_LEN' bytes.-  -> IO (a, Key)-allocRetKey g = do-  (a, bs) <- BA.allocRet keyLen g-  pure (a, Key bs)+key bin | BA.length bin /= keyLen = Nothing+        | otherwise = Just (Key bin)  --------------------------------------------------------------------------------  -- | Context for BLAKE3 key derivation. Obtain with 'context'.-newtype Context = Context BA.Bytes -- ^ NUL-terminated 'CString'.+newtype Context+  = Context BA.Bytes+  -- ^ NUL-terminated 'CString'. We store things this way so as to avoid+  -- re-creating the 'CString' each time we need to use this 'Context' in+  -- 'c_init_derive_key'. We never expose the NUL-terminating byte to users+  -- of this library.   deriving newtype (Eq) +-- We exclude the NUL-terminating byte. That's internal.+instance BA.ByteArrayAccess Context where+  length (Context x) = max 0 (BA.length x - 1)+  withByteArray c@(Context x) = BA.withByteArray (BA.takeView x (BA.length c))+ -- | Base 16 (hexadecimal). instance Show Context where-  show (Context x) = showBase16 (BA.takeView x (BA.length x - 1))+  show = showBase16  -- | 'fromString' is a /partial/ function that fails if the given 'String'--- contains 'Char's outside the range @['toEnum' 1 .. 'toEnum' 255]@. ---+-- contains 'Char's outside the range @['toEnum' 1 .. 'toEnum' 255]@. -- See 'context' for more details. instance IsString Context where   fromString s = case traverse charToWord8 s of@@ -204,59 +237,219 @@  -------------------------------------------------------------------------------- --- | Initialize a 'Raw.HasherInternal'.+showBase16 :: BA.ByteArrayAccess x => x -> String+showBase16 = fmap (toEnum . fromIntegral)+           . BA.unpack @BA.ScrubbedBytes+           . BA.convertToBase BA.Base16++--------------------------------------------------------------------------------++-- | BLAKE3 hashing.+hash+  :: forall len bin+  .  (KnownNat len, BA.ByteArrayAccess bin)+  => [bin]+  -- ^ Data to hash.+  -> IO (Digest len)+  -- ^ Default digest length is 'BIO.DEFAULT_DIGEST_LEN'.+  -- The 'Digest' is wiped from memory as soon as the 'Digest' becomes unused.+hash bins = do+  (dig, _ :: Hasher) <- BAS.allocRet Proxy $ \ph -> do+    init ph+    update ph bins+    finalize ph+  pure dig++-- | Initialize a 'Hasher'. init-  :: Ptr Raw.HasherInternal -- ^ Will be mutated.+  :: Ptr Hasher  -- ^ Obtain with 'BAS.alloc' or similar. It will be mutated.   -> IO ()-init = Raw.init+init = c_init --- | Initialize a 'Raw.HasherInternal' in keyed mode.+-- | Initialize a 'Hasher' in keyed mode. initKeyed-  :: Ptr Raw.HasherInternal -- ^ Will be mutated.+  :: Ptr Hasher  -- ^ Obtain with 'BAS.alloc' or similar. It will be mutated.   -> Key-  -> IO () -- ^+  -> IO () initKeyed ph key0 =   BA.withByteArray key0 $ \pkey ->-  Raw.init_keyed ph pkey+  c_init_keyed ph pkey --- | Initialize a 'Raw.HasherInternal' in derivation mode.+-- | Initialize a 'Hasher' in derivation mode. -- -- The input key material must be provided afterwards, using 'update'. initDerive-  :: Ptr Raw.HasherInternal -- ^ Will be mutated.+  :: Ptr Hasher  -- ^ Obtain with 'BAS.alloc' or similar. It will be mutated.   -> Context   -> IO () initDerive ph (Context ctx) =   BA.withByteArray ctx $ \pc ->-  Raw.init_derive_key ph pc+  c_init_derive_key ph pc --- | Update 'Raw.HasherInternal' state with new data.+-- | Update 'Hasher' state with new data. update   :: forall bin   .  BA.ByteArrayAccess bin-  => Ptr Raw.HasherInternal -- ^ Will be mutated.+  => Ptr Hasher -- ^ Obtain with 'modifyHasher'. It will be mutated.   -> [bin]-  -> IO () -- ^+  -> IO () update ph bins =   for_ bins $ \bin ->   BA.withByteArray bin $ \pbin ->-  Raw.update ph pbin (fromIntegral (BA.length bin))+  c_update ph pbin (fromIntegral (BA.length bin)) --- | Finalize 'Raw.HasherInternal' state and obtain a digest.------ The 'Raw.HasherInternal' is mutated.+-- | Finalize incremental hashin and obtain a 'Digest'. finalize   :: forall len   .  KnownNat len-  => Ptr Raw.HasherInternal -- ^ Will be mutated.-  -> IO (Digest len) -- ^+  => Ptr Hasher -- ^ Obtain with 'modifyHasher'. It will be mutated.+  -> IO (Digest len)+  -- ^ Default digest length is 'BIO.DEFAULT_DIGEST_LEN'.+  -- The 'Digest' is wiped from memory as soon as the 'Digest' becomes unused. finalize ph =-  fmap snd $ allocRetDigest $ \pd ->-  Raw.finalize ph pd (fromIntegral (natVal (Proxy @len)))+  BAS.alloc $ \pd ->+  c_finalize ph pd (fromIntegral (natVal (Proxy @len))) +-- | Finalize incremental hashing and obtain a 'Digest' of length @len@ /after/+-- the specified number of bytes of BLAKE3 output.+--+-- @+-- 'finalize' h = 'finalizeSeek' h 0+-- @+finalizeSeek+  :: forall len+  .  KnownNat len+  => Ptr Hasher -- ^ Obtain with 'modifyHasher'. It will be mutated.+  -> Word64     -- ^ Number of bytes to skip before obtaning the digest output.+  -> IO (Digest len)+  -- ^ Default digest length is 'BIO.DEFAULT_DIGEST_LEN'.+  -- The 'Digest' is wiped from memory as soon as the 'Digest' becomes unused.+finalizeSeek ph pos =+  BAS.alloc $ \pd ->+  c_finalize_seek ph pos pd (fromIntegral (natVal (Proxy @len)))+ -------------------------------------------------------------------------------- -showBase16 :: BA.ByteArrayAccess x => x -> String-showBase16 = fmap (toEnum . fromIntegral)-           . BA.unpack @BA.ScrubbedBytes-           . BA.convertToBase BA.Base16+type HASHER_ALIGNMENT = 8++-- | In bytes.+type HASHER_SIZE = 1912++hasherSize :: Int+hasherSize = fromIntegral (natVal (Proxy @HASHER_SIZE))++-- | In bytes.+type KEY_LEN = 32++keyLen :: Int+keyLen = fromIntegral (natVal (Proxy @KEY_LEN))++-- | In bytes.+type DEFAULT_DIGEST_LEN = 32++-- | In bytes.+type BLOCK_SIZE = 64++type CHUNK_LEN = 1024+type MAX_DEPTH = 54+type MAX_SIMD_DEGREE = 16++--------------------------------------------------------------------------------++-- | BLAKE3 internal state.+--+-- Obtain with 'BLAKE3.hasher', 'BLAKE3.hasherKeyed'.+newtype Hasher = Hasher (BAS.SizedByteArray HASHER_SIZE BA.ScrubbedBytes)+  deriving newtype+    ( BA.ByteArrayAccess+      -- ^ Length is 'HASHER_SIZE'.+    , BAS.ByteArrayN HASHER_SIZE+      -- ^ Allocate a 'Hasher'.+      -- The memory is wiped and freed as soon as the 'Hasher' becomes unused.+    )++instance Eq Hasher where+  (==) = BA.eq++-- | Base 16 (hexadecimal).+instance Show Hasher where+  show = showBase16++-- | Obtain a @'Ptr' 'Hasher'@ to use with functions like 'initDerive', etc.+modifyHasher+  :: Hasher+  -> (Ptr Hasher -> IO a) -- ^ 'HASHER_SIZE' bytes.+  -> IO a+modifyHasher = BA.withByteArray++-- | When allocating a 'Hasher', prefer to use 'BAS.alloc', which+-- wipes and releases the memory as soon it becomes unused.+instance Storable Hasher where+  sizeOf _ = hasherSize+  alignment _ = fromIntegral (natVal (Proxy @HASHER_ALIGNMENT))+  peek ps = BAS.alloc $ \pd -> copyArray pd ps 1+  poke pd src = BA.withByteArray src $ \ps -> copyArray pd ps 1++--------------------------------------------------------------------------------++-- | @void blake3_hasher_init(blake3_hasher *self)@+foreign import ccall unsafe+  "blake3.h blake3_hasher_init"+  c_init+    :: Ptr Hasher  -- ^ You can obtain with 'BAS.alloc'.+                   -- Otherwise, any chunk of 'HASHER_SIZE' bytes aligned to+                   -- 'HASHER_ALIGNMENT' will do.+    -> IO ()++-- | @void blake3_hasher_init_keyed(blake3_hasher *self, const uint8_t key['KEY_LEN'])@+foreign import ccall unsafe+  "blake3.h blake3_hasher_init_keyed"+  c_init_keyed+    :: Ptr Hasher  -- ^ You can obtain with 'BAS.alloc'.+                   -- Otherwise, any chunk of 'HASHER_SIZE' bytes aligned to+                   -- 'HASHER_ALIGNMENT' will do.+    -> Ptr Word8   -- ^ You can obtain with 'BAS.alloc'.+                   -- Otherwise, any chunk of length 'KEY_LEN' will do.+    -> IO ()++-- | @void blake3_hasher_init_derive_key(blake3_hasher *self, const char *context)@+foreign import ccall unsafe+  "blake3.h blake3_hasher_init_derive_key"+  c_init_derive_key+    :: Ptr Hasher  -- ^ You can obtain with 'BAS.alloc'.+                   -- Otherwise, any chunk of 'HASHER_SIZE' bytes aligned to+                   -- 'HASHER_ALIGNMENT' will do.+    -> CString  -- ^ Context.+    -> IO ()++-- | @void blake3_hasher_update(blake3_hasher *self, const void *input, size_t input_len)@+foreign import ccall unsafe+  "blake3.h blake3_hasher_update"+  c_update+    :: Ptr Hasher -- ^ Must have been previously initializedi. See 'c_init',+                  -- 'c_init_keyed', 'c_init_derive_key'.+    -> Ptr Word8  -- ^ Data.+    -> CSize      -- ^ Data length.+    -> IO ()++-- | @void blake3_hasher_finalize(const blake3_hasher *self, uint8_t *out, size_t out_len)@+foreign import ccall unsafe+  "blake3.h blake3_hasher_finalize"+  c_finalize+    :: Ptr Hasher -- ^ Must have been previously initializedi. See 'c_init',+                  -- 'c_init_keyed', 'c_init_derive_key'.+    -> Ptr Word8  -- ^ Out.+    -> CSize      -- ^ Out length.+    -> IO ()++-- | @void blake3_hasher_finalize_seek(const blake3_hasher *self, uint64_t seek, uint8_t *out, size_t out_len)@+foreign import ccall unsafe+  "blake3.h blake3_hasher_finalize_seek"+  c_finalize_seek+    :: Ptr Hasher  -- ^ Must have been previously initializedi. See 'c_init',+                   -- 'c_init_keyed', 'c_init_derive_key'.+    -> Word64      -- ^ Seek position.+    -> Ptr Word8   -- ^ Out.+    -> CSize       -- ^ Out length.+    -> IO ()+
− lib/BLAKE3/Raw.hs
@@ -1,82 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# OPTIONS_HADDOCK hide not_home #-}--module BLAKE3.Raw where--import Data.Word-import Foreign.C.Types-import Foreign.C.String-import Foreign.Ptr-import Prelude hiding (init)------------------------------------------------------------------------------------type HASHER_ALIGNMENT = 8--- | In bytes.-type HASHER_SIZE = 1912--- | In bytes.-type KEY_LEN = 32--- | In bytes.-type DEFAULT_DIGEST_LEN = 32--- | In bytes.-type BLOCK_SIZE = 64--type CHUNK_LEN = 1024-type MAX_DEPTH = 54-type MAX_SIMD_DEGREE = 16---- | Opaque datatype of size 'HASHER_SIZE' and alignment 'HASHER_ALIGNMENT'.------ Obtain with 'BLAKE3.IO.withHasherInternal'.-data HasherInternal---- | @void blake3_hasher_init(blake3_hasher *self)@-foreign import ccall unsafe-  "blake3.h blake3_hasher_init"-  init-    :: Ptr HasherInternal-    -> IO () -- ^---- | @void blake3_hasher_init_keyed(blake3_hasher *self, const uint8_t key['KEY_LEN'])@-foreign import ccall unsafe-  "blake3.h blake3_hasher_init_keyed"-  init_keyed-    :: Ptr HasherInternal-    -> Ptr Word8   -- ^ Key of length 'KEY_LEN'.-    -> IO ()---- | @void blake3_hasher_init_derive_key(blake3_hasher *self, const char *context)@-foreign import ccall unsafe-  "blake3.h blake3_hasher_init_derive_key"-  init_derive_key-    :: Ptr HasherInternal-    -> CString  -- ^ Context.-    -> IO ()    -- ^---- | @void blake3_hasher_update(blake3_hasher *self, const void *input, size_t input_len)@-foreign import ccall unsafe-  "blake3.h blake3_hasher_update"-  update-    :: Ptr HasherInternal-    -> Ptr Word8 -- ^ Data.-    -> CSize     -- ^ Data length.-    -> IO () -- ^---- | @void blake3_hasher_finalize(const blake3_hasher *self, uint8_t *out, size_t out_len)@-foreign import ccall unsafe-  "blake3.h blake3_hasher_finalize"-  finalize-    :: Ptr HasherInternal-    -> Ptr Word8 -- ^ Out.-    -> CSize     -- ^ Out length.-    -> IO () -- ^---- | @void blake3_hasher_finalize_seek(const blake3_hasher *self, uint64_t seek, uint8_t *out, size_t out_len)@-foreign import ccall unsafe-  "blake3.h blake3_hasher_finalize"-  finalize_seek-    :: Ptr HasherInternal-    -> Word64      -- ^ Seek.-    -> Ptr Word8   -- ^ Out.-    -> CSize       -- ^ Out length.-    -> IO () -- ^
test/Main.hs view
@@ -5,9 +5,11 @@ module Main (main) where  import qualified Data.ByteArray as BA+import qualified Data.ByteArray.Sized as BAS import qualified Data.List as List import Data.Proxy import Data.Word+import Foreign.Storable import GHC.TypeLits import qualified Test.Tasty as Tasty import qualified Test.Tasty.Runners as Tasty@@ -30,6 +32,7 @@   , tt_context   , tt_key   , tt_hasher+  , tt_digest   ]  @@ -107,8 +110,34 @@       "92b2b75604ed3c761f9d" @=? show (B.finalize @10 h1)       "f46255baa87a3280d644" @=? show (B.finalize @10 h2)       "b5ee60b5d89ac7e1289b" @=? show (B.finalize @10 h3)++  , testCase "finalize vs finalizeSeek: zero" $ do+      let dig0 :: B.Digest 50 = B.finalize B.hasher+      let dig1 :: B.Digest 50 = B.finalizeSeek B.hasher 0+      dig0 @=? dig1++  , testCase "finalize vs finalizeSeek: non-zero" $ do+      let dig0 :: B.Digest 2050 = B.finalize B.hasher+      let dig1 :: B.Digest 50 = B.finalizeSeek B.hasher 2000+      BA.drop 2000 (BA.convert dig0) @=? (BA.convert dig1 :: BA.Bytes)++  , testCase "Storable" $ do+      let h1 = B.hasher+      h2 <- BAS.alloc $ \ph2 -> poke ph2 h1+      h1 @=? h2+      h3 <- BA.withByteArray h1 peek+      h1 @=? h3   ] +tt_digest :: TestTree+tt_digest = testGroup "Digest"+  [ testCase "Storable" $ do+      let d1 :: B.Digest 400 = B.hash ([] :: [BA.Bytes])+      d2 <- BAS.alloc $ \pd2 -> poke pd2 d1+      d1 @=? d2+      d3 <- BA.withByteArray d1 peek+      d1 @=? d3+  ]  testVector_context :: B.Context testVector_context = "BLAKE3 2019-12-27 16:29:52 test vectors context"@@ -122,6 +151,10 @@   , testCase "has null" $ do       Nothing @=? B.context ("\NUL" :: BA.ScrubbedBytes)       Nothing @=? B.context ("hi\NUL" :: BA.ScrubbedBytes)+  , testCase "fromString" $ do+      Just "" @=? B.context ("" :: BA.ScrubbedBytes)+      Just "a" @=? B.context ("a" :: BA.ScrubbedBytes)+      Just "ab" @=? B.context ("ab" :: BA.ScrubbedBytes)   ]  tt_testVectors :: TestTree@@ -147,6 +180,12 @@         @=? show testVector_key   , testCase "long" $ do       Nothing @=? B.key (BA.replicate 100 0 :: BA.Bytes)+  , testCase "Storable" $ do+      let k1 = testVector_key+      k2 <- BAS.alloc $ \pk2 -> poke pk2 k1+      k1 @=? k2+      k3 <- BA.withByteArray k1 peek+      k1 @=? k3   ]  data TestVector = TestVector