hs-blake2 (empty) → 0.0.1
raw patch · 11 files changed
+518/−0 lines, 11 filesdep +QuickCheckdep +basedep +bytestringsetup-changed
Dependencies added: QuickCheck, base, bytestring, bytestring-arbitrary, criterion, cryptohash, hs-blake2, tasty, tasty-quickcheck
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- benches/BenchAll.hs +40/−0
- cbits/alloc.c +13/−0
- cbits/alloc.h +2/−0
- hs-blake2.cabal +59/−0
- src/Crypto/Hash/Tsuraan/Blake2.hs +41/−0
- src/Crypto/Hash/Tsuraan/Blake2/Internal.hs +77/−0
- src/Crypto/Hash/Tsuraan/Blake2/Parallel.hs +92/−0
- src/Crypto/Hash/Tsuraan/Blake2/Serial.hs +92/−0
- tests/TestAll.hs +70/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2014, jay groven++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of jay groven nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ benches/BenchAll.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE OverloadedStrings #-}+module Main+( main+) where++import qualified Crypto.Hash.MD5 as MD5+import qualified Crypto.Hash.SHA1 as SHA1+import qualified Crypto.Hash.SHA256 as SHA2+import qualified Crypto.Hash.SHA3 as SHA3+import qualified Crypto.Hash.Skein256 as Skein+import qualified Crypto.Hash.Tiger as Tiger+import Data.ByteString ( hGetSome )+import System.IO+import Criterion.Main++import qualified Crypto.Hash.Tsuraan.Blake2.Parallel as ParBlake2+import qualified Crypto.Hash.Tsuraan.Blake2.Serial as SerBlake2+import qualified Crypto.Hash.Tsuraan.Blake2 as Blake2++main :: IO ()+main = do+ strings <- withFile "/dev/urandom" ReadMode $ \handle -> do+ -- let lens = [1000,2000,3000,4000,5000,6000,7000,8000,9000,10000]+ let lens = [1,10,100,1000,3000,6000,10000,30000,60000,100000]+ ss <- mapM (hGetSome handle) lens+ return $ zip lens ss+ defaultMain $+ [ bgroup (show sz)+ [ bench "Parallel" $ nf (ParBlake2.hash 32) st+ , bench "Sequential" $ nf (SerBlake2.hash 32) st+ , bench "Auto" $ nf (Blake2.hash 32) st+ , bench "MD5" $ nf MD5.hash st+ , bench "SHA1" $ nf SHA1.hash st+ , bench "SHA2" $ nf SHA2.hash st+ , bench "SHA3" $ nf (SHA3.hash 32) st+ , bench "Skein" $ nf (Skein.hash 32) st+ , bench "Tiger" $ nf Tiger.hash st+ ]+ | (sz, st) <- strings ]+
+ cbits/alloc.c view
@@ -0,0 +1,13 @@+#include <stdlib.h>+#include <blake2.h>++size_t blake2b_size()+{+ return sizeof(blake2b_state);+}++size_t blake2bp_size()+{+ return sizeof(blake2bp_state);+}+
+ cbits/alloc.h view
@@ -0,0 +1,2 @@+void *malloc_blake2b();+void *malloc_blake2bp();
+ hs-blake2.cabal view
@@ -0,0 +1,59 @@+-- Initial hs-blake2.cabal generated by cabal init. For further +-- documentation, see http://haskell.org/cabal/users-guide/++name: hs-blake2+version: 0.0.1+synopsis: A cryptohash-inspired library for blake2+-- description: +license: BSD3+license-file: LICENSE+author: jay groven+maintainer: tsuraan@gmail.com+-- copyright: +category: Data+build-type: Simple+extra-source-files: cbits/alloc.h+cabal-version: >=1.10++library+ exposed-modules: Crypto.Hash.Tsuraan.Blake2+ , Crypto.Hash.Tsuraan.Blake2.Parallel+ , Crypto.Hash.Tsuraan.Blake2.Serial+ other-modules: Crypto.Hash.Tsuraan.Blake2.Internal+ c-sources: cbits/alloc.c+ -- other-extensions: + build-depends: base >=4.6 && < 4.8+ , bytestring >=0.10+ hs-source-dirs: src+ include-dirs: cbits+ default-language: Haskell2010+ ghc-options: -Wall+ extra-libraries: b2++benchmark benchmark-all+ type: exitcode-stdio-1.0+ main-is: BenchAll.hs+ build-depends: base >=4.6 && < 4.8+ , bytestring >=0.10+ , cryptohash+ , criterion+ , hs-blake2+ hs-source-dirs: benches+ default-language: Haskell2010+ ghc-options: -Wall+ extra-libraries: b2++test-suite test-all+ type: exitcode-stdio-1.0+ main-is: TestAll.hs+ build-depends: base >=4.6 && < 4.8+ , bytestring >=0.10+ , bytestring-arbitrary+ , hs-blake2+ , QuickCheck+ , tasty+ , tasty-quickcheck+ hs-source-dirs: tests+ default-language: Haskell2010+ ghc-options: -Wall+ extra-libraries: b2
+ src/Crypto/Hash/Tsuraan/Blake2.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE OverloadedStrings #-}+module Crypto.Hash.Tsuraan.Blake2+( hash+, hash_key+) where++import Data.ByteString ( ByteString, length )+import Prelude hiding ( length )++import qualified Crypto.Hash.Tsuraan.Blake2.Parallel as Par+import qualified Crypto.Hash.Tsuraan.Blake2.Serial as Ser++-- | Hash a strict 'ByteString' into a digest 'ByteString' using a key. This+-- will choose to use parallel or serial Blake2 depending on the size of the+-- input 'ByteString'.+hash_key :: ByteString -- ^The key to use when hashing+ -> Int -- ^The digest size to generate; must be 1-64+ -> ByteString -- ^The 'ByteString' to hash+ -> ByteString+hash_key key hashlen bytes =+ if length bytes < cutoff+ then Ser.hash_key key hashlen bytes+ else Par.hash_key key hashlen bytes++-- | Hash a strict 'ByteString' into a digest 'ByteString'. This will choose to+-- use parallel or serial Blake2 depending on the size of the input+-- 'ByteString'+hash :: Int -- ^The digest size to generate; must be 1-64+ -> ByteString -- ^The 'ByteString' to hash+ -> ByteString+hash hashlen bytes =+ if length bytes < cutoff+ then Ser.hash hashlen bytes+ else Par.hash hashlen bytes++-- This is a fairly sane cross-over point for when a hash is faster to+-- calculate in parallel than serially. This was found through experimentation,+-- so there's probably a smarter way to deal with it.+cutoff :: Int+cutoff = 5000+
+ src/Crypto/Hash/Tsuraan/Blake2/Internal.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE OverloadedStrings #-}+module Crypto.Hash.Tsuraan.Blake2.Internal+( BlakeState(..)+, runInit+, runInitKey+, runUpdate+, runFinalize+, runHasher+) where++import Data.ByteString.Internal ( create, toForeignPtr )+import Data.ByteString ( ByteString )+import System.IO.Unsafe ( unsafePerformIO )+import Foreign.ForeignPtr ( ForeignPtr, withForeignPtr, mallocForeignPtr )+import Foreign.Ptr ( Ptr, plusPtr )+import Foreign.C ( CInt(..) )+import Foreign.Storable ( Storable )+import Data.Word ( Word8 )++type BlakeHasher =+ Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> Int -> Int -> Int -> IO CInt++data BlakeState a = BlakeState Int (ForeignPtr a) deriving ( Show )++runInit :: Storable a => (Ptr a -> Int -> IO CInt) -> Int -> IO (BlakeState a)+runInit ifn outlen = do+ fptr <- mallocForeignPtr+ withForeignPtr fptr $ \ptr -> do+ success <- ifn ptr outlen+ if success == 0+ then return $ BlakeState outlen fptr+ else error "runInit failure; probably a bad digest size"++runInitKey :: Storable a => (Ptr a -> Int -> Ptr Word8 -> Int -> IO CInt)+ -> ByteString -> Int -> IO (BlakeState a)+runInitKey ifn key outlen =+ let (kforeignptr, koff, klen) = toForeignPtr key+ in withForeignPtr kforeignptr $ \kptr -> do+ fptr <- mallocForeignPtr+ withForeignPtr fptr $ \ptr -> do+ success <- ifn ptr outlen (kptr `plusPtr` koff) klen+ if success == 0+ then return $ BlakeState outlen fptr+ else error "runInit failure; bad digest size or bad key size"++runUpdate :: (Ptr a -> Ptr Word8 -> Int -> IO CInt)+ -> BlakeState a -> ByteString -> IO ()+runUpdate ufn (BlakeState _ ctx) string =+ let (sforeignptr, soff, slen) = toForeignPtr string+ in withForeignPtr sforeignptr $ \sptr ->+ withForeignPtr ctx $ \cptr -> do+ _ <- ufn cptr (sptr `plusPtr` soff) slen+ return ()++runFinalize :: (Ptr a -> Ptr Word8 -> Int -> IO CInt)+ -> BlakeState a -> IO ByteString+runFinalize ffn (BlakeState hashlen fptr) =+ create hashlen $ \hptr ->+ withForeignPtr fptr $ \ctx -> do+ _ <- ffn ctx hptr hashlen+ return ()++runHasher :: BlakeHasher -> ByteString -> Int -> ByteString -> ByteString+runHasher hasher key hashlen bytes = unsafePerformIO $ create hashlen $ \hptr ->+ let (kforeignptr, koff, klen) = toForeignPtr key+ (bforeignptr, boff, blen) = toForeignPtr bytes+ in withForeignPtr kforeignptr $ \kptr ->+ withForeignPtr bforeignptr $ \bptr -> do+ result <- hasher hptr (bptr `plusPtr` boff) (kptr `plusPtr` koff)+ hashlen blen klen+ if result /= 0+ then error "Blake2bp Error: probably bad key length or hash length"+ else return ()++-- Inline here gives us a ~15% speedup on ghc 7.8.2+{-# INLINE runHasher #-}+
+ src/Crypto/Hash/Tsuraan/Blake2/Parallel.hs view
@@ -0,0 +1,92 @@+{-# LANGUAGE OverloadedStrings #-}+module Crypto.Hash.Tsuraan.Blake2.Parallel+( Ctx+, init+, init_key+, update+, finalize+, hash+, hash_key+) where++import Data.ByteString ( ByteString )+import System.IO.Unsafe ( unsafePerformIO )+import Foreign.C ( CInt(..) )+import Foreign.Ptr ( Ptr )+import Foreign.Storable ( Storable(..) )+import Data.Word ( Word8 )++import Crypto.Hash.Tsuraan.Blake2.Internal ( BlakeState(..), runHasher, runInit, runInitKey, runUpdate, runFinalize )++import Prelude hiding ( init )++data Blake2bpState++-- |Opaque type that tracks the Blake2 hashing state. The update and finalize+-- functions mutate this context.+newtype Ctx = Ctx (BlakeState Blake2bpState) deriving ( Show )++instance Storable Blake2bpState where+ sizeOf _ = unsafePerformIO blake2bp_size+ alignment _ = 64 -- from blake2.h; this should be automagical, I think+ peek _ = error "no peek"+ poke _a _b = error "no poke"++foreign import ccall "blake2.h blake2bp" blake2bp+ :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> Int -> Int -> Int -> IO CInt++foreign import ccall "blake2.h blake2bp_init" blake2bp_init+ :: Ptr Blake2bpState -> Int -> IO CInt++foreign import ccall "blake2.h blake2bp_init_key" blake2bp_init_key+ :: Ptr Blake2bpState -> Int -> Ptr Word8 -> Int -> IO CInt++foreign import ccall "blake2.h blake2bp_update" blake2bp_update+ :: Ptr Blake2bpState -> Ptr Word8 -> Int -> IO CInt++foreign import ccall "blake2.h blake2bp_final" blake2bp_final+ :: Ptr Blake2bpState -> Ptr Word8 -> Int -> IO CInt++foreign import ccall "alloc.h blake2bp_size" blake2bp_size+ :: IO Int++-- |Create a hashing context.+init :: Int -- ^Desired digest size+ -> IO Ctx+init outlen = Ctx `fmap` runInit blake2bp_init outlen++-- |Create a hashing context for key-based hashing.+init_key :: ByteString -- ^Desired hashing key+ -> Int -- ^Desired digest size+ -> IO Ctx+init_key key outlen = Ctx `fmap` runInitKey blake2bp_init_key key outlen++-- |Add more data to the hash.+update :: Ctx -- ^Hashing context+ -> ByteString -- ^Data to add to the hash+ -> IO ()+update (Ctx ptr) bs = runUpdate blake2bp_update ptr bs++-- |Finish hashing. This returns the digest of all the data that's been given+-- to the 'update' function.+finalize :: Ctx -- ^Hashing context+ -> IO ByteString+finalize (Ctx state) = runFinalize blake2bp_final state++-- |Hash a 'ByteString' into a digest 'ByteString' using a key. This function+-- always runs in parallel, which is slower for very small strings but faster+-- as the strings get larger.+hash_key :: ByteString -- ^The key to hash with+ -> Int -- ^The digest size to generate; must be 1-64+ -> ByteString -- ^The string to hash+ -> ByteString+hash_key key hashlen bytes = runHasher blake2bp key hashlen bytes++-- |Hash a 'ByteString' into a digest 'ByteString'. This function always runs+-- in parallel, which is slower for very small strings but faster as the+-- strings get larger.+hash :: Int -- ^The digest size to generate; must be 1-64+ -> ByteString -- ^The string to hash+ -> ByteString+hash = hash_key ""+
+ src/Crypto/Hash/Tsuraan/Blake2/Serial.hs view
@@ -0,0 +1,92 @@+{-# LANGUAGE OverloadedStrings #-}+module Crypto.Hash.Tsuraan.Blake2.Serial+( Ctx+, init+, init_key+, update+, finalize+, hash+, hash_key+) where++import Data.ByteString ( ByteString )+import System.IO.Unsafe ( unsafePerformIO )+import Foreign.C ( CInt(..) )+import Foreign.Ptr ( Ptr )+import Foreign.Storable ( Storable(..) )+import Data.Word ( Word8 )++import Crypto.Hash.Tsuraan.Blake2.Internal ( BlakeState(..), runHasher, runInit, runInitKey, runUpdate, runFinalize )++import Prelude hiding ( init )++data Blake2bState++-- |Opaque type that tracks the Blake2 hashing state. The update and finalize+-- functions mutate this context.+newtype Ctx = Ctx (BlakeState Blake2bState) deriving ( Show )++instance Storable Blake2bState where+ sizeOf _ = unsafePerformIO blake2b_size+ alignment _ = 64 -- from blake2.h; this should be automagical, I think+ peek _ = error "no peek"+ poke _a _b = error "no poke"++foreign import ccall "blake2.h blake2b" blake2b+ :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> Int -> Int -> Int -> IO CInt++foreign import ccall "blake2.h blake2b_init" blake2b_init+ :: Ptr Blake2bState -> Int -> IO CInt++foreign import ccall "blake2.h blake2b_init_key" blake2b_init_key+ :: Ptr Blake2bState -> Int -> Ptr Word8 -> Int -> IO CInt++foreign import ccall "blake2.h blake2b_update" blake2b_update+ :: Ptr Blake2bState -> Ptr Word8 -> Int -> IO CInt++foreign import ccall "blake2.h blake2b_final" blake2b_final+ :: Ptr Blake2bState -> Ptr Word8 -> Int -> IO CInt++foreign import ccall "alloc.h blake2b_size" blake2b_size+ :: IO Int++-- |Create a hashing context.+init :: Int -- ^Desired digest size+ -> IO Ctx+init outlen = Ctx `fmap` runInit blake2b_init outlen++-- |Create a hashing context for key-based hashing.+init_key :: ByteString -- ^Desired hashing key+ -> Int -- ^Desired digest size+ -> IO Ctx+init_key key outlen = Ctx `fmap` runInitKey blake2b_init_key key outlen++-- |Add more data to the hash.+update :: Ctx -- ^Hashing context+ -> ByteString -- ^Data to add to the hash+ -> IO ()+update (Ctx ptr) bs = runUpdate blake2b_update ptr bs++-- |Finish hashing. This returns the digest of all the data that's been given+-- to the 'update' function.+finalize :: Ctx -- ^Hashing context+ -> IO ByteString+finalize (Ctx state) = runFinalize blake2b_final state++-- |Hash a 'ByteString' into a digest 'ByteString' using a key. This function+-- always runs in serial, which is faster for very small strings but slower as+-- the strings get larger.+hash_key :: ByteString -- ^The key to hash with+ -> Int -- ^The digest size to generate; must be 1-64+ -> ByteString -- ^The string to hash+ -> ByteString+hash_key key hashlen bytes = runHasher blake2b key hashlen bytes++-- |Hash a 'ByteString' into a digest 'ByteString'. This function always runs+-- in serial, which is faster for very small strings but slower as the strings+-- get larger.+hash :: Int -- ^The digest size to generate; must be 1-64+ -> ByteString -- ^The string to hash+ -> ByteString+hash = hash_key ""+
+ tests/TestAll.hs view
@@ -0,0 +1,70 @@+module Main+( main+) where++import qualified Data.ByteString as BS+import Data.ByteString.Arbitrary ( ArbByteString(..) )+import Data.ByteString ( ByteString )+import Control.Monad ( replicateM )++import qualified Crypto.Hash.Tsuraan.Blake2.Parallel as P+import qualified Crypto.Hash.Tsuraan.Blake2.Serial as S++import Test.QuickCheck+import Test.QuickCheck.Monadic+import Test.Tasty+import Test.Tasty.QuickCheck ( testProperty )++main :: IO ()+main = defaultMain $+ testGroup+ "Blake2"+ [ testGroup "Simple"+ [ testProperty "Parallel"+ $ update_equals_hash P.init P.update P.finalize P.hash+ , testProperty "Serial"+ $ update_equals_hash S.init S.update S.finalize S.hash+ ]+ , testGroup "Keyed"+ [ testProperty "Parallel"+ $ key_update_equals_hash P.init_key P.update P.finalize P.hash_key+ , testProperty "Serial"+ $ key_update_equals_hash S.init_key S.update S.finalize S.hash_key+ ]+ ]++update_equals_hash :: (Int -> IO a)+ -> (a -> ByteString -> IO())+ -> (a -> IO ByteString)+ -> (Int -> ByteString -> ByteString)+ -> Property+update_equals_hash ini upd fin hash = monadicIO $ do+ num <- pick $ choose (1,20)+ strs <- replicateM num $ fromABS `fmap` pick arbitrary+ sz <- pick $ choose (1,64)++ h <- run $ do+ ctx <- ini sz+ mapM_ (upd ctx) strs+ fin ctx+ assert $ h == hash sz (BS.concat strs)++key_update_equals_hash :: (ByteString -> Int -> IO a)+ -> (a -> ByteString -> IO())+ -> (a -> IO ByteString)+ -> (ByteString -> Int -> ByteString -> ByteString)+ -> Property+key_update_equals_hash ini upd fin hash = monadicIO $ do+ keylen <- pick $ choose (1,64)+ keyw8s <- replicateM keylen $ pick arbitrary+ num <- pick $ choose (1,20)+ strs <- replicateM num $ fromABS `fmap` pick arbitrary+ sz <- pick $ choose (1,64)++ let key = BS.pack keyw8s+ h <- run $ do+ ctx <- ini key sz+ mapM_ (upd ctx) strs+ fin ctx+ assert $ h == hash key sz (BS.concat strs)+