diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/benches/BenchAll.hs b/benches/BenchAll.hs
new file mode 100644
--- /dev/null
+++ b/benches/BenchAll.hs
@@ -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 ]
+
diff --git a/cbits/alloc.c b/cbits/alloc.c
new file mode 100644
--- /dev/null
+++ b/cbits/alloc.c
@@ -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);
+}
+
diff --git a/cbits/alloc.h b/cbits/alloc.h
new file mode 100644
--- /dev/null
+++ b/cbits/alloc.h
@@ -0,0 +1,2 @@
+void *malloc_blake2b();
+void *malloc_blake2bp();
diff --git a/hs-blake2.cabal b/hs-blake2.cabal
new file mode 100644
--- /dev/null
+++ b/hs-blake2.cabal
@@ -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
diff --git a/src/Crypto/Hash/Tsuraan/Blake2.hs b/src/Crypto/Hash/Tsuraan/Blake2.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Hash/Tsuraan/Blake2.hs
@@ -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
+
diff --git a/src/Crypto/Hash/Tsuraan/Blake2/Internal.hs b/src/Crypto/Hash/Tsuraan/Blake2/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Hash/Tsuraan/Blake2/Internal.hs
@@ -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 #-}
+
diff --git a/src/Crypto/Hash/Tsuraan/Blake2/Parallel.hs b/src/Crypto/Hash/Tsuraan/Blake2/Parallel.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Hash/Tsuraan/Blake2/Parallel.hs
@@ -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 ""
+
diff --git a/src/Crypto/Hash/Tsuraan/Blake2/Serial.hs b/src/Crypto/Hash/Tsuraan/Blake2/Serial.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Hash/Tsuraan/Blake2/Serial.hs
@@ -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 ""
+
diff --git a/tests/TestAll.hs b/tests/TestAll.hs
new file mode 100644
--- /dev/null
+++ b/tests/TestAll.hs
@@ -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)
+
