diff --git a/.travis.yml b/.travis.yml
new file mode 100644
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,84 @@
+# This file has been generated -- see https://github.com/hvr/multi-ghc-travis
+language: c
+sudo: false
+
+cache:
+  directories:
+    - $HOME/.cabsnap
+    - $HOME/.cabal/packages
+
+before_cache:
+  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log
+  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.tar
+
+matrix:
+  include:
+    - env: CABALVER=1.22 GHCVER=7.10.1
+      compiler: ": #GHC 7.10.1"
+      addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.1,alex-3.1.4,happy-1.19.5], sources: [hvr-ghc]}}
+    - env: CABALVER=1.22 GHCVER=7.10.2
+      compiler: ": #GHC 7.10.2"
+      addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.2,alex-3.1.4,happy-1.19.5], sources: [hvr-ghc]}}
+    - env: CABALVER=1.22 GHCVER=head
+      compiler: ": #GHC head"
+      addons: {apt: {packages: [cabal-install-1.22,ghc-head,alex-3.1.4,happy-1.19.5], sources: [hvr-ghc]}}
+
+  allow_failures:
+   - env: CABALVER=1.22 GHCVER=head
+
+before_install:
+  - unset CC
+  - export HAPPYVER=1.19.5
+  - export ALEXVER=3.1.4
+  - export PATH=~/.cabal/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:/opt/happy/$HAPPYVER/bin:/opt/alex/$ALEXVER/bin:$PATH
+
+install:
+  - cabal --version
+  - echo "$(ghc --version) [$(ghc --print-project-git-commit-id 2> /dev/null || echo '?')]"
+  - if [ -f $HOME/.cabal/packages/hackage.haskell.org/00-index.tar.gz ];
+    then
+      zcat $HOME/.cabal/packages/hackage.haskell.org/00-index.tar.gz >
+           $HOME/.cabal/packages/hackage.haskell.org/00-index.tar;
+    fi
+  - travis_retry cabal update
+  - "sed -i  's/^jobs:.*$/jobs: 2/' $HOME/.cabal/config"
+  - cabal install --only-dependencies --enable-tests --dry -v blake2.cabal > installplan.txt
+  - sed -i -e '1,/^Resolving /d' installplan.txt; cat installplan.txt
+
+  # check whether current requested install-plan matches cached package-db snapshot
+  - if diff -u installplan.txt $HOME/.cabsnap/installplan.txt;
+    then
+      echo "cabal build-cache HIT";
+      rm -rfv .ghc;
+      cp -a $HOME/.cabsnap/ghc $HOME/.ghc;
+      cp -a $HOME/.cabsnap/lib $HOME/.cabsnap/share $HOME/.cabsnap/bin $HOME/.cabal/;
+    else
+      echo "cabal build-cache MISS";
+      rm -rf $HOME/.cabsnap;
+      mkdir -p $HOME/.ghc $HOME/.cabal/lib $HOME/.cabal/share $HOME/.cabal/bin;
+      cabal install --only-dependencies --enable-tests blake2.cabal;
+      if [ "$GHCVER" = "7.10.1" ]; then cabal install Cabal-1.22.4.0; fi;
+    fi
+
+  # snapshot package-db on cache miss
+  - if [ ! -d $HOME/.cabsnap ];
+    then
+      echo "snapshotting package-db to build-cache";
+      mkdir $HOME/.cabsnap;
+      cp -a $HOME/.ghc $HOME/.cabsnap/ghc;
+      cp -a $HOME/.cabal/lib $HOME/.cabal/share $HOME/.cabal/bin installplan.txt $HOME/.cabsnap/;
+    fi
+
+  - cabal install
+
+script:
+  - cabal configure --enable-tests -v2  # -v2 provides useful information for debugging
+  - cabal build   # this builds all libraries and executables (including tests)
+  - cabal test
+  - cabal sdist   # tests that a source-distribution can be generated
+
+  # Check that the resulting source distribution can be built & installed.
+  # If there are no other `.tar.gz` files in `dist`, this can be even simpler:
+  # `cabal install --force-reinstalls dist/*-*.tar.gz`
+  - SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz &&
+    (cd dist && cabal install --enable-tests --run-tests --force-reinstalls "$SRC_TGZ")
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org>
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,6 @@
+# blake2
+
+[![Build Status](https://travis-ci.org/centromere/blake2.svg?branch=master)](https://travis-ci.org/centromere/blake2)
+[![Haskell](http://b.repl.ca/v1/language-haskell-blue.png)](http://www.haskell.org)
+
+This library provides the [BLAKE2](https://blake2.net/) hash algorithms.
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/benchmarks/bench.hs b/benchmarks/bench.hs
new file mode 100644
--- /dev/null
+++ b/benchmarks/bench.hs
@@ -0,0 +1,40 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Main where
+
+import Criterion.Main
+import Data.ByteString                (ByteString)
+import qualified Data.ByteString as B (replicate)
+
+import qualified Crypto.Hash.BLAKE2.BLAKE2b as B2b
+import qualified Crypto.Hash.BLAKE2.BLAKE2bp as B2bp
+import qualified Crypto.Hash.BLAKE2.BLAKE2s as B2s
+import qualified Crypto.Hash.BLAKE2.BLAKE2sp as B2sp
+
+kb :: ByteString
+kb = B.replicate 1024 3
+
+mb :: ByteString
+mb = B.replicate (1024^2) 3
+
+gb :: ByteString
+gb = B.replicate (1024^3) 3
+
+main :: IO ()
+main = defaultMain
+  [ bgroup "blake2s" [ bench "1 kb" $ nf (B2s.hash 32 "") kb
+                     , bench "1 mb" $ nf (B2s.hash 32 "") mb
+                     , bench "1 gb" $ nf (B2s.hash 32 "") gb
+                     ]
+  , bgroup "blake2sp" [ bench "1 kb" $ nf (B2sp.hash 32 "") kb
+                      , bench "1 mb" $ nf (B2sp.hash 32 "") mb
+                      , bench "1 gb" $ nf (B2sp.hash 32 "") gb
+                      ]
+  , bgroup "blake2b" [ bench "1 kb" $ nf (B2b.hash 64 "") kb
+                     , bench "1 mb" $ nf (B2b.hash 64 "") mb
+                     , bench "1 gb" $ nf (B2b.hash 64 "") gb
+                     ]
+  , bgroup "blake2bp" [ bench "1 kb" $ nf (B2bp.hash 64 "") kb
+                      , bench "1 mb" $ nf (B2bp.hash 64 "") mb
+                      , bench "1 gb" $ nf (B2bp.hash 64 "") gb
+                      ]
+  ]
diff --git a/blake2.cabal b/blake2.cabal
new file mode 100644
--- /dev/null
+++ b/blake2.cabal
@@ -0,0 +1,129 @@
+name:          blake2
+version:       0.1.0
+synopsis:      A library providing BLAKE2
+license:       PublicDomain
+license-file:  LICENSE
+author:        John Galt
+maintainer:    jgalt@centromere.net
+homepage:      https://github.com/centromere/blake2
+bug-reports:   https://github.com/centromere/blake2/issues
+category:      Cryptography
+build-type:    Simple
+cabal-version: >=1.10
+tested-with:   GHC == 7.10.1, GHC == 7.10.2
+description:
+  This library provides the <https://blake2.net/ BLAKE2> hash algorithms.
+
+extra-source-files:
+  .travis.yml
+  README.md
+  changelog.md
+  LICENSE
+  cbits/blake2s.c
+  cbits/blake2bp.c
+  cbits/blake2sp.c
+  cbits/blake2b.c
+  include/blake2.h
+  include/blake2s-round.h
+  include/blake2b-round.h
+  include/blake2b-load-sse41.h
+  include/blake2s-load-sse2.h
+  include/blake2b-load-sse2.h
+  include/blake2s-load-sse41.h
+  include/blake2-impl.h
+  include/blake2-config.h
+  include/blake2s-load-xop.h
+
+source-repository head
+  type: git
+  location: https://github.com/centromere/blake2.git
+
+--------------------------------------------------------------------------------
+-- FLAGS
+
+flag hlint
+
+flag doctest
+
+flag llvm
+  default: False
+  manual: True
+
+--------------------------------------------------------------------------------
+-- LIBRARY
+
+library
+  build-depends:
+    base >=4.8 && <5,
+    bytestring
+  hs-source-dirs:   src
+  default-language: Haskell2010
+  exposed-modules:
+    Crypto.Hash.BLAKE2.BLAKE2b
+    Crypto.Hash.BLAKE2.BLAKE2bp
+    Crypto.Hash.BLAKE2.BLAKE2s
+    Crypto.Hash.BLAKE2.BLAKE2sp
+    Crypto.Hash.BLAKE2.Internal
+  ghc-options:      -rtsopts -with-rtsopts=-N -Wall -fwarn-tabs
+  cc-options:       -std=c99
+  include-dirs:     include
+  c-sources:
+    cbits/blake2b.c
+    cbits/blake2bp.c
+    cbits/blake2s.c
+    cbits/blake2sp.c
+
+  if flag(llvm)
+    ghc-options: -fllvm
+
+--------------------------------------------------------------------------------
+-- TESTS
+
+test-suite properties
+  type:             exitcode-stdio-1.0
+  main-is:          properties.hs
+  ghc-options:      -Wall -fwarn-tabs
+  hs-source-dirs:   tests
+  default-language: Haskell2010
+
+  build-depends:
+    base >=4.8 && <5,
+    base16-bytestring,
+    bytestring,
+    blake2,
+    QuickCheck,
+    tasty,
+    tasty-quickcheck
+
+  other-modules:
+    Imports
+
+test-suite hlint
+  type:             exitcode-stdio-1.0
+  main-is:          hlint.hs
+  ghc-options:      -Wall -fwarn-tabs
+  hs-source-dirs:   tests
+  default-language: Haskell2010
+
+  if !flag(hlint)
+    buildable: False
+  else
+    build-depends:
+      base  >=4.8 && <5,
+      hlint
+
+--------------------------------------------------------------------------------
+-- BENCHMARKS
+
+benchmark bench
+  type:             exitcode-stdio-1.0
+  main-is:          bench.hs
+  ghc-options:      -Wall -fwarn-tabs
+  hs-source-dirs:   benchmarks
+  default-language: Haskell2010
+
+  build-depends:
+    base >=4.8 && <5,
+    blake2,
+    bytestring,
+    criterion
diff --git a/cbits/blake2b.c b/cbits/blake2b.c
new file mode 100644
--- /dev/null
+++ b/cbits/blake2b.c
@@ -0,0 +1,439 @@
+/*
+   BLAKE2 reference source code package - optimized C implementations
+
+   Written in 2012 by Samuel Neves <sneves@dei.uc.pt>
+
+   To the extent possible under law, the author(s) have dedicated all copyright
+   and related and neighboring rights to this software to the public domain
+   worldwide. This software is distributed without any warranty.
+
+   You should have received a copy of the CC0 Public Domain Dedication along with
+   this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
+*/
+
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "blake2.h"
+#include "blake2-impl.h"
+
+#include "blake2-config.h"
+
+#ifdef _MSC_VER
+#include <intrin.h> /* for _mm_set_epi64x */
+#endif
+#include <emmintrin.h>
+#if defined(HAVE_SSSE3)
+#include <tmmintrin.h>
+#endif
+#if defined(HAVE_SSE41)
+#include <smmintrin.h>
+#endif
+#if defined(HAVE_AVX)
+#include <immintrin.h>
+#endif
+#if defined(HAVE_XOP)
+#include <x86intrin.h>
+#endif
+
+#include "blake2b-round.h"
+
+static const uint64_t blake2b_IV[8] =
+{
+  0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL,
+  0x3c6ef372fe94f82bULL, 0xa54ff53a5f1d36f1ULL,
+  0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL,
+  0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL
+};
+
+static const uint8_t blake2b_sigma[12][16] =
+{
+  {  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15 } ,
+  { 14, 10,  4,  8,  9, 15, 13,  6,  1, 12,  0,  2, 11,  7,  5,  3 } ,
+  { 11,  8, 12,  0,  5,  2, 15, 13, 10, 14,  3,  6,  7,  1,  9,  4 } ,
+  {  7,  9,  3,  1, 13, 12, 11, 14,  2,  6,  5, 10,  4,  0, 15,  8 } ,
+  {  9,  0,  5,  7,  2,  4, 10, 15, 14,  1, 11, 12,  6,  8,  3, 13 } ,
+  {  2, 12,  6, 10,  0, 11,  8,  3,  4, 13,  7,  5, 15, 14,  1,  9 } ,
+  { 12,  5,  1, 15, 14, 13,  4, 10,  0,  7,  6,  3,  9,  2,  8, 11 } ,
+  { 13, 11,  7, 14, 12,  1,  3,  9,  5,  0, 15,  4,  8,  6,  2, 10 } ,
+  {  6, 15, 14,  9, 11,  3,  0,  8, 12,  2, 13,  7,  1,  4, 10,  5 } ,
+  { 10,  2,  8,  4,  7,  6,  1,  5, 15, 11,  9, 14,  3, 12, 13 , 0 } ,
+  {  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15 } ,
+  { 14, 10,  4,  8,  9, 15, 13,  6,  1, 12,  0,  2, 11,  7,  5,  3 }
+};
+
+
+/* Some helper functions, not necessarily useful */
+static inline int blake2b_set_lastnode( blake2b_state *S )
+{
+  S->f[1] = -1;
+  return 0;
+}
+
+static inline int blake2b_clear_lastnode( blake2b_state *S )
+{
+  S->f[1] = 0;
+  return 0;
+}
+
+static inline int blake2b_set_lastblock( blake2b_state *S )
+{
+  if( S->last_node ) blake2b_set_lastnode( S );
+
+  S->f[0] = -1;
+  return 0;
+}
+
+static inline int blake2b_clear_lastblock( blake2b_state *S )
+{
+  if( S->last_node ) blake2b_clear_lastnode( S );
+
+  S->f[0] = 0;
+  return 0;
+}
+
+
+static inline int blake2b_increment_counter( blake2b_state *S, const uint64_t inc )
+{
+#if __x86_64__
+  // ADD/ADC chain
+  __uint128_t t = ( ( __uint128_t )S->t[1] << 64 ) | S->t[0];
+  t += inc;
+  S->t[0] = ( uint64_t )( t >>  0 );
+  S->t[1] = ( uint64_t )( t >> 64 );
+#else
+  S->t[0] += inc;
+  S->t[1] += ( S->t[0] < inc );
+#endif
+  return 0;
+}
+
+
+// Parameter-related functions
+static inline int blake2b_param_set_digest_length( blake2b_param *P, const uint8_t digest_length )
+{
+  P->digest_length = digest_length;
+  return 0;
+}
+
+static inline int blake2b_param_set_fanout( blake2b_param *P, const uint8_t fanout )
+{
+  P->fanout = fanout;
+  return 0;
+}
+
+static inline int blake2b_param_set_max_depth( blake2b_param *P, const uint8_t depth )
+{
+  P->depth = depth;
+  return 0;
+}
+
+static inline int blake2b_param_set_leaf_length( blake2b_param *P, const uint32_t leaf_length )
+{
+  P->leaf_length = leaf_length;
+  return 0;
+}
+
+static inline int blake2b_param_set_node_offset( blake2b_param *P, const uint64_t node_offset )
+{
+  P->node_offset = node_offset;
+  return 0;
+}
+
+static inline int blake2b_param_set_node_depth( blake2b_param *P, const uint8_t node_depth )
+{
+  P->node_depth = node_depth;
+  return 0;
+}
+
+static inline int blake2b_param_set_inner_length( blake2b_param *P, const uint8_t inner_length )
+{
+  P->inner_length = inner_length;
+  return 0;
+}
+
+static inline int blake2b_param_set_salt( blake2b_param *P, const uint8_t salt[BLAKE2B_SALTBYTES] )
+{
+  memcpy( P->salt, salt, BLAKE2B_SALTBYTES );
+  return 0;
+}
+
+static inline int blake2b_param_set_personal( blake2b_param *P, const uint8_t personal[BLAKE2B_PERSONALBYTES] )
+{
+  memcpy( P->personal, personal, BLAKE2B_PERSONALBYTES );
+  return 0;
+}
+
+static inline int blake2b_init0( blake2b_state *S )
+{
+  memset( S, 0, sizeof( blake2b_state ) );
+
+  for( int i = 0; i < 8; ++i ) S->h[i] = blake2b_IV[i];
+
+  return 0;
+}
+
+/* init xors IV with input parameter block */
+int blake2b_init_param( blake2b_state *S, const blake2b_param *P )
+{
+  //blake2b_init0( S );
+  const uint8_t * v = ( const uint8_t * )( blake2b_IV );
+  const uint8_t * p = ( const uint8_t * )( P );
+  uint8_t * h = ( uint8_t * )( S->h );
+  /* IV XOR ParamBlock */
+  memset( S, 0, sizeof( blake2b_state ) );
+
+  for( int i = 0; i < BLAKE2B_OUTBYTES; ++i ) h[i] = v[i] ^ p[i];
+
+  return 0;
+}
+
+
+/* Some sort of default parameter block initialization, for sequential blake2b */
+int blake2b_init( blake2b_state *S, const uint8_t outlen )
+{
+  if ( ( !outlen ) || ( outlen > BLAKE2B_OUTBYTES ) ) return -1;
+
+  const blake2b_param P =
+  {
+    outlen,
+    0,
+    1,
+    1,
+    0,
+    0,
+    0,
+    0,
+    {0},
+    {0},
+    {0}
+  };
+  return blake2b_init_param( S, &P );
+}
+
+int blake2b_init_key( blake2b_state *S, const uint8_t outlen, const void *key, const uint8_t keylen )
+{
+  if ( ( !outlen ) || ( outlen > BLAKE2B_OUTBYTES ) ) return -1;
+
+  if ( ( !keylen ) || keylen > BLAKE2B_KEYBYTES ) return -1;
+
+  const blake2b_param P =
+  {
+    outlen,
+    keylen,
+    1,
+    1,
+    0,
+    0,
+    0,
+    0,
+    {0},
+    {0},
+    {0}
+  };
+
+  if( blake2b_init_param( S, &P ) < 0 )
+    return 0;
+
+  {
+    uint8_t block[BLAKE2B_BLOCKBYTES];
+    memset( block, 0, BLAKE2B_BLOCKBYTES );
+    memcpy( block, key, keylen );
+    blake2b_update( S, block, BLAKE2B_BLOCKBYTES );
+    secure_zero_memory( block, BLAKE2B_BLOCKBYTES ); /* Burn the key from stack */
+  }
+  return 0;
+}
+
+static inline int blake2b_compress( blake2b_state *S, const uint8_t block[BLAKE2B_BLOCKBYTES] )
+{
+  __m128i row1l, row1h;
+  __m128i row2l, row2h;
+  __m128i row3l, row3h;
+  __m128i row4l, row4h;
+  __m128i b0, b1;
+  __m128i t0, t1;
+#if defined(HAVE_SSSE3) && !defined(HAVE_XOP)
+  const __m128i r16 = _mm_setr_epi8( 2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9 );
+  const __m128i r24 = _mm_setr_epi8( 3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10 );
+#endif
+#if defined(HAVE_SSE41)
+  const __m128i m0 = LOADU( block + 00 );
+  const __m128i m1 = LOADU( block + 16 );
+  const __m128i m2 = LOADU( block + 32 );
+  const __m128i m3 = LOADU( block + 48 );
+  const __m128i m4 = LOADU( block + 64 );
+  const __m128i m5 = LOADU( block + 80 );
+  const __m128i m6 = LOADU( block + 96 );
+  const __m128i m7 = LOADU( block + 112 );
+#else
+  const uint64_t  m0 = ( ( uint64_t * )block )[ 0];
+  const uint64_t  m1 = ( ( uint64_t * )block )[ 1];
+  const uint64_t  m2 = ( ( uint64_t * )block )[ 2];
+  const uint64_t  m3 = ( ( uint64_t * )block )[ 3];
+  const uint64_t  m4 = ( ( uint64_t * )block )[ 4];
+  const uint64_t  m5 = ( ( uint64_t * )block )[ 5];
+  const uint64_t  m6 = ( ( uint64_t * )block )[ 6];
+  const uint64_t  m7 = ( ( uint64_t * )block )[ 7];
+  const uint64_t  m8 = ( ( uint64_t * )block )[ 8];
+  const uint64_t  m9 = ( ( uint64_t * )block )[ 9];
+  const uint64_t m10 = ( ( uint64_t * )block )[10];
+  const uint64_t m11 = ( ( uint64_t * )block )[11];
+  const uint64_t m12 = ( ( uint64_t * )block )[12];
+  const uint64_t m13 = ( ( uint64_t * )block )[13];
+  const uint64_t m14 = ( ( uint64_t * )block )[14];
+  const uint64_t m15 = ( ( uint64_t * )block )[15];
+#endif
+  row1l = LOADU( &S->h[0] );
+  row1h = LOADU( &S->h[2] );
+  row2l = LOADU( &S->h[4] );
+  row2h = LOADU( &S->h[6] );
+  row3l = LOADU( &blake2b_IV[0] );
+  row3h = LOADU( &blake2b_IV[2] );
+  row4l = _mm_xor_si128( LOADU( &blake2b_IV[4] ), LOADU( &S->t[0] ) );
+  row4h = _mm_xor_si128( LOADU( &blake2b_IV[6] ), LOADU( &S->f[0] ) );
+  ROUND( 0 );
+  ROUND( 1 );
+  ROUND( 2 );
+  ROUND( 3 );
+  ROUND( 4 );
+  ROUND( 5 );
+  ROUND( 6 );
+  ROUND( 7 );
+  ROUND( 8 );
+  ROUND( 9 );
+  ROUND( 10 );
+  ROUND( 11 );
+  row1l = _mm_xor_si128( row3l, row1l );
+  row1h = _mm_xor_si128( row3h, row1h );
+  STOREU( &S->h[0], _mm_xor_si128( LOADU( &S->h[0] ), row1l ) );
+  STOREU( &S->h[2], _mm_xor_si128( LOADU( &S->h[2] ), row1h ) );
+  row2l = _mm_xor_si128( row4l, row2l );
+  row2h = _mm_xor_si128( row4h, row2h );
+  STOREU( &S->h[4], _mm_xor_si128( LOADU( &S->h[4] ), row2l ) );
+  STOREU( &S->h[6], _mm_xor_si128( LOADU( &S->h[6] ), row2h ) );
+  return 0;
+}
+
+
+int blake2b_update( blake2b_state *S, const uint8_t *in, uint64_t inlen )
+{
+  while( inlen > 0 )
+  {
+    size_t left = S->buflen;
+    size_t fill = 2 * BLAKE2B_BLOCKBYTES - left;
+
+    if( inlen > fill )
+    {
+      memcpy( S->buf + left, in, fill ); // Fill buffer
+      S->buflen += fill;
+      blake2b_increment_counter( S, BLAKE2B_BLOCKBYTES );
+      blake2b_compress( S, S->buf ); // Compress
+      memcpy( S->buf, S->buf + BLAKE2B_BLOCKBYTES, BLAKE2B_BLOCKBYTES ); // Shift buffer left
+      S->buflen -= BLAKE2B_BLOCKBYTES;
+      in += fill;
+      inlen -= fill;
+    }
+    else // inlen <= fill
+    {
+      memcpy( S->buf + left, in, inlen );
+      S->buflen += inlen; // Be lazy, do not compress
+      in += inlen;
+      inlen -= inlen;
+    }
+  }
+
+  return 0;
+}
+
+
+int blake2b_final( blake2b_state *S, uint8_t *out, uint8_t outlen )
+{
+  if( outlen > BLAKE2B_OUTBYTES )
+    return -1;
+
+  if( S->buflen > BLAKE2B_BLOCKBYTES )
+  {
+    blake2b_increment_counter( S, BLAKE2B_BLOCKBYTES );
+    blake2b_compress( S, S->buf );
+    S->buflen -= BLAKE2B_BLOCKBYTES;
+    memcpy( S->buf, S->buf + BLAKE2B_BLOCKBYTES, S->buflen );
+  }
+
+  blake2b_increment_counter( S, S->buflen );
+  blake2b_set_lastblock( S );
+  memset( S->buf + S->buflen, 0, 2 * BLAKE2B_BLOCKBYTES - S->buflen ); /* Padding */
+  blake2b_compress( S, S->buf );
+  memcpy( out, &S->h[0], outlen );
+  return 0;
+}
+
+
+int blake2b( uint8_t *out, const void *in, const void *key, const uint8_t outlen, const uint64_t inlen, uint8_t keylen )
+{
+  blake2b_state S[1];
+
+  /* Verify parameters */
+  if ( NULL == in && inlen > 0 ) return -1;
+
+  if ( NULL == out ) return -1;
+
+  if( NULL == key && keylen > 0 ) return -1;
+
+  if( !outlen || outlen > BLAKE2B_OUTBYTES ) return -1;
+
+  if( keylen > BLAKE2B_KEYBYTES ) return -1;
+
+  if( keylen )
+  {
+    if( blake2b_init_key( S, outlen, key, keylen ) < 0 ) return -1;
+  }
+  else
+  {
+    if( blake2b_init( S, outlen ) < 0 ) return -1;
+  }
+
+  blake2b_update( S, ( const uint8_t * )in, inlen );
+  blake2b_final( S, out, outlen );
+  return 0;
+}
+
+#if defined(SUPERCOP)
+int crypto_hash( unsigned char *out, unsigned char *in, unsigned long long inlen )
+{
+  return blake2b( out, in, NULL, BLAKE2B_OUTBYTES, inlen, 0 );
+}
+#endif
+
+#if defined(BLAKE2B_SELFTEST)
+#include <string.h>
+#include "blake2-kat.h"
+int main( int argc, char **argv )
+{
+  uint8_t key[BLAKE2B_KEYBYTES];
+  uint8_t buf[KAT_LENGTH];
+
+  for( size_t i = 0; i < BLAKE2B_KEYBYTES; ++i )
+    key[i] = ( uint8_t )i;
+
+  for( size_t i = 0; i < KAT_LENGTH; ++i )
+    buf[i] = ( uint8_t )i;
+
+  for( size_t i = 0; i < KAT_LENGTH; ++i )
+  {
+    uint8_t hash[BLAKE2B_OUTBYTES];
+    blake2b( hash, buf, key, BLAKE2B_OUTBYTES, i, BLAKE2B_KEYBYTES );
+
+    if( 0 != memcmp( hash, blake2b_keyed_kat[i], BLAKE2B_OUTBYTES ) )
+    {
+      puts( "error" );
+      return -1;
+    }
+  }
+
+  puts( "ok" );
+  return 0;
+}
+#endif
+
diff --git a/cbits/blake2bp.c b/cbits/blake2bp.c
new file mode 100644
--- /dev/null
+++ b/cbits/blake2bp.c
@@ -0,0 +1,298 @@
+/*
+   BLAKE2 reference source code package - optimized C implementations
+
+   Written in 2012 by Samuel Neves <sneves@dei.uc.pt>
+
+   To the extent possible under law, the author(s) have dedicated all copyright
+   and related and neighboring rights to this software to the public domain
+   worldwide. This software is distributed without any warranty.
+
+   You should have received a copy of the CC0 Public Domain Dedication along with
+   this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+
+#if defined(_OPENMP)
+#include <omp.h>
+#endif
+
+#include "blake2.h"
+#include "blake2-impl.h"
+
+#define PARALLELISM_DEGREE 4
+
+static inline int blake2bp_init_leaf( blake2b_state *S, uint8_t outlen, uint8_t keylen, uint64_t offset )
+{
+  blake2b_param P[1];
+  P->digest_length = outlen;
+  P->key_length = keylen;
+  P->fanout = PARALLELISM_DEGREE;
+  P->depth = 2;
+  P->leaf_length = 0;
+  P->node_offset = offset;
+  P->node_depth = 0;
+  P->inner_length = BLAKE2B_OUTBYTES;
+  memset( P->reserved, 0, sizeof( P->reserved ) );
+  memset( P->salt, 0, sizeof( P->salt ) );
+  memset( P->personal, 0, sizeof( P->personal ) );
+  return blake2b_init_param( S, P );
+}
+
+static inline int blake2bp_init_root( blake2b_state *S, uint8_t outlen, uint8_t keylen )
+{
+  blake2b_param P[1];
+  P->digest_length = outlen;
+  P->key_length = keylen;
+  P->fanout = PARALLELISM_DEGREE;
+  P->depth = 2;
+  P->leaf_length = 0;
+  P->node_offset = 0;
+  P->node_depth = 1;
+  P->inner_length = BLAKE2B_OUTBYTES;
+  memset( P->reserved, 0, sizeof( P->reserved ) );
+  memset( P->salt, 0, sizeof( P->salt ) );
+  memset( P->personal, 0, sizeof( P->personal ) );
+  return blake2b_init_param( S, P );
+}
+
+
+int blake2bp_init( blake2bp_state *S, const uint8_t outlen )
+{
+  if( !outlen || outlen > BLAKE2B_OUTBYTES ) return -1;
+
+  memset( S->buf, 0, sizeof( S->buf ) );
+  S->buflen = 0;
+
+  if( blake2bp_init_root( S->R, outlen, 0 ) < 0 )
+    return -1;
+
+  for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+    if( blake2bp_init_leaf( S->S[i], outlen, 0, i ) < 0 ) return -1;
+
+  S->R->last_node = 1;
+  S->S[PARALLELISM_DEGREE - 1]->last_node = 1;
+  return 0;
+}
+
+int blake2bp_init_key( blake2bp_state *S, const uint8_t outlen, const void *key, const uint8_t keylen )
+{
+  if( !outlen || outlen > BLAKE2B_OUTBYTES ) return -1;
+
+  if( !key || !keylen || keylen > BLAKE2B_KEYBYTES ) return -1;
+
+  memset( S->buf, 0, sizeof( S->buf ) );
+  S->buflen = 0;
+
+  if( blake2bp_init_root( S->R, outlen, keylen ) < 0 )
+    return -1;
+
+  for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+    if( blake2bp_init_leaf( S->S[i], outlen, keylen, i ) < 0 ) return -1;
+
+  S->R->last_node = 1;
+  S->S[PARALLELISM_DEGREE - 1]->last_node = 1;
+  {
+    uint8_t block[BLAKE2B_BLOCKBYTES];
+    memset( block, 0, BLAKE2B_BLOCKBYTES );
+    memcpy( block, key, keylen );
+
+    for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+      blake2b_update( S->S[i], block, BLAKE2B_BLOCKBYTES );
+
+    secure_zero_memory( block, BLAKE2B_BLOCKBYTES ); /* Burn the key from stack */
+  }
+  return 0;
+}
+
+
+int blake2bp_update( blake2bp_state *S, const uint8_t *in, uint64_t inlen )
+{
+  size_t left = S->buflen;
+  size_t fill = sizeof( S->buf ) - left;
+
+  if( left && inlen >= fill )
+  {
+    memcpy( S->buf + left, in, fill );
+
+    for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+      blake2b_update( S->S[i], S->buf + i * BLAKE2B_BLOCKBYTES, BLAKE2B_BLOCKBYTES );
+
+    in += fill;
+    inlen -= fill;
+    left = 0;
+  }
+
+#if defined(_OPENMP)
+  #pragma omp parallel shared(S), num_threads(PARALLELISM_DEGREE)
+#else
+
+  for( size_t id__ = 0; id__ < PARALLELISM_DEGREE; ++id__ )
+#endif
+  {
+#if defined(_OPENMP)
+    size_t      id__ = omp_get_thread_num();
+#endif
+    uint64_t inlen__ = inlen;
+    const uint8_t *in__ = ( const uint8_t * )in;
+    in__ += id__ * BLAKE2B_BLOCKBYTES;
+
+    while( inlen__ >= PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES )
+    {
+      blake2b_update( S->S[id__], in__, BLAKE2B_BLOCKBYTES );
+      in__ += PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES;
+      inlen__ -= PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES;
+    }
+  }
+
+  in += inlen - inlen % ( PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES );
+  inlen %= PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES;
+
+  if( inlen > 0 )
+    memcpy( S->buf + left, in, inlen );
+
+  S->buflen = left + inlen;
+  return 0;
+}
+
+
+
+int blake2bp_final( blake2bp_state *S, uint8_t *out, const uint8_t outlen )
+{
+  uint8_t hash[PARALLELISM_DEGREE][BLAKE2B_OUTBYTES];
+
+  for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+  {
+    if( S->buflen > i * BLAKE2B_BLOCKBYTES )
+    {
+      size_t left = S->buflen - i * BLAKE2B_BLOCKBYTES;
+
+      if( left > BLAKE2B_BLOCKBYTES ) left = BLAKE2B_BLOCKBYTES;
+
+      blake2b_update( S->S[i], S->buf + i * BLAKE2B_BLOCKBYTES, left );
+    }
+
+    blake2b_final( S->S[i], hash[i], BLAKE2B_OUTBYTES );
+  }
+
+  for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+    blake2b_update( S->R, hash[i], BLAKE2B_OUTBYTES );
+
+  return blake2b_final( S->R, out, outlen );
+}
+
+int blake2bp( uint8_t *out, const void *in, const void *key, uint8_t outlen, uint64_t inlen, uint8_t keylen )
+{
+  uint8_t hash[PARALLELISM_DEGREE][BLAKE2B_OUTBYTES];
+  blake2b_state S[PARALLELISM_DEGREE][1];
+  blake2b_state FS[1];
+
+  /* Verify parameters */
+  if ( NULL == in && inlen > 0 ) return -1;
+
+  if ( NULL == out ) return -1;
+
+  if( NULL == key && keylen > 0 ) return -1;
+
+  if( !outlen || outlen > BLAKE2B_OUTBYTES ) return -1;
+
+  if( keylen > BLAKE2B_KEYBYTES ) return -1;
+
+  for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+    if( blake2bp_init_leaf( S[i], outlen, keylen, i ) < 0 ) return -1;
+
+  S[PARALLELISM_DEGREE - 1]->last_node = 1; // mark last node
+
+  if( keylen > 0 )
+  {
+    uint8_t block[BLAKE2B_BLOCKBYTES];
+    memset( block, 0, BLAKE2B_BLOCKBYTES );
+    memcpy( block, key, keylen );
+
+    for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+      blake2b_update( S[i], block, BLAKE2B_BLOCKBYTES );
+
+    secure_zero_memory( block, BLAKE2B_BLOCKBYTES ); /* Burn the key from stack */
+  }
+
+#if defined(_OPENMP)
+  #pragma omp parallel shared(S,hash), num_threads(PARALLELISM_DEGREE)
+#else
+
+  for( size_t id__ = 0; id__ < PARALLELISM_DEGREE; ++id__ )
+#endif
+  {
+#if defined(_OPENMP)
+    size_t      id__ = omp_get_thread_num();
+#endif
+    uint64_t inlen__ = inlen;
+    const uint8_t *in__ = ( const uint8_t * )in;
+    in__ += id__ * BLAKE2B_BLOCKBYTES;
+
+    while( inlen__ >= PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES )
+    {
+      blake2b_update( S[id__], in__, BLAKE2B_BLOCKBYTES );
+      in__ += PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES;
+      inlen__ -= PARALLELISM_DEGREE * BLAKE2B_BLOCKBYTES;
+    }
+
+    if( inlen__ > id__ * BLAKE2B_BLOCKBYTES )
+    {
+      const size_t left = inlen__ - id__ * BLAKE2B_BLOCKBYTES;
+      const size_t len = left <= BLAKE2B_BLOCKBYTES ? left : BLAKE2B_BLOCKBYTES;
+      blake2b_update( S[id__], in__, len );
+    }
+
+    blake2b_final( S[id__], hash[id__], BLAKE2B_OUTBYTES );
+  }
+
+  if( blake2bp_init_root( FS, outlen, keylen ) < 0 )
+    return -1;
+
+  FS->last_node = 1; // Mark as last node
+
+  for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+    blake2b_update( FS, hash[i], BLAKE2B_OUTBYTES );
+
+  return blake2b_final( FS, out, outlen );
+}
+
+
+#if defined(BLAKE2BP_SELFTEST)
+#include <string.h>
+#include "blake2-kat.h"
+int main( int argc, char **argv )
+{
+  uint8_t key[BLAKE2B_KEYBYTES];
+  uint8_t buf[KAT_LENGTH];
+
+  for( size_t i = 0; i < BLAKE2B_KEYBYTES; ++i )
+    key[i] = ( uint8_t )i;
+
+  for( size_t i = 0; i < KAT_LENGTH; ++i )
+    buf[i] = ( uint8_t )i;
+
+  for( size_t i = 0; i < KAT_LENGTH; ++i )
+  {
+    uint8_t hash[BLAKE2B_OUTBYTES];
+    //blake2bp( hash, buf, key, BLAKE2B_OUTBYTES, i, BLAKE2B_KEYBYTES );
+    blake2bp_state S[1];
+    blake2bp_init_key( S, BLAKE2B_OUTBYTES, key, BLAKE2B_KEYBYTES );
+    blake2bp_update( S, buf, i );
+    blake2bp_final( S, hash, BLAKE2B_OUTBYTES );
+
+    if( 0 != memcmp( hash, blake2bp_keyed_kat[i], BLAKE2B_OUTBYTES ) )
+    {
+      puts( "error" );
+      return -1;
+    }
+  }
+
+  puts( "ok" );
+  return 0;
+}
+#endif
+
diff --git a/cbits/blake2s.c b/cbits/blake2s.c
new file mode 100644
--- /dev/null
+++ b/cbits/blake2s.c
@@ -0,0 +1,422 @@
+/*
+   BLAKE2 reference source code package - optimized C implementations
+
+   Written in 2012 by Samuel Neves <sneves@dei.uc.pt>
+
+   To the extent possible under law, the author(s) have dedicated all copyright
+   and related and neighboring rights to this software to the public domain
+   worldwide. This software is distributed without any warranty.
+
+   You should have received a copy of the CC0 Public Domain Dedication along with
+   this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
+*/
+
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "blake2.h"
+#include "blake2-impl.h"
+
+#include "blake2-config.h"
+
+
+#include <emmintrin.h>
+#if defined(HAVE_SSSE3)
+#include <tmmintrin.h>
+#endif
+#if defined(HAVE_SSE41)
+#include <smmintrin.h>
+#endif
+#if defined(HAVE_AVX)
+#include <immintrin.h>
+#endif
+#if defined(HAVE_XOP)
+#include <x86intrin.h>
+#endif
+
+#include "blake2s-round.h"
+
+static const uint32_t blake2s_IV[8] =
+{
+  0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL,
+  0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL
+};
+
+static const uint8_t blake2s_sigma[10][16] =
+{
+  {  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15 } ,
+  { 14, 10,  4,  8,  9, 15, 13,  6,  1, 12,  0,  2, 11,  7,  5,  3 } ,
+  { 11,  8, 12,  0,  5,  2, 15, 13, 10, 14,  3,  6,  7,  1,  9,  4 } ,
+  {  7,  9,  3,  1, 13, 12, 11, 14,  2,  6,  5, 10,  4,  0, 15,  8 } ,
+  {  9,  0,  5,  7,  2,  4, 10, 15, 14,  1, 11, 12,  6,  8,  3, 13 } ,
+  {  2, 12,  6, 10,  0, 11,  8,  3,  4, 13,  7,  5, 15, 14,  1,  9 } ,
+  { 12,  5,  1, 15, 14, 13,  4, 10,  0,  7,  6,  3,  9,  2,  8, 11 } ,
+  { 13, 11,  7, 14, 12,  1,  3,  9,  5,  0, 15,  4,  8,  6,  2, 10 } ,
+  {  6, 15, 14,  9, 11,  3,  0,  8, 12,  2, 13,  7,  1,  4, 10,  5 } ,
+  { 10,  2,  8,  4,  7,  6,  1,  5, 15, 11,  9, 14,  3, 12, 13 , 0 } ,
+};
+
+
+/* Some helper functions, not necessarily useful */
+static inline int blake2s_set_lastnode( blake2s_state *S )
+{
+  S->f[1] = -1;
+  return 0;
+}
+
+static inline int blake2s_clear_lastnode( blake2s_state *S )
+{
+  S->f[1] = 0;
+  return 0;
+}
+
+static inline int blake2s_set_lastblock( blake2s_state *S )
+{
+  if( S->last_node ) blake2s_set_lastnode( S );
+
+  S->f[0] = -1;
+  return 0;
+}
+
+static inline int blake2s_clear_lastblock( blake2s_state *S )
+{
+  if( S->last_node ) blake2s_clear_lastnode( S );
+
+  S->f[0] = 0;
+  return 0;
+}
+
+static inline int blake2s_increment_counter( blake2s_state *S, const uint32_t inc )
+{
+  uint64_t t = ( ( uint64_t )S->t[1] << 32 ) | S->t[0];
+  t += inc;
+  S->t[0] = ( uint32_t )( t >>  0 );
+  S->t[1] = ( uint32_t )( t >> 32 );
+  return 0;
+}
+
+
+// Parameter-related functions
+static inline int blake2s_param_set_digest_length( blake2s_param *P, const uint8_t digest_length )
+{
+  P->digest_length = digest_length;
+  return 0;
+}
+
+static inline int blake2s_param_set_fanout( blake2s_param *P, const uint8_t fanout )
+{
+  P->fanout = fanout;
+  return 0;
+}
+
+static inline int blake2s_param_set_max_depth( blake2s_param *P, const uint8_t depth )
+{
+  P->depth = depth;
+  return 0;
+}
+
+static inline int blake2s_param_set_leaf_length( blake2s_param *P, const uint32_t leaf_length )
+{
+  P->leaf_length = leaf_length;
+  return 0;
+}
+
+static inline int blake2s_param_set_node_offset( blake2s_param *P, const uint64_t node_offset )
+{
+  store48( P->node_offset, node_offset );
+  return 0;
+}
+
+static inline int blake2s_param_set_node_depth( blake2s_param *P, const uint8_t node_depth )
+{
+  P->node_depth = node_depth;
+  return 0;
+}
+
+static inline int blake2s_param_set_inner_length( blake2s_param *P, const uint8_t inner_length )
+{
+  P->inner_length = inner_length;
+  return 0;
+}
+
+static inline int blake2s_param_set_salt( blake2s_param *P, const uint8_t salt[BLAKE2S_SALTBYTES] )
+{
+  memcpy( P->salt, salt, BLAKE2S_SALTBYTES );
+  return 0;
+}
+
+static inline int blake2s_param_set_personal( blake2s_param *P, const uint8_t personal[BLAKE2S_PERSONALBYTES] )
+{
+  memcpy( P->personal, personal, BLAKE2S_PERSONALBYTES );
+  return 0;
+}
+
+static inline int blake2s_init0( blake2s_state *S )
+{
+  memset( S, 0, sizeof( blake2s_state ) );
+
+  for( int i = 0; i < 8; ++i ) S->h[i] = blake2s_IV[i];
+
+  return 0;
+}
+
+/* init2 xors IV with input parameter block */
+int blake2s_init_param( blake2s_state *S, const blake2s_param *P )
+{
+  //blake2s_init0( S );
+  const uint8_t * v = ( const uint8_t * )( blake2s_IV );
+  const uint8_t * p = ( const uint8_t * )( P );
+  uint8_t * h = ( uint8_t * )( S->h );
+  /* IV XOR ParamBlock */
+  memset( S, 0, sizeof( blake2s_state ) );
+
+  for( int i = 0; i < BLAKE2S_OUTBYTES; ++i ) h[i] = v[i] ^ p[i];
+
+  return 0;
+}
+
+
+/* Some sort of default parameter block initialization, for sequential blake2s */
+int blake2s_init( blake2s_state *S, const uint8_t outlen )
+{
+  /* Move interval verification here? */
+  if ( ( !outlen ) || ( outlen > BLAKE2S_OUTBYTES ) ) return -1;
+
+  const blake2s_param P =
+  {
+    outlen,
+    0,
+    1,
+    1,
+    0,
+    {0},
+    0,
+    0,
+    {0},
+    {0}
+  };
+  return blake2s_init_param( S, &P );
+}
+
+
+int blake2s_init_key( blake2s_state *S, const uint8_t outlen, const void *key, const uint8_t keylen )
+{
+  /* Move interval verification here? */
+  if ( ( !outlen ) || ( outlen > BLAKE2S_OUTBYTES ) ) return -1;
+
+  if ( ( !key ) || ( !keylen ) || keylen > BLAKE2S_KEYBYTES ) return -1;
+
+  const blake2s_param P =
+  {
+    outlen,
+    keylen,
+    1,
+    1,
+    0,
+    {0},
+    0,
+    0,
+    {0},
+    {0}
+  };
+
+  if( blake2s_init_param( S, &P ) < 0 )
+    return -1;
+
+  {
+    uint8_t block[BLAKE2S_BLOCKBYTES];
+    memset( block, 0, BLAKE2S_BLOCKBYTES );
+    memcpy( block, key, keylen );
+    blake2s_update( S, block, BLAKE2S_BLOCKBYTES );
+    secure_zero_memory( block, BLAKE2S_BLOCKBYTES ); /* Burn the key from stack */
+  }
+  return 0;
+}
+
+
+static inline int blake2s_compress( blake2s_state *S, const uint8_t block[BLAKE2S_BLOCKBYTES] )
+{
+  __m128i row1, row2, row3, row4;
+  __m128i buf1, buf2, buf3, buf4;
+#if defined(HAVE_SSE41)
+  __m128i t0, t1;
+#if !defined(HAVE_XOP)
+  __m128i t2;
+#endif
+#endif
+  __m128i ff0, ff1;
+#if defined(HAVE_SSSE3) && !defined(HAVE_XOP)
+  const __m128i r8 = _mm_set_epi8( 12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1 );
+  const __m128i r16 = _mm_set_epi8( 13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2 );
+#endif
+#if defined(HAVE_SSE41)
+  const __m128i m0 = LOADU( block +  00 );
+  const __m128i m1 = LOADU( block +  16 );
+  const __m128i m2 = LOADU( block +  32 );
+  const __m128i m3 = LOADU( block +  48 );
+#else
+  const uint32_t  m0 = ( ( uint32_t * )block )[ 0];
+  const uint32_t  m1 = ( ( uint32_t * )block )[ 1];
+  const uint32_t  m2 = ( ( uint32_t * )block )[ 2];
+  const uint32_t  m3 = ( ( uint32_t * )block )[ 3];
+  const uint32_t  m4 = ( ( uint32_t * )block )[ 4];
+  const uint32_t  m5 = ( ( uint32_t * )block )[ 5];
+  const uint32_t  m6 = ( ( uint32_t * )block )[ 6];
+  const uint32_t  m7 = ( ( uint32_t * )block )[ 7];
+  const uint32_t  m8 = ( ( uint32_t * )block )[ 8];
+  const uint32_t  m9 = ( ( uint32_t * )block )[ 9];
+  const uint32_t m10 = ( ( uint32_t * )block )[10];
+  const uint32_t m11 = ( ( uint32_t * )block )[11];
+  const uint32_t m12 = ( ( uint32_t * )block )[12];
+  const uint32_t m13 = ( ( uint32_t * )block )[13];
+  const uint32_t m14 = ( ( uint32_t * )block )[14];
+  const uint32_t m15 = ( ( uint32_t * )block )[15];
+#endif
+  row1 = ff0 = LOADU( &S->h[0] );
+  row2 = ff1 = LOADU( &S->h[4] );
+  row3 = _mm_setr_epi32( 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A );
+  row4 = _mm_xor_si128( _mm_setr_epi32( 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 ), LOADU( &S->t[0] ) );
+  ROUND( 0 );
+  ROUND( 1 );
+  ROUND( 2 );
+  ROUND( 3 );
+  ROUND( 4 );
+  ROUND( 5 );
+  ROUND( 6 );
+  ROUND( 7 );
+  ROUND( 8 );
+  ROUND( 9 );
+  STOREU( &S->h[0], _mm_xor_si128( ff0, _mm_xor_si128( row1, row3 ) ) );
+  STOREU( &S->h[4], _mm_xor_si128( ff1, _mm_xor_si128( row2, row4 ) ) );
+  return 0;
+}
+
+/* inlen now in bytes */
+int blake2s_update( blake2s_state *S, const uint8_t *in, uint64_t inlen )
+{
+  while( inlen > 0 )
+  {
+    size_t left = S->buflen;
+    size_t fill = 2 * BLAKE2S_BLOCKBYTES - left;
+
+    if( inlen > fill )
+    {
+      memcpy( S->buf + left, in, fill ); // Fill buffer
+      S->buflen += fill;
+      blake2s_increment_counter( S, BLAKE2S_BLOCKBYTES );
+      blake2s_compress( S, S->buf ); // Compress
+      memcpy( S->buf, S->buf + BLAKE2S_BLOCKBYTES, BLAKE2S_BLOCKBYTES ); // Shift buffer left
+      S->buflen -= BLAKE2S_BLOCKBYTES;
+      in += fill;
+      inlen -= fill;
+    }
+    else // inlen <= fill
+    {
+      memcpy( S->buf + left, in, inlen );
+      S->buflen += inlen; // Be lazy, do not compress
+      in += inlen;
+      inlen -= inlen;
+    }
+  }
+
+  return 0;
+}
+
+/* Is this correct? */
+int blake2s_final( blake2s_state *S, uint8_t *out, uint8_t outlen )
+{
+  uint8_t buffer[BLAKE2S_OUTBYTES] = {0};
+
+  if( outlen > BLAKE2S_OUTBYTES )
+    return -1;
+
+  if( S->buflen > BLAKE2S_BLOCKBYTES )
+  {
+    blake2s_increment_counter( S, BLAKE2S_BLOCKBYTES );
+    blake2s_compress( S, S->buf );
+    S->buflen -= BLAKE2S_BLOCKBYTES;
+    memcpy( S->buf, S->buf + BLAKE2S_BLOCKBYTES, S->buflen );
+  }
+
+  blake2s_increment_counter( S, ( uint32_t )S->buflen );
+  blake2s_set_lastblock( S );
+  memset( S->buf + S->buflen, 0, 2 * BLAKE2S_BLOCKBYTES - S->buflen ); /* Padding */
+  blake2s_compress( S, S->buf );
+
+  for( int i = 0; i < 8; ++i ) /* Output full hash to temp buffer */
+    store32( buffer + sizeof( S->h[i] ) * i, S->h[i] );
+
+  memcpy( out, buffer, outlen );
+  return 0;
+}
+
+/* inlen, at least, should be uint64_t. Others can be size_t. */
+int blake2s( uint8_t *out, const void *in, const void *key, const uint8_t outlen, const uint64_t inlen, uint8_t keylen )
+{
+  blake2s_state S[1];
+
+  /* Verify parameters */
+  if ( NULL == in && inlen > 0 ) return -1;
+
+  if ( NULL == out ) return -1;
+
+  if ( NULL == key && keylen > 0) return -1;
+
+  if( !outlen || outlen > BLAKE2S_OUTBYTES ) return -1;
+
+  if( keylen > BLAKE2S_KEYBYTES ) return -1;
+
+  if( keylen > 0 )
+  {
+    if( blake2s_init_key( S, outlen, key, keylen ) < 0 ) return -1;
+  }
+  else
+  {
+    if( blake2s_init( S, outlen ) < 0 ) return -1;
+  }
+
+  blake2s_update( S, ( const uint8_t * )in, inlen );
+  blake2s_final( S, out, outlen );
+  return 0;
+}
+
+#if defined(SUPERCOP)
+int crypto_hash( unsigned char *out, unsigned char *in, unsigned long long inlen )
+{
+  return blake2s( out, in, NULL, BLAKE2S_OUTBYTES, inlen, 0 );
+}
+#endif
+
+#if defined(BLAKE2S_SELFTEST)
+#include <string.h>
+#include "blake2-kat.h"
+int main( int argc, char **argv )
+{
+  uint8_t key[BLAKE2S_KEYBYTES];
+  uint8_t buf[KAT_LENGTH];
+
+  for( size_t i = 0; i < BLAKE2S_KEYBYTES; ++i )
+    key[i] = ( uint8_t )i;
+
+  for( size_t i = 0; i < KAT_LENGTH; ++i )
+    buf[i] = ( uint8_t )i;
+
+  for( size_t i = 0; i < KAT_LENGTH; ++i )
+  {
+    uint8_t hash[BLAKE2S_OUTBYTES];
+
+    if( blake2s( hash, buf, key, BLAKE2S_OUTBYTES, i, BLAKE2S_KEYBYTES ) < 0 ||
+        0 != memcmp( hash, blake2s_keyed_kat[i], BLAKE2S_OUTBYTES ) )
+    {
+      puts( "error" );
+      return -1;
+    }
+  }
+
+  puts( "ok" );
+  return 0;
+}
+#endif
+
+
diff --git a/cbits/blake2sp.c b/cbits/blake2sp.c
new file mode 100644
--- /dev/null
+++ b/cbits/blake2sp.c
@@ -0,0 +1,293 @@
+/*
+   BLAKE2 reference source code package - optimized C implementations
+
+   Written in 2012 by Samuel Neves <sneves@dei.uc.pt>
+
+   To the extent possible under law, the author(s) have dedicated all copyright
+   and related and neighboring rights to this software to the public domain
+   worldwide. This software is distributed without any warranty.
+
+   You should have received a copy of the CC0 Public Domain Dedication along with
+   this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#if defined(_OPENMP)
+#include <omp.h>
+#endif
+
+#include "blake2.h"
+#include "blake2-impl.h"
+
+#define PARALLELISM_DEGREE 8
+
+static inline int blake2sp_init_leaf( blake2s_state *S, uint8_t outlen, uint8_t keylen, uint64_t offset )
+{
+  blake2s_param P[1];
+  P->digest_length = outlen;
+  P->key_length = keylen;
+  P->fanout = PARALLELISM_DEGREE;
+  P->depth = 2;
+  P->leaf_length = 0;
+  store48( P->node_offset, offset );
+  P->node_depth = 0;
+  P->inner_length = BLAKE2S_OUTBYTES;
+  memset( P->salt, 0, sizeof( P->salt ) );
+  memset( P->personal, 0, sizeof( P->personal ) );
+  return blake2s_init_param( S, P );
+}
+
+static inline int blake2sp_init_root( blake2s_state *S, uint8_t outlen, uint8_t keylen )
+{
+  blake2s_param P[1];
+  P->digest_length = outlen;
+  P->key_length = keylen;
+  P->fanout = PARALLELISM_DEGREE;
+  P->depth = 2;
+  P->leaf_length = 0;
+  store48( P->node_offset, 0ULL );
+  P->node_depth = 1;
+  P->inner_length = BLAKE2S_OUTBYTES;
+  memset( P->salt, 0, sizeof( P->salt ) );
+  memset( P->personal, 0, sizeof( P->personal ) );
+  return blake2s_init_param( S, P );
+}
+
+
+int blake2sp_init( blake2sp_state *S, const uint8_t outlen )
+{
+  if( !outlen || outlen > BLAKE2S_OUTBYTES ) return -1;
+
+  memset( S->buf, 0, sizeof( S->buf ) );
+  S->buflen = 0;
+
+  if( blake2sp_init_root( S->R, outlen, 0 ) < 0 )
+    return -1;
+
+  for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+    if( blake2sp_init_leaf( S->S[i], outlen, 0, i ) < 0 ) return -1;
+
+  S->R->last_node = 1;
+  S->S[PARALLELISM_DEGREE - 1]->last_node = 1;
+  return 0;
+}
+
+int blake2sp_init_key( blake2sp_state *S, const uint8_t outlen, const void *key, const uint8_t keylen )
+{
+  if( !outlen || outlen > BLAKE2S_OUTBYTES ) return -1;
+
+  if( !key || !keylen || keylen > BLAKE2S_KEYBYTES ) return -1;
+
+  memset( S->buf, 0, sizeof( S->buf ) );
+  S->buflen = 0;
+
+  if( blake2sp_init_root( S->R, outlen, keylen ) < 0 )
+    return -1;
+
+  for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+    if( blake2sp_init_leaf( S->S[i], outlen, keylen, i ) < 0 ) return -1;
+
+  S->R->last_node = 1;
+  S->S[PARALLELISM_DEGREE - 1]->last_node = 1;
+  {
+    uint8_t block[BLAKE2S_BLOCKBYTES];
+    memset( block, 0, BLAKE2S_BLOCKBYTES );
+    memcpy( block, key, keylen );
+
+    for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+      blake2s_update( S->S[i], block, BLAKE2S_BLOCKBYTES );
+
+    secure_zero_memory( block, BLAKE2S_BLOCKBYTES ); /* Burn the key from stack */
+  }
+  return 0;
+}
+
+
+int blake2sp_update( blake2sp_state *S, const uint8_t *in, uint64_t inlen )
+{
+  size_t left = S->buflen;
+  size_t fill = sizeof( S->buf ) - left;
+
+  if( left && inlen >= fill )
+  {
+    memcpy( S->buf + left, in, fill );
+
+    for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+      blake2s_update( S->S[i], S->buf + i * BLAKE2S_BLOCKBYTES, BLAKE2S_BLOCKBYTES );
+
+    in += fill;
+    inlen -= fill;
+    left = 0;
+  }
+
+#if defined(_OPENMP)
+  #pragma omp parallel shared(S), num_threads(PARALLELISM_DEGREE)
+#else
+
+  for( size_t id__ = 0; id__ < PARALLELISM_DEGREE; ++id__ )
+#endif
+  {
+#if defined(_OPENMP)
+    size_t      id__ = omp_get_thread_num();
+#endif
+    uint64_t inlen__ = inlen;
+    const uint8_t *in__ = ( const uint8_t * )in;
+    in__ += id__ * BLAKE2S_BLOCKBYTES;
+
+    while( inlen__ >= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES )
+    {
+      blake2s_update( S->S[id__], in__, BLAKE2S_BLOCKBYTES );
+      in__ += PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES;
+      inlen__ -= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES;
+    }
+  }
+
+  in += inlen - inlen % ( PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES );
+  inlen %= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES;
+
+  if( inlen > 0 )
+    memcpy( S->buf + left, in, inlen );
+
+  S->buflen = left + inlen;
+  return 0;
+}
+
+
+int blake2sp_final( blake2sp_state *S, uint8_t *out, const uint8_t outlen )
+{
+  uint8_t hash[PARALLELISM_DEGREE][BLAKE2S_OUTBYTES];
+
+  for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+  {
+    if( S->buflen > i * BLAKE2S_BLOCKBYTES )
+    {
+      size_t left = S->buflen - i * BLAKE2S_BLOCKBYTES;
+
+      if( left > BLAKE2S_BLOCKBYTES ) left = BLAKE2S_BLOCKBYTES;
+
+      blake2s_update( S->S[i], S->buf + i * BLAKE2S_BLOCKBYTES, left );
+    }
+
+    blake2s_final( S->S[i], hash[i], BLAKE2S_OUTBYTES );
+  }
+
+  for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+    blake2s_update( S->R, hash[i], BLAKE2S_OUTBYTES );
+
+  return blake2s_final( S->R, out, outlen );
+}
+
+
+int blake2sp( uint8_t *out, const void *in, const void *key, uint8_t outlen, uint64_t inlen, uint8_t keylen )
+{
+  uint8_t hash[PARALLELISM_DEGREE][BLAKE2S_OUTBYTES];
+  blake2s_state S[PARALLELISM_DEGREE][1];
+  blake2s_state FS[1];
+
+  /* Verify parameters */
+  if ( NULL == in && inlen > 0 ) return -1;
+
+  if ( NULL == out ) return -1;
+
+  if ( NULL == key && keylen > 0) return -1;
+
+  if( !outlen || outlen > BLAKE2S_OUTBYTES ) return -1;
+
+  if( keylen > BLAKE2S_KEYBYTES ) return -1;
+
+  for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+    if( blake2sp_init_leaf( S[i], outlen, keylen, i ) < 0 ) return -1;
+
+  S[PARALLELISM_DEGREE - 1]->last_node = 1; // mark last node
+
+  if( keylen > 0 )
+  {
+    uint8_t block[BLAKE2S_BLOCKBYTES];
+    memset( block, 0, BLAKE2S_BLOCKBYTES );
+    memcpy( block, key, keylen );
+
+    for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+      blake2s_update( S[i], block, BLAKE2S_BLOCKBYTES );
+
+    secure_zero_memory( block, BLAKE2S_BLOCKBYTES ); /* Burn the key from stack */
+  }
+
+#if defined(_OPENMP)
+  #pragma omp parallel shared(S,hash), num_threads(PARALLELISM_DEGREE)
+#else
+
+  for( size_t id__ = 0; id__ < PARALLELISM_DEGREE; ++id__ )
+#endif
+  {
+#if defined(_OPENMP)
+    size_t      id__ = omp_get_thread_num();
+#endif
+    uint64_t inlen__ = inlen;
+    const uint8_t *in__ = ( const uint8_t * )in;
+    in__ += id__ * BLAKE2S_BLOCKBYTES;
+
+    while( inlen__ >= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES )
+    {
+      blake2s_update( S[id__], in__, BLAKE2S_BLOCKBYTES );
+      in__ += PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES;
+      inlen__ -= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES;
+    }
+
+    if( inlen__ > id__ * BLAKE2S_BLOCKBYTES )
+    {
+      const size_t left = inlen__ - id__ * BLAKE2S_BLOCKBYTES;
+      const size_t len = left <= BLAKE2S_BLOCKBYTES ? left : BLAKE2S_BLOCKBYTES;
+      blake2s_update( S[id__], in__, len );
+    }
+
+    blake2s_final( S[id__], hash[id__], BLAKE2S_OUTBYTES );
+  }
+
+  if( blake2sp_init_root( FS, outlen, keylen ) < 0 )
+    return -1;
+
+  FS->last_node = 1;
+
+  for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+    blake2s_update( FS, hash[i], BLAKE2S_OUTBYTES );
+
+  return blake2s_final( FS, out, outlen );
+}
+
+#if defined(BLAKE2SP_SELFTEST)
+#include <string.h>
+#include "blake2-kat.h"
+int main( int argc, char **argv )
+{
+  uint8_t key[BLAKE2S_KEYBYTES];
+  uint8_t buf[KAT_LENGTH];
+
+  for( size_t i = 0; i < BLAKE2S_KEYBYTES; ++i )
+    key[i] = ( uint8_t )i;
+
+  for( size_t i = 0; i < KAT_LENGTH; ++i )
+    buf[i] = ( uint8_t )i;
+
+  for( size_t i = 0; i < KAT_LENGTH; ++i )
+  {
+    uint8_t hash[BLAKE2S_OUTBYTES];
+    blake2sp( hash, buf, key, BLAKE2S_OUTBYTES, i, BLAKE2S_KEYBYTES );
+
+    if( 0 != memcmp( hash, blake2sp_keyed_kat[i], BLAKE2S_OUTBYTES ) )
+    {
+      puts( "error" );
+      return -1;
+    }
+  }
+
+  puts( "ok" );
+  return 0;
+}
+#endif
+
+
+
+
diff --git a/changelog.md b/changelog.md
new file mode 100644
--- /dev/null
+++ b/changelog.md
@@ -0,0 +1,3 @@
+# 0.1.0
+
+* First version.
diff --git a/include/blake2-config.h b/include/blake2-config.h
new file mode 100644
--- /dev/null
+++ b/include/blake2-config.h
@@ -0,0 +1,72 @@
+/*
+   BLAKE2 reference source code package - optimized C implementations
+
+   Written in 2012 by Samuel Neves <sneves@dei.uc.pt>
+
+   To the extent possible under law, the author(s) have dedicated all copyright
+   and related and neighboring rights to this software to the public domain
+   worldwide. This software is distributed without any warranty.
+
+   You should have received a copy of the CC0 Public Domain Dedication along with
+   this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
+*/
+#pragma once
+#ifndef __BLAKE2_CONFIG_H__
+#define __BLAKE2_CONFIG_H__
+
+// These don't work everywhere
+#if defined(__SSE2__)
+#define HAVE_SSE2
+#endif
+
+#if defined(__SSSE3__)
+#define HAVE_SSSE3
+#endif
+
+#if defined(__SSE4_1__)
+#define HAVE_SSE41
+#endif
+
+#if defined(__AVX__)
+#define HAVE_AVX
+#endif
+
+#if defined(__XOP__)
+#define HAVE_XOP
+#endif
+
+
+#ifdef HAVE_AVX2
+#ifndef HAVE_AVX
+#define HAVE_AVX
+#endif
+#endif
+
+#ifdef HAVE_XOP
+#ifndef HAVE_AVX
+#define HAVE_AVX
+#endif
+#endif
+
+#ifdef HAVE_AVX
+#ifndef HAVE_SSE41
+#define HAVE_SSE41
+#endif
+#endif
+
+#ifdef HAVE_SSE41
+#ifndef HAVE_SSSE3
+#define HAVE_SSSE3
+#endif
+#endif
+
+#ifdef HAVE_SSSE3
+#define HAVE_SSE2
+#endif
+
+#if !defined(HAVE_SSE2)
+#error "This code requires at least SSE2."
+#endif
+
+#endif
+
diff --git a/include/blake2-impl.h b/include/blake2-impl.h
new file mode 100644
--- /dev/null
+++ b/include/blake2-impl.h
@@ -0,0 +1,136 @@
+/*
+   BLAKE2 reference source code package - optimized C implementations
+
+   Written in 2012 by Samuel Neves <sneves@dei.uc.pt>
+
+   To the extent possible under law, the author(s) have dedicated all copyright
+   and related and neighboring rights to this software to the public domain
+   worldwide. This software is distributed without any warranty.
+
+   You should have received a copy of the CC0 Public Domain Dedication along with
+   this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
+*/
+#pragma once
+#ifndef __BLAKE2_IMPL_H__
+#define __BLAKE2_IMPL_H__
+
+#include <stdint.h>
+
+static inline uint32_t load32( const void *src )
+{
+#if defined(NATIVE_LITTLE_ENDIAN)
+  uint32_t w;
+  memcpy(&w, src, sizeof w);
+  return w;
+#else
+  const uint8_t *p = ( const uint8_t * )src;
+  uint32_t w = *p++;
+  w |= ( uint32_t )( *p++ ) <<  8;
+  w |= ( uint32_t )( *p++ ) << 16;
+  w |= ( uint32_t )( *p++ ) << 24;
+  return w;
+#endif
+}
+
+static inline uint64_t load64( const void *src )
+{
+#if defined(NATIVE_LITTLE_ENDIAN)
+  uint64_t w;
+  memcpy(&w, src, sizeof w);
+  return w;
+#else
+  const uint8_t *p = ( const uint8_t * )src;
+  uint64_t w = *p++;
+  w |= ( uint64_t )( *p++ ) <<  8;
+  w |= ( uint64_t )( *p++ ) << 16;
+  w |= ( uint64_t )( *p++ ) << 24;
+  w |= ( uint64_t )( *p++ ) << 32;
+  w |= ( uint64_t )( *p++ ) << 40;
+  w |= ( uint64_t )( *p++ ) << 48;
+  w |= ( uint64_t )( *p++ ) << 56;
+  return w;
+#endif
+}
+
+static inline void store32( void *dst, uint32_t w )
+{
+#if defined(NATIVE_LITTLE_ENDIAN)
+  memcpy(dst, &w, sizeof w);
+#else
+  uint8_t *p = ( uint8_t * )dst;
+  *p++ = ( uint8_t )w; w >>= 8;
+  *p++ = ( uint8_t )w; w >>= 8;
+  *p++ = ( uint8_t )w; w >>= 8;
+  *p++ = ( uint8_t )w;
+#endif
+}
+
+static inline void store64( void *dst, uint64_t w )
+{
+#if defined(NATIVE_LITTLE_ENDIAN)
+  memcpy(dst, &w, sizeof w);
+#else
+  uint8_t *p = ( uint8_t * )dst;
+  *p++ = ( uint8_t )w; w >>= 8;
+  *p++ = ( uint8_t )w; w >>= 8;
+  *p++ = ( uint8_t )w; w >>= 8;
+  *p++ = ( uint8_t )w; w >>= 8;
+  *p++ = ( uint8_t )w; w >>= 8;
+  *p++ = ( uint8_t )w; w >>= 8;
+  *p++ = ( uint8_t )w; w >>= 8;
+  *p++ = ( uint8_t )w;
+#endif
+}
+
+static inline uint64_t load48( const void *src )
+{
+  const uint8_t *p = ( const uint8_t * )src;
+  uint64_t w = *p++;
+  w |= ( uint64_t )( *p++ ) <<  8;
+  w |= ( uint64_t )( *p++ ) << 16;
+  w |= ( uint64_t )( *p++ ) << 24;
+  w |= ( uint64_t )( *p++ ) << 32;
+  w |= ( uint64_t )( *p++ ) << 40;
+  return w;
+}
+
+static inline void store48( void *dst, uint64_t w )
+{
+  uint8_t *p = ( uint8_t * )dst;
+  *p++ = ( uint8_t )w; w >>= 8;
+  *p++ = ( uint8_t )w; w >>= 8;
+  *p++ = ( uint8_t )w; w >>= 8;
+  *p++ = ( uint8_t )w; w >>= 8;
+  *p++ = ( uint8_t )w; w >>= 8;
+  *p++ = ( uint8_t )w;
+}
+
+static inline uint32_t rotl32( const uint32_t w, const unsigned c )
+{
+  return ( w << c ) | ( w >> ( 32 - c ) );
+}
+
+static inline uint64_t rotl64( const uint64_t w, const unsigned c )
+{
+  return ( w << c ) | ( w >> ( 64 - c ) );
+}
+
+static inline uint32_t rotr32( const uint32_t w, const unsigned c )
+{
+  return ( w >> c ) | ( w << ( 32 - c ) );
+}
+
+static inline uint64_t rotr64( const uint64_t w, const unsigned c )
+{
+  return ( w >> c ) | ( w << ( 64 - c ) );
+}
+
+/* prevents compiler optimizing out memset() */
+static inline void secure_zero_memory( void *v, size_t n )
+{
+  volatile uint8_t *p = ( volatile uint8_t * )v;
+  while( n-- ) *p++ = 0;
+}
+
+#endif
+
diff --git a/include/blake2.h b/include/blake2.h
new file mode 100644
--- /dev/null
+++ b/include/blake2.h
@@ -0,0 +1,150 @@
+/*
+   BLAKE2 reference source code package - optimized C implementations
+
+   Written in 2012 by Samuel Neves <sneves@dei.uc.pt>
+
+   To the extent possible under law, the author(s) have dedicated all copyright
+   and related and neighboring rights to this software to the public domain
+   worldwide. This software is distributed without any warranty.
+
+   You should have received a copy of the CC0 Public Domain Dedication along with
+   this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
+*/
+#pragma once
+#ifndef __BLAKE2_H__
+#define __BLAKE2_H__
+
+#include <stddef.h>
+#include <stdint.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+  enum blake2s_constant
+  {
+    BLAKE2S_BLOCKBYTES = 64,
+    BLAKE2S_OUTBYTES   = 32,
+    BLAKE2S_KEYBYTES   = 32,
+    BLAKE2S_SALTBYTES  = 8,
+    BLAKE2S_PERSONALBYTES = 8
+  };
+
+  enum blake2b_constant
+  {
+    BLAKE2B_BLOCKBYTES = 128,
+    BLAKE2B_OUTBYTES   = 64,
+    BLAKE2B_KEYBYTES   = 64,
+    BLAKE2B_SALTBYTES  = 16,
+    BLAKE2B_PERSONALBYTES = 16
+  };
+
+#pragma pack(push, 1)
+  typedef struct __blake2s_param
+  {
+    uint8_t  digest_length; // 1
+    uint8_t  key_length;    // 2
+    uint8_t  fanout;        // 3
+    uint8_t  depth;         // 4
+    uint32_t leaf_length;   // 8
+    uint8_t  node_offset[6];// 14
+    uint8_t  node_depth;    // 15
+    uint8_t  inner_length;  // 16
+    // uint8_t  reserved[0];
+    uint8_t  salt[BLAKE2S_SALTBYTES]; // 24
+    uint8_t  personal[BLAKE2S_PERSONALBYTES];  // 32
+  } blake2s_param;
+
+  typedef struct __blake2s_state
+  {
+    uint32_t h[8];
+    uint32_t t[2];
+    uint32_t f[2];
+    uint8_t  buf[2 * BLAKE2S_BLOCKBYTES];
+    size_t   buflen;
+    uint8_t  last_node;
+  } blake2s_state;
+
+  typedef struct __blake2b_param
+  {
+    uint8_t  digest_length; // 1
+    uint8_t  key_length;    // 2
+    uint8_t  fanout;        // 3
+    uint8_t  depth;         // 4
+    uint32_t leaf_length;   // 8
+    uint64_t node_offset;   // 16
+    uint8_t  node_depth;    // 17
+    uint8_t  inner_length;  // 18
+    uint8_t  reserved[14];  // 32
+    uint8_t  salt[BLAKE2B_SALTBYTES]; // 48
+    uint8_t  personal[BLAKE2B_PERSONALBYTES];  // 64
+  } blake2b_param;
+
+  typedef struct __blake2b_state
+  {
+    uint64_t h[8];
+    uint64_t t[2];
+    uint64_t f[2];
+    uint8_t  buf[2 * BLAKE2B_BLOCKBYTES];
+    size_t   buflen;
+    uint8_t  last_node;
+  } blake2b_state;
+
+  typedef struct __blake2sp_state
+  {
+    blake2s_state S[8][1];
+    blake2s_state R[1];
+    uint8_t buf[8 * BLAKE2S_BLOCKBYTES];
+    size_t  buflen;
+  } blake2sp_state;
+
+  typedef struct __blake2bp_state
+  {
+    blake2b_state S[4][1];
+    blake2b_state R[1];
+    uint8_t buf[4 * BLAKE2B_BLOCKBYTES];
+    size_t  buflen;
+  } blake2bp_state;
+#pragma pack(pop)
+
+  // Streaming API
+  int blake2s_init( blake2s_state *S, const uint8_t outlen );
+  int blake2s_init_key( blake2s_state *S, const uint8_t outlen, const void *key, const uint8_t keylen );
+  int blake2s_init_param( blake2s_state *S, const blake2s_param *P );
+  int blake2s_update( blake2s_state *S, const uint8_t *in, uint64_t inlen );
+  int blake2s_final( blake2s_state *S, uint8_t *out, uint8_t outlen );
+
+  int blake2b_init( blake2b_state *S, const uint8_t outlen );
+  int blake2b_init_key( blake2b_state *S, const uint8_t outlen, const void *key, const uint8_t keylen );
+  int blake2b_init_param( blake2b_state *S, const blake2b_param *P );
+  int blake2b_update( blake2b_state *S, const uint8_t *in, uint64_t inlen );
+  int blake2b_final( blake2b_state *S, uint8_t *out, uint8_t outlen );
+
+  int blake2sp_init( blake2sp_state *S, const uint8_t outlen );
+  int blake2sp_init_key( blake2sp_state *S, const uint8_t outlen, const void *key, const uint8_t keylen );
+  int blake2sp_update( blake2sp_state *S, const uint8_t *in, uint64_t inlen );
+  int blake2sp_final( blake2sp_state *S, uint8_t *out, uint8_t outlen );
+
+  int blake2bp_init( blake2bp_state *S, const uint8_t outlen );
+  int blake2bp_init_key( blake2bp_state *S, const uint8_t outlen, const void *key, const uint8_t keylen );
+  int blake2bp_update( blake2bp_state *S, const uint8_t *in, uint64_t inlen );
+  int blake2bp_final( blake2bp_state *S, uint8_t *out, uint8_t outlen );
+
+  // Simple API
+  int blake2s( uint8_t *out, const void *in, const void *key, const uint8_t outlen, const uint64_t inlen, uint8_t keylen );
+  int blake2b( uint8_t *out, const void *in, const void *key, const uint8_t outlen, const uint64_t inlen, uint8_t keylen );
+
+  int blake2sp( uint8_t *out, const void *in, const void *key, const uint8_t outlen, const uint64_t inlen, uint8_t keylen );
+  int blake2bp( uint8_t *out, const void *in, const void *key, const uint8_t outlen, const uint64_t inlen, uint8_t keylen );
+
+  static inline int blake2( uint8_t *out, const void *in, const void *key, const uint8_t outlen, const uint64_t inlen, uint8_t keylen )
+  {
+    return blake2b( out, in, key, outlen, inlen, keylen );
+  }
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
+
diff --git a/include/blake2b-load-sse2.h b/include/blake2b-load-sse2.h
new file mode 100644
--- /dev/null
+++ b/include/blake2b-load-sse2.h
@@ -0,0 +1,68 @@
+/*
+   BLAKE2 reference source code package - optimized C implementations
+
+   Written in 2012 by Samuel Neves <sneves@dei.uc.pt>
+
+   To the extent possible under law, the author(s) have dedicated all copyright
+   and related and neighboring rights to this software to the public domain
+   worldwide. This software is distributed without any warranty.
+
+   You should have received a copy of the CC0 Public Domain Dedication along with
+   this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
+*/
+#pragma once
+#ifndef __BLAKE2B_LOAD_SSE2_H__
+#define __BLAKE2B_LOAD_SSE2_H__
+
+#define LOAD_MSG_0_1(b0, b1) b0 = _mm_set_epi64x(m2, m0); b1 = _mm_set_epi64x(m6, m4)
+#define LOAD_MSG_0_2(b0, b1) b0 = _mm_set_epi64x(m3, m1); b1 = _mm_set_epi64x(m7, m5)
+#define LOAD_MSG_0_3(b0, b1) b0 = _mm_set_epi64x(m10, m8); b1 = _mm_set_epi64x(m14, m12)
+#define LOAD_MSG_0_4(b0, b1) b0 = _mm_set_epi64x(m11, m9); b1 = _mm_set_epi64x(m15, m13)
+#define LOAD_MSG_1_1(b0, b1) b0 = _mm_set_epi64x(m4, m14); b1 = _mm_set_epi64x(m13, m9)
+#define LOAD_MSG_1_2(b0, b1) b0 = _mm_set_epi64x(m8, m10); b1 = _mm_set_epi64x(m6, m15)
+#define LOAD_MSG_1_3(b0, b1) b0 = _mm_set_epi64x(m0, m1); b1 = _mm_set_epi64x(m5, m11)
+#define LOAD_MSG_1_4(b0, b1) b0 = _mm_set_epi64x(m2, m12); b1 = _mm_set_epi64x(m3, m7)
+#define LOAD_MSG_2_1(b0, b1) b0 = _mm_set_epi64x(m12, m11); b1 = _mm_set_epi64x(m15, m5)
+#define LOAD_MSG_2_2(b0, b1) b0 = _mm_set_epi64x(m0, m8); b1 = _mm_set_epi64x(m13, m2)
+#define LOAD_MSG_2_3(b0, b1) b0 = _mm_set_epi64x(m3, m10); b1 = _mm_set_epi64x(m9, m7)
+#define LOAD_MSG_2_4(b0, b1) b0 = _mm_set_epi64x(m6, m14); b1 = _mm_set_epi64x(m4, m1)
+#define LOAD_MSG_3_1(b0, b1) b0 = _mm_set_epi64x(m3, m7); b1 = _mm_set_epi64x(m11, m13)
+#define LOAD_MSG_3_2(b0, b1) b0 = _mm_set_epi64x(m1, m9); b1 = _mm_set_epi64x(m14, m12)
+#define LOAD_MSG_3_3(b0, b1) b0 = _mm_set_epi64x(m5, m2); b1 = _mm_set_epi64x(m15, m4)
+#define LOAD_MSG_3_4(b0, b1) b0 = _mm_set_epi64x(m10, m6); b1 = _mm_set_epi64x(m8, m0)
+#define LOAD_MSG_4_1(b0, b1) b0 = _mm_set_epi64x(m5, m9); b1 = _mm_set_epi64x(m10, m2)
+#define LOAD_MSG_4_2(b0, b1) b0 = _mm_set_epi64x(m7, m0); b1 = _mm_set_epi64x(m15, m4)
+#define LOAD_MSG_4_3(b0, b1) b0 = _mm_set_epi64x(m11, m14); b1 = _mm_set_epi64x(m3, m6)
+#define LOAD_MSG_4_4(b0, b1) b0 = _mm_set_epi64x(m12, m1); b1 = _mm_set_epi64x(m13, m8)
+#define LOAD_MSG_5_1(b0, b1) b0 = _mm_set_epi64x(m6, m2); b1 = _mm_set_epi64x(m8, m0)
+#define LOAD_MSG_5_2(b0, b1) b0 = _mm_set_epi64x(m10, m12); b1 = _mm_set_epi64x(m3, m11)
+#define LOAD_MSG_5_3(b0, b1) b0 = _mm_set_epi64x(m7, m4); b1 = _mm_set_epi64x(m1, m15)
+#define LOAD_MSG_5_4(b0, b1) b0 = _mm_set_epi64x(m5, m13); b1 = _mm_set_epi64x(m9, m14)
+#define LOAD_MSG_6_1(b0, b1) b0 = _mm_set_epi64x(m1, m12); b1 = _mm_set_epi64x(m4, m14)
+#define LOAD_MSG_6_2(b0, b1) b0 = _mm_set_epi64x(m15, m5); b1 = _mm_set_epi64x(m10, m13)
+#define LOAD_MSG_6_3(b0, b1) b0 = _mm_set_epi64x(m6, m0); b1 = _mm_set_epi64x(m8, m9)
+#define LOAD_MSG_6_4(b0, b1) b0 = _mm_set_epi64x(m3, m7); b1 = _mm_set_epi64x(m11, m2)
+#define LOAD_MSG_7_1(b0, b1) b0 = _mm_set_epi64x(m7, m13); b1 = _mm_set_epi64x(m3, m12)
+#define LOAD_MSG_7_2(b0, b1) b0 = _mm_set_epi64x(m14, m11); b1 = _mm_set_epi64x(m9, m1)
+#define LOAD_MSG_7_3(b0, b1) b0 = _mm_set_epi64x(m15, m5); b1 = _mm_set_epi64x(m2, m8)
+#define LOAD_MSG_7_4(b0, b1) b0 = _mm_set_epi64x(m4, m0); b1 = _mm_set_epi64x(m10, m6)
+#define LOAD_MSG_8_1(b0, b1) b0 = _mm_set_epi64x(m14, m6); b1 = _mm_set_epi64x(m0, m11)
+#define LOAD_MSG_8_2(b0, b1) b0 = _mm_set_epi64x(m9, m15); b1 = _mm_set_epi64x(m8, m3)
+#define LOAD_MSG_8_3(b0, b1) b0 = _mm_set_epi64x(m13, m12); b1 = _mm_set_epi64x(m10, m1)
+#define LOAD_MSG_8_4(b0, b1) b0 = _mm_set_epi64x(m7, m2); b1 = _mm_set_epi64x(m5, m4)
+#define LOAD_MSG_9_1(b0, b1) b0 = _mm_set_epi64x(m8, m10); b1 = _mm_set_epi64x(m1, m7)
+#define LOAD_MSG_9_2(b0, b1) b0 = _mm_set_epi64x(m4, m2); b1 = _mm_set_epi64x(m5, m6)
+#define LOAD_MSG_9_3(b0, b1) b0 = _mm_set_epi64x(m9, m15); b1 = _mm_set_epi64x(m13, m3)
+#define LOAD_MSG_9_4(b0, b1) b0 = _mm_set_epi64x(m14, m11); b1 = _mm_set_epi64x(m0, m12)
+#define LOAD_MSG_10_1(b0, b1) b0 = _mm_set_epi64x(m2, m0); b1 = _mm_set_epi64x(m6, m4)
+#define LOAD_MSG_10_2(b0, b1) b0 = _mm_set_epi64x(m3, m1); b1 = _mm_set_epi64x(m7, m5)
+#define LOAD_MSG_10_3(b0, b1) b0 = _mm_set_epi64x(m10, m8); b1 = _mm_set_epi64x(m14, m12)
+#define LOAD_MSG_10_4(b0, b1) b0 = _mm_set_epi64x(m11, m9); b1 = _mm_set_epi64x(m15, m13)
+#define LOAD_MSG_11_1(b0, b1) b0 = _mm_set_epi64x(m4, m14); b1 = _mm_set_epi64x(m13, m9)
+#define LOAD_MSG_11_2(b0, b1) b0 = _mm_set_epi64x(m8, m10); b1 = _mm_set_epi64x(m6, m15)
+#define LOAD_MSG_11_3(b0, b1) b0 = _mm_set_epi64x(m0, m1); b1 = _mm_set_epi64x(m5, m11)
+#define LOAD_MSG_11_4(b0, b1) b0 = _mm_set_epi64x(m2, m12); b1 = _mm_set_epi64x(m3, m7)
+
+
+#endif
+
diff --git a/include/blake2b-load-sse41.h b/include/blake2b-load-sse41.h
new file mode 100644
--- /dev/null
+++ b/include/blake2b-load-sse41.h
@@ -0,0 +1,402 @@
+/*
+   BLAKE2 reference source code package - optimized C implementations
+
+   Written in 2012 by Samuel Neves <sneves@dei.uc.pt>
+
+   To the extent possible under law, the author(s) have dedicated all copyright
+   and related and neighboring rights to this software to the public domain
+   worldwide. This software is distributed without any warranty.
+
+   You should have received a copy of the CC0 Public Domain Dedication along with
+   this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
+*/
+#pragma once
+#ifndef __BLAKE2B_LOAD_SSE41_H__
+#define __BLAKE2B_LOAD_SSE41_H__
+
+#define LOAD_MSG_0_1(b0, b1) \
+do \
+{ \
+b0 = _mm_unpacklo_epi64(m0, m1); \
+b1 = _mm_unpacklo_epi64(m2, m3); \
+} while(0)
+
+
+#define LOAD_MSG_0_2(b0, b1) \
+do \
+{ \
+b0 = _mm_unpackhi_epi64(m0, m1); \
+b1 = _mm_unpackhi_epi64(m2, m3); \
+} while(0)
+
+
+#define LOAD_MSG_0_3(b0, b1) \
+do \
+{ \
+b0 = _mm_unpacklo_epi64(m4, m5); \
+b1 = _mm_unpacklo_epi64(m6, m7); \
+} while(0)
+
+
+#define LOAD_MSG_0_4(b0, b1) \
+do \
+{ \
+b0 = _mm_unpackhi_epi64(m4, m5); \
+b1 = _mm_unpackhi_epi64(m6, m7); \
+} while(0)
+
+
+#define LOAD_MSG_1_1(b0, b1) \
+do \
+{ \
+b0 = _mm_unpacklo_epi64(m7, m2); \
+b1 = _mm_unpackhi_epi64(m4, m6); \
+} while(0)
+
+
+#define LOAD_MSG_1_2(b0, b1) \
+do \
+{ \
+b0 = _mm_unpacklo_epi64(m5, m4); \
+b1 = _mm_alignr_epi8(m3, m7, 8); \
+} while(0)
+
+
+#define LOAD_MSG_1_3(b0, b1) \
+do \
+{ \
+b0 = _mm_shuffle_epi32(m0, _MM_SHUFFLE(1,0,3,2)); \
+b1 = _mm_unpackhi_epi64(m5, m2); \
+} while(0)
+
+
+#define LOAD_MSG_1_4(b0, b1) \
+do \
+{ \
+b0 = _mm_unpacklo_epi64(m6, m1); \
+b1 = _mm_unpackhi_epi64(m3, m1); \
+} while(0)
+
+
+#define LOAD_MSG_2_1(b0, b1) \
+do \
+{ \
+b0 = _mm_alignr_epi8(m6, m5, 8); \
+b1 = _mm_unpackhi_epi64(m2, m7); \
+} while(0)
+
+
+#define LOAD_MSG_2_2(b0, b1) \
+do \
+{ \
+b0 = _mm_unpacklo_epi64(m4, m0); \
+b1 = _mm_blend_epi16(m1, m6, 0xF0); \
+} while(0)
+
+
+#define LOAD_MSG_2_3(b0, b1) \
+do \
+{ \
+b0 = _mm_blend_epi16(m5, m1, 0xF0); \
+b1 = _mm_unpackhi_epi64(m3, m4); \
+} while(0)
+
+
+#define LOAD_MSG_2_4(b0, b1) \
+do \
+{ \
+b0 = _mm_unpacklo_epi64(m7, m3); \
+b1 = _mm_alignr_epi8(m2, m0, 8); \
+} while(0)
+
+
+#define LOAD_MSG_3_1(b0, b1) \
+do \
+{ \
+b0 = _mm_unpackhi_epi64(m3, m1); \
+b1 = _mm_unpackhi_epi64(m6, m5); \
+} while(0)
+
+
+#define LOAD_MSG_3_2(b0, b1) \
+do \
+{ \
+b0 = _mm_unpackhi_epi64(m4, m0); \
+b1 = _mm_unpacklo_epi64(m6, m7); \
+} while(0)
+
+
+#define LOAD_MSG_3_3(b0, b1) \
+do \
+{ \
+b0 = _mm_blend_epi16(m1, m2, 0xF0); \
+b1 = _mm_blend_epi16(m2, m7, 0xF0); \
+} while(0)
+
+
+#define LOAD_MSG_3_4(b0, b1) \
+do \
+{ \
+b0 = _mm_unpacklo_epi64(m3, m5); \
+b1 = _mm_unpacklo_epi64(m0, m4); \
+} while(0)
+
+
+#define LOAD_MSG_4_1(b0, b1) \
+do \
+{ \
+b0 = _mm_unpackhi_epi64(m4, m2); \
+b1 = _mm_unpacklo_epi64(m1, m5); \
+} while(0)
+
+
+#define LOAD_MSG_4_2(b0, b1) \
+do \
+{ \
+b0 = _mm_blend_epi16(m0, m3, 0xF0); \
+b1 = _mm_blend_epi16(m2, m7, 0xF0); \
+} while(0)
+
+
+#define LOAD_MSG_4_3(b0, b1) \
+do \
+{ \
+b0 = _mm_blend_epi16(m7, m5, 0xF0); \
+b1 = _mm_blend_epi16(m3, m1, 0xF0); \
+} while(0)
+
+
+#define LOAD_MSG_4_4(b0, b1) \
+do \
+{ \
+b0 = _mm_alignr_epi8(m6, m0, 8); \
+b1 = _mm_blend_epi16(m4, m6, 0xF0); \
+} while(0)
+
+
+#define LOAD_MSG_5_1(b0, b1) \
+do \
+{ \
+b0 = _mm_unpacklo_epi64(m1, m3); \
+b1 = _mm_unpacklo_epi64(m0, m4); \
+} while(0)
+
+
+#define LOAD_MSG_5_2(b0, b1) \
+do \
+{ \
+b0 = _mm_unpacklo_epi64(m6, m5); \
+b1 = _mm_unpackhi_epi64(m5, m1); \
+} while(0)
+
+
+#define LOAD_MSG_5_3(b0, b1) \
+do \
+{ \
+b0 = _mm_blend_epi16(m2, m3, 0xF0); \
+b1 = _mm_unpackhi_epi64(m7, m0); \
+} while(0)
+
+
+#define LOAD_MSG_5_4(b0, b1) \
+do \
+{ \
+b0 = _mm_unpackhi_epi64(m6, m2); \
+b1 = _mm_blend_epi16(m7, m4, 0xF0); \
+} while(0)
+
+
+#define LOAD_MSG_6_1(b0, b1) \
+do \
+{ \
+b0 = _mm_blend_epi16(m6, m0, 0xF0); \
+b1 = _mm_unpacklo_epi64(m7, m2); \
+} while(0)
+
+
+#define LOAD_MSG_6_2(b0, b1) \
+do \
+{ \
+b0 = _mm_unpackhi_epi64(m2, m7); \
+b1 = _mm_alignr_epi8(m5, m6, 8); \
+} while(0)
+
+
+#define LOAD_MSG_6_3(b0, b1) \
+do \
+{ \
+b0 = _mm_unpacklo_epi64(m0, m3); \
+b1 = _mm_shuffle_epi32(m4, _MM_SHUFFLE(1,0,3,2)); \
+} while(0)
+
+
+#define LOAD_MSG_6_4(b0, b1) \
+do \
+{ \
+b0 = _mm_unpackhi_epi64(m3, m1); \
+b1 = _mm_blend_epi16(m1, m5, 0xF0); \
+} while(0)
+
+
+#define LOAD_MSG_7_1(b0, b1) \
+do \
+{ \
+b0 = _mm_unpackhi_epi64(m6, m3); \
+b1 = _mm_blend_epi16(m6, m1, 0xF0); \
+} while(0)
+
+
+#define LOAD_MSG_7_2(b0, b1) \
+do \
+{ \
+b0 = _mm_alignr_epi8(m7, m5, 8); \
+b1 = _mm_unpackhi_epi64(m0, m4); \
+} while(0)
+
+
+#define LOAD_MSG_7_3(b0, b1) \
+do \
+{ \
+b0 = _mm_unpackhi_epi64(m2, m7); \
+b1 = _mm_unpacklo_epi64(m4, m1); \
+} while(0)
+
+
+#define LOAD_MSG_7_4(b0, b1) \
+do \
+{ \
+b0 = _mm_unpacklo_epi64(m0, m2); \
+b1 = _mm_unpacklo_epi64(m3, m5); \
+} while(0)
+
+
+#define LOAD_MSG_8_1(b0, b1) \
+do \
+{ \
+b0 = _mm_unpacklo_epi64(m3, m7); \
+b1 = _mm_alignr_epi8(m0, m5, 8); \
+} while(0)
+
+
+#define LOAD_MSG_8_2(b0, b1) \
+do \
+{ \
+b0 = _mm_unpackhi_epi64(m7, m4); \
+b1 = _mm_alignr_epi8(m4, m1, 8); \
+} while(0)
+
+
+#define LOAD_MSG_8_3(b0, b1) \
+do \
+{ \
+b0 = m6; \
+b1 = _mm_alignr_epi8(m5, m0, 8); \
+} while(0)
+
+
+#define LOAD_MSG_8_4(b0, b1) \
+do \
+{ \
+b0 = _mm_blend_epi16(m1, m3, 0xF0); \
+b1 = m2; \
+} while(0)
+
+
+#define LOAD_MSG_9_1(b0, b1) \
+do \
+{ \
+b0 = _mm_unpacklo_epi64(m5, m4); \
+b1 = _mm_unpackhi_epi64(m3, m0); \
+} while(0)
+
+
+#define LOAD_MSG_9_2(b0, b1) \
+do \
+{ \
+b0 = _mm_unpacklo_epi64(m1, m2); \
+b1 = _mm_blend_epi16(m3, m2, 0xF0); \
+} while(0)
+
+
+#define LOAD_MSG_9_3(b0, b1) \
+do \
+{ \
+b0 = _mm_unpackhi_epi64(m7, m4); \
+b1 = _mm_unpackhi_epi64(m1, m6); \
+} while(0)
+
+
+#define LOAD_MSG_9_4(b0, b1) \
+do \
+{ \
+b0 = _mm_alignr_epi8(m7, m5, 8); \
+b1 = _mm_unpacklo_epi64(m6, m0); \
+} while(0)
+
+
+#define LOAD_MSG_10_1(b0, b1) \
+do \
+{ \
+b0 = _mm_unpacklo_epi64(m0, m1); \
+b1 = _mm_unpacklo_epi64(m2, m3); \
+} while(0)
+
+
+#define LOAD_MSG_10_2(b0, b1) \
+do \
+{ \
+b0 = _mm_unpackhi_epi64(m0, m1); \
+b1 = _mm_unpackhi_epi64(m2, m3); \
+} while(0)
+
+
+#define LOAD_MSG_10_3(b0, b1) \
+do \
+{ \
+b0 = _mm_unpacklo_epi64(m4, m5); \
+b1 = _mm_unpacklo_epi64(m6, m7); \
+} while(0)
+
+
+#define LOAD_MSG_10_4(b0, b1) \
+do \
+{ \
+b0 = _mm_unpackhi_epi64(m4, m5); \
+b1 = _mm_unpackhi_epi64(m6, m7); \
+} while(0)
+
+
+#define LOAD_MSG_11_1(b0, b1) \
+do \
+{ \
+b0 = _mm_unpacklo_epi64(m7, m2); \
+b1 = _mm_unpackhi_epi64(m4, m6); \
+} while(0)
+
+
+#define LOAD_MSG_11_2(b0, b1) \
+do \
+{ \
+b0 = _mm_unpacklo_epi64(m5, m4); \
+b1 = _mm_alignr_epi8(m3, m7, 8); \
+} while(0)
+
+
+#define LOAD_MSG_11_3(b0, b1) \
+do \
+{ \
+b0 = _mm_shuffle_epi32(m0, _MM_SHUFFLE(1,0,3,2)); \
+b1 = _mm_unpackhi_epi64(m5, m2); \
+} while(0)
+
+
+#define LOAD_MSG_11_4(b0, b1) \
+do \
+{ \
+b0 = _mm_unpacklo_epi64(m6, m1); \
+b1 = _mm_unpackhi_epi64(m3, m1); \
+} while(0)
+
+
+#endif
+
diff --git a/include/blake2b-round.h b/include/blake2b-round.h
new file mode 100644
--- /dev/null
+++ b/include/blake2b-round.h
@@ -0,0 +1,157 @@
+/*
+   BLAKE2 reference source code package - optimized C implementations
+
+   Written in 2012 by Samuel Neves <sneves@dei.uc.pt>
+
+   To the extent possible under law, the author(s) have dedicated all copyright
+   and related and neighboring rights to this software to the public domain
+   worldwide. This software is distributed without any warranty.
+
+   You should have received a copy of the CC0 Public Domain Dedication along with
+   this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
+*/
+#pragma once
+#ifndef __BLAKE2B_ROUND_H__
+#define __BLAKE2B_ROUND_H__
+
+#define LOADU(p)  _mm_loadu_si128( (const __m128i *)(p) )
+#define STOREU(p,r) _mm_storeu_si128((__m128i *)(p), r)
+
+#define TOF(reg) _mm_castsi128_ps((reg))
+#define TOI(reg) _mm_castps_si128((reg))
+
+#define LIKELY(x) __builtin_expect((x),1)
+
+
+/* Microarchitecture-specific macros */
+#ifndef HAVE_XOP
+#ifdef HAVE_SSSE3
+#define _mm_roti_epi64(x, c) \
+    (-(c) == 32) ? _mm_shuffle_epi32((x), _MM_SHUFFLE(2,3,0,1))  \
+    : (-(c) == 24) ? _mm_shuffle_epi8((x), r24) \
+    : (-(c) == 16) ? _mm_shuffle_epi8((x), r16) \
+    : (-(c) == 63) ? _mm_xor_si128(_mm_srli_epi64((x), -(c)), _mm_add_epi64((x), (x)))  \
+    : _mm_xor_si128(_mm_srli_epi64((x), -(c)), _mm_slli_epi64((x), 64-(-(c))))
+#else
+#define _mm_roti_epi64(r, c) _mm_xor_si128(_mm_srli_epi64( (r), -(c) ),_mm_slli_epi64( (r), 64-(-(c)) ))
+#endif
+#else
+/* ... */
+#endif
+
+
+
+#define G1(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h,b0,b1) \
+  row1l = _mm_add_epi64(_mm_add_epi64(row1l, b0), row2l); \
+  row1h = _mm_add_epi64(_mm_add_epi64(row1h, b1), row2h); \
+  \
+  row4l = _mm_xor_si128(row4l, row1l); \
+  row4h = _mm_xor_si128(row4h, row1h); \
+  \
+  row4l = _mm_roti_epi64(row4l, -32); \
+  row4h = _mm_roti_epi64(row4h, -32); \
+  \
+  row3l = _mm_add_epi64(row3l, row4l); \
+  row3h = _mm_add_epi64(row3h, row4h); \
+  \
+  row2l = _mm_xor_si128(row2l, row3l); \
+  row2h = _mm_xor_si128(row2h, row3h); \
+  \
+  row2l = _mm_roti_epi64(row2l, -24); \
+  row2h = _mm_roti_epi64(row2h, -24); \
+ 
+#define G2(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h,b0,b1) \
+  row1l = _mm_add_epi64(_mm_add_epi64(row1l, b0), row2l); \
+  row1h = _mm_add_epi64(_mm_add_epi64(row1h, b1), row2h); \
+  \
+  row4l = _mm_xor_si128(row4l, row1l); \
+  row4h = _mm_xor_si128(row4h, row1h); \
+  \
+  row4l = _mm_roti_epi64(row4l, -16); \
+  row4h = _mm_roti_epi64(row4h, -16); \
+  \
+  row3l = _mm_add_epi64(row3l, row4l); \
+  row3h = _mm_add_epi64(row3h, row4h); \
+  \
+  row2l = _mm_xor_si128(row2l, row3l); \
+  row2h = _mm_xor_si128(row2h, row3h); \
+  \
+  row2l = _mm_roti_epi64(row2l, -63); \
+  row2h = _mm_roti_epi64(row2h, -63); \
+ 
+#if defined(HAVE_SSSE3)
+#define DIAGONALIZE(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h) \
+  t0 = _mm_alignr_epi8(row2h, row2l, 8); \
+  t1 = _mm_alignr_epi8(row2l, row2h, 8); \
+  row2l = t0; \
+  row2h = t1; \
+  \
+  t0 = row3l; \
+  row3l = row3h; \
+  row3h = t0;    \
+  \
+  t0 = _mm_alignr_epi8(row4h, row4l, 8); \
+  t1 = _mm_alignr_epi8(row4l, row4h, 8); \
+  row4l = t1; \
+  row4h = t0;
+
+#define UNDIAGONALIZE(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h) \
+  t0 = _mm_alignr_epi8(row2l, row2h, 8); \
+  t1 = _mm_alignr_epi8(row2h, row2l, 8); \
+  row2l = t0; \
+  row2h = t1; \
+  \
+  t0 = row3l; \
+  row3l = row3h; \
+  row3h = t0; \
+  \
+  t0 = _mm_alignr_epi8(row4l, row4h, 8); \
+  t1 = _mm_alignr_epi8(row4h, row4l, 8); \
+  row4l = t1; \
+  row4h = t0;
+#else
+
+#define DIAGONALIZE(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h) \
+  t0 = row4l;\
+  t1 = row2l;\
+  row4l = row3l;\
+  row3l = row3h;\
+  row3h = row4l;\
+  row4l = _mm_unpackhi_epi64(row4h, _mm_unpacklo_epi64(t0, t0)); \
+  row4h = _mm_unpackhi_epi64(t0, _mm_unpacklo_epi64(row4h, row4h)); \
+  row2l = _mm_unpackhi_epi64(row2l, _mm_unpacklo_epi64(row2h, row2h)); \
+  row2h = _mm_unpackhi_epi64(row2h, _mm_unpacklo_epi64(t1, t1))
+
+#define UNDIAGONALIZE(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h) \
+  t0 = row3l;\
+  row3l = row3h;\
+  row3h = t0;\
+  t0 = row2l;\
+  t1 = row4l;\
+  row2l = _mm_unpackhi_epi64(row2h, _mm_unpacklo_epi64(row2l, row2l)); \
+  row2h = _mm_unpackhi_epi64(t0, _mm_unpacklo_epi64(row2h, row2h)); \
+  row4l = _mm_unpackhi_epi64(row4l, _mm_unpacklo_epi64(row4h, row4h)); \
+  row4h = _mm_unpackhi_epi64(row4h, _mm_unpacklo_epi64(t1, t1))
+
+#endif
+
+#if defined(HAVE_SSE41)
+#include "blake2b-load-sse41.h"
+#else
+#include "blake2b-load-sse2.h"
+#endif
+
+#define ROUND(r) \
+  LOAD_MSG_ ##r ##_1(b0, b1); \
+  G1(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h,b0,b1); \
+  LOAD_MSG_ ##r ##_2(b0, b1); \
+  G2(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h,b0,b1); \
+  DIAGONALIZE(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h); \
+  LOAD_MSG_ ##r ##_3(b0, b1); \
+  G1(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h,b0,b1); \
+  LOAD_MSG_ ##r ##_4(b0, b1); \
+  G2(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h,b0,b1); \
+  UNDIAGONALIZE(row1l,row2l,row3l,row4l,row1h,row2h,row3h,row4h);
+
+#endif
+
diff --git a/include/blake2s-load-sse2.h b/include/blake2s-load-sse2.h
new file mode 100644
--- /dev/null
+++ b/include/blake2s-load-sse2.h
@@ -0,0 +1,59 @@
+/*
+   BLAKE2 reference source code package - optimized C implementations
+
+   Written in 2012 by Samuel Neves <sneves@dei.uc.pt>
+
+   To the extent possible under law, the author(s) have dedicated all copyright
+   and related and neighboring rights to this software to the public domain
+   worldwide. This software is distributed without any warranty.
+
+   You should have received a copy of the CC0 Public Domain Dedication along with
+   this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
+*/
+#pragma once
+#ifndef __BLAKE2S_LOAD_SSE2_H__
+#define __BLAKE2S_LOAD_SSE2_H__
+
+#define LOAD_MSG_0_1(buf) buf = _mm_set_epi32(m6,m4,m2,m0)
+#define LOAD_MSG_0_2(buf) buf = _mm_set_epi32(m7,m5,m3,m1)
+#define LOAD_MSG_0_3(buf) buf = _mm_set_epi32(m14,m12,m10,m8)
+#define LOAD_MSG_0_4(buf) buf = _mm_set_epi32(m15,m13,m11,m9)
+#define LOAD_MSG_1_1(buf) buf = _mm_set_epi32(m13,m9,m4,m14)
+#define LOAD_MSG_1_2(buf) buf = _mm_set_epi32(m6,m15,m8,m10)
+#define LOAD_MSG_1_3(buf) buf = _mm_set_epi32(m5,m11,m0,m1)
+#define LOAD_MSG_1_4(buf) buf = _mm_set_epi32(m3,m7,m2,m12)
+#define LOAD_MSG_2_1(buf) buf = _mm_set_epi32(m15,m5,m12,m11)
+#define LOAD_MSG_2_2(buf) buf = _mm_set_epi32(m13,m2,m0,m8)
+#define LOAD_MSG_2_3(buf) buf = _mm_set_epi32(m9,m7,m3,m10)
+#define LOAD_MSG_2_4(buf) buf = _mm_set_epi32(m4,m1,m6,m14)
+#define LOAD_MSG_3_1(buf) buf = _mm_set_epi32(m11,m13,m3,m7)
+#define LOAD_MSG_3_2(buf) buf = _mm_set_epi32(m14,m12,m1,m9)
+#define LOAD_MSG_3_3(buf) buf = _mm_set_epi32(m15,m4,m5,m2)
+#define LOAD_MSG_3_4(buf) buf = _mm_set_epi32(m8,m0,m10,m6)
+#define LOAD_MSG_4_1(buf) buf = _mm_set_epi32(m10,m2,m5,m9)
+#define LOAD_MSG_4_2(buf) buf = _mm_set_epi32(m15,m4,m7,m0)
+#define LOAD_MSG_4_3(buf) buf = _mm_set_epi32(m3,m6,m11,m14)
+#define LOAD_MSG_4_4(buf) buf = _mm_set_epi32(m13,m8,m12,m1)
+#define LOAD_MSG_5_1(buf) buf = _mm_set_epi32(m8,m0,m6,m2)
+#define LOAD_MSG_5_2(buf) buf = _mm_set_epi32(m3,m11,m10,m12)
+#define LOAD_MSG_5_3(buf) buf = _mm_set_epi32(m1,m15,m7,m4)
+#define LOAD_MSG_5_4(buf) buf = _mm_set_epi32(m9,m14,m5,m13)
+#define LOAD_MSG_6_1(buf) buf = _mm_set_epi32(m4,m14,m1,m12)
+#define LOAD_MSG_6_2(buf) buf = _mm_set_epi32(m10,m13,m15,m5)
+#define LOAD_MSG_6_3(buf) buf = _mm_set_epi32(m8,m9,m6,m0)
+#define LOAD_MSG_6_4(buf) buf = _mm_set_epi32(m11,m2,m3,m7)
+#define LOAD_MSG_7_1(buf) buf = _mm_set_epi32(m3,m12,m7,m13)
+#define LOAD_MSG_7_2(buf) buf = _mm_set_epi32(m9,m1,m14,m11)
+#define LOAD_MSG_7_3(buf) buf = _mm_set_epi32(m2,m8,m15,m5)
+#define LOAD_MSG_7_4(buf) buf = _mm_set_epi32(m10,m6,m4,m0)
+#define LOAD_MSG_8_1(buf) buf = _mm_set_epi32(m0,m11,m14,m6)
+#define LOAD_MSG_8_2(buf) buf = _mm_set_epi32(m8,m3,m9,m15)
+#define LOAD_MSG_8_3(buf) buf = _mm_set_epi32(m10,m1,m13,m12)
+#define LOAD_MSG_8_4(buf) buf = _mm_set_epi32(m5,m4,m7,m2)
+#define LOAD_MSG_9_1(buf) buf = _mm_set_epi32(m1,m7,m8,m10)
+#define LOAD_MSG_9_2(buf) buf = _mm_set_epi32(m5,m6,m4,m2)
+#define LOAD_MSG_9_3(buf) buf = _mm_set_epi32(m13,m3,m9,m15)
+#define LOAD_MSG_9_4(buf) buf = _mm_set_epi32(m0,m12,m14,m11)
+
+
+#endif
diff --git a/include/blake2s-load-sse41.h b/include/blake2s-load-sse41.h
new file mode 100644
--- /dev/null
+++ b/include/blake2s-load-sse41.h
@@ -0,0 +1,229 @@
+/*
+   BLAKE2 reference source code package - optimized C implementations
+
+   Written in 2012 by Samuel Neves <sneves@dei.uc.pt>
+
+   To the extent possible under law, the author(s) have dedicated all copyright
+   and related and neighboring rights to this software to the public domain
+   worldwide. This software is distributed without any warranty.
+
+   You should have received a copy of the CC0 Public Domain Dedication along with
+   this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
+*/
+#pragma once
+#ifndef __BLAKE2S_LOAD_SSE41_H__
+#define __BLAKE2S_LOAD_SSE41_H__
+
+#define LOAD_MSG_0_1(buf) \
+buf = TOI(_mm_shuffle_ps(TOF(m0), TOF(m1), _MM_SHUFFLE(2,0,2,0)));
+
+#define LOAD_MSG_0_2(buf) \
+buf = TOI(_mm_shuffle_ps(TOF(m0), TOF(m1), _MM_SHUFFLE(3,1,3,1)));
+
+#define LOAD_MSG_0_3(buf) \
+buf = TOI(_mm_shuffle_ps(TOF(m2), TOF(m3), _MM_SHUFFLE(2,0,2,0)));
+
+#define LOAD_MSG_0_4(buf) \
+buf = TOI(_mm_shuffle_ps(TOF(m2), TOF(m3), _MM_SHUFFLE(3,1,3,1)));
+
+#define LOAD_MSG_1_1(buf) \
+t0 = _mm_blend_epi16(m1, m2, 0x0C); \
+t1 = _mm_slli_si128(m3, 4); \
+t2 = _mm_blend_epi16(t0, t1, 0xF0); \
+buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2,1,0,3));
+
+#define LOAD_MSG_1_2(buf) \
+t0 = _mm_shuffle_epi32(m2,_MM_SHUFFLE(0,0,2,0)); \
+t1 = _mm_blend_epi16(m1,m3,0xC0); \
+t2 = _mm_blend_epi16(t0, t1, 0xF0); \
+buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2,3,0,1));
+
+#define LOAD_MSG_1_3(buf) \
+t0 = _mm_slli_si128(m1, 4); \
+t1 = _mm_blend_epi16(m2, t0, 0x30); \
+t2 = _mm_blend_epi16(m0, t1, 0xF0); \
+buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2,3,0,1));
+
+#define LOAD_MSG_1_4(buf) \
+t0 = _mm_unpackhi_epi32(m0,m1); \
+t1 = _mm_slli_si128(m3, 4); \
+t2 = _mm_blend_epi16(t0, t1, 0x0C); \
+buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2,3,0,1));
+
+#define LOAD_MSG_2_1(buf) \
+t0 = _mm_unpackhi_epi32(m2,m3); \
+t1 = _mm_blend_epi16(m3,m1,0x0C); \
+t2 = _mm_blend_epi16(t0, t1, 0x0F); \
+buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(3,1,0,2));
+
+#define LOAD_MSG_2_2(buf) \
+t0 = _mm_unpacklo_epi32(m2,m0); \
+t1 = _mm_blend_epi16(t0, m0, 0xF0); \
+t2 = _mm_slli_si128(m3, 8); \
+buf = _mm_blend_epi16(t1, t2, 0xC0);
+
+#define LOAD_MSG_2_3(buf) \
+t0 = _mm_blend_epi16(m0, m2, 0x3C); \
+t1 = _mm_srli_si128(m1, 12); \
+t2 = _mm_blend_epi16(t0,t1,0x03); \
+buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(1,0,3,2));
+
+#define LOAD_MSG_2_4(buf) \
+t0 = _mm_slli_si128(m3, 4); \
+t1 = _mm_blend_epi16(m0, m1, 0x33); \
+t2 = _mm_blend_epi16(t1, t0, 0xC0); \
+buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(0,1,2,3));
+
+#define LOAD_MSG_3_1(buf) \
+t0 = _mm_unpackhi_epi32(m0,m1); \
+t1 = _mm_unpackhi_epi32(t0, m2); \
+t2 = _mm_blend_epi16(t1, m3, 0x0C); \
+buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(3,1,0,2));
+
+#define LOAD_MSG_3_2(buf) \
+t0 = _mm_slli_si128(m2, 8); \
+t1 = _mm_blend_epi16(m3,m0,0x0C); \
+t2 = _mm_blend_epi16(t1, t0, 0xC0); \
+buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2,0,1,3));
+
+#define LOAD_MSG_3_3(buf) \
+t0 = _mm_blend_epi16(m0,m1,0x0F); \
+t1 = _mm_blend_epi16(t0, m3, 0xC0); \
+buf = _mm_shuffle_epi32(t1, _MM_SHUFFLE(3,0,1,2));
+
+#define LOAD_MSG_3_4(buf) \
+t0 = _mm_unpacklo_epi32(m0,m2); \
+t1 = _mm_unpackhi_epi32(m1,m2); \
+buf = _mm_unpacklo_epi64(t1,t0);
+
+#define LOAD_MSG_4_1(buf) \
+t0 = _mm_unpacklo_epi64(m1,m2); \
+t1 = _mm_unpackhi_epi64(m0,m2); \
+t2 = _mm_blend_epi16(t0,t1,0x33); \
+buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2,0,1,3));
+
+#define LOAD_MSG_4_2(buf) \
+t0 = _mm_unpackhi_epi64(m1,m3); \
+t1 = _mm_unpacklo_epi64(m0,m1); \
+buf = _mm_blend_epi16(t0,t1,0x33);
+
+#define LOAD_MSG_4_3(buf) \
+t0 = _mm_unpackhi_epi64(m3,m1); \
+t1 = _mm_unpackhi_epi64(m2,m0); \
+buf = _mm_blend_epi16(t1,t0,0x33);
+
+#define LOAD_MSG_4_4(buf) \
+t0 = _mm_blend_epi16(m0,m2,0x03); \
+t1 = _mm_slli_si128(t0, 8); \
+t2 = _mm_blend_epi16(t1,m3,0x0F); \
+buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(1,2,0,3));
+
+#define LOAD_MSG_5_1(buf) \
+t0 = _mm_unpackhi_epi32(m0,m1); \
+t1 = _mm_unpacklo_epi32(m0,m2); \
+buf = _mm_unpacklo_epi64(t0,t1);
+
+#define LOAD_MSG_5_2(buf) \
+t0 = _mm_srli_si128(m2, 4); \
+t1 = _mm_blend_epi16(m0,m3,0x03); \
+buf = _mm_blend_epi16(t1,t0,0x3C);
+
+#define LOAD_MSG_5_3(buf) \
+t0 = _mm_blend_epi16(m1,m0,0x0C); \
+t1 = _mm_srli_si128(m3, 4); \
+t2 = _mm_blend_epi16(t0,t1,0x30); \
+buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(1,2,3,0));
+
+#define LOAD_MSG_5_4(buf) \
+t0 = _mm_unpacklo_epi64(m1,m2); \
+t1= _mm_shuffle_epi32(m3, _MM_SHUFFLE(0,2,0,1)); \
+buf = _mm_blend_epi16(t0,t1,0x33);
+
+#define LOAD_MSG_6_1(buf) \
+t0 = _mm_slli_si128(m1, 12); \
+t1 = _mm_blend_epi16(m0,m3,0x33); \
+buf = _mm_blend_epi16(t1,t0,0xC0);
+
+#define LOAD_MSG_6_2(buf) \
+t0 = _mm_blend_epi16(m3,m2,0x30); \
+t1 = _mm_srli_si128(m1, 4); \
+t2 = _mm_blend_epi16(t0,t1,0x03); \
+buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2,1,3,0));
+
+#define LOAD_MSG_6_3(buf) \
+t0 = _mm_unpacklo_epi64(m0,m2); \
+t1 = _mm_srli_si128(m1, 4); \
+buf = _mm_shuffle_epi32(_mm_blend_epi16(t0,t1,0x0C), _MM_SHUFFLE(2,3,1,0));
+
+#define LOAD_MSG_6_4(buf) \
+t0 = _mm_unpackhi_epi32(m1,m2); \
+t1 = _mm_unpackhi_epi64(m0,t0); \
+buf = _mm_shuffle_epi32(t1, _MM_SHUFFLE(3,0,1,2));
+
+#define LOAD_MSG_7_1(buf) \
+t0 = _mm_unpackhi_epi32(m0,m1); \
+t1 = _mm_blend_epi16(t0,m3,0x0F); \
+buf = _mm_shuffle_epi32(t1,_MM_SHUFFLE(2,0,3,1));
+
+#define LOAD_MSG_7_2(buf) \
+t0 = _mm_blend_epi16(m2,m3,0x30); \
+t1 = _mm_srli_si128(m0,4); \
+t2 = _mm_blend_epi16(t0,t1,0x03); \
+buf = _mm_shuffle_epi32(t2, _MM_SHUFFLE(1,0,2,3));
+
+#define LOAD_MSG_7_3(buf) \
+t0 = _mm_unpackhi_epi64(m0,m3); \
+t1 = _mm_unpacklo_epi64(m1,m2); \
+t2 = _mm_blend_epi16(t0,t1,0x3C); \
+buf = _mm_shuffle_epi32(t2,_MM_SHUFFLE(0,2,3,1));
+
+#define LOAD_MSG_7_4(buf) \
+t0 = _mm_unpacklo_epi32(m0,m1); \
+t1 = _mm_unpackhi_epi32(m1,m2); \
+buf = _mm_unpacklo_epi64(t0,t1);
+
+#define LOAD_MSG_8_1(buf) \
+t0 = _mm_unpackhi_epi32(m1,m3); \
+t1 = _mm_unpacklo_epi64(t0,m0); \
+t2 = _mm_blend_epi16(t1,m2,0xC0); \
+buf = _mm_shufflehi_epi16(t2,_MM_SHUFFLE(1,0,3,2));
+
+#define LOAD_MSG_8_2(buf) \
+t0 = _mm_unpackhi_epi32(m0,m3); \
+t1 = _mm_blend_epi16(m2,t0,0xF0); \
+buf = _mm_shuffle_epi32(t1,_MM_SHUFFLE(0,2,1,3));
+
+#define LOAD_MSG_8_3(buf) \
+t0 = _mm_blend_epi16(m2,m0,0x0C); \
+t1 = _mm_slli_si128(t0,4); \
+buf = _mm_blend_epi16(t1,m3,0x0F);
+
+#define LOAD_MSG_8_4(buf) \
+t0 = _mm_blend_epi16(m1,m0,0x30); \
+buf = _mm_shuffle_epi32(t0,_MM_SHUFFLE(1,0,3,2));
+
+#define LOAD_MSG_9_1(buf) \
+t0 = _mm_blend_epi16(m0,m2,0x03); \
+t1 = _mm_blend_epi16(m1,m2,0x30); \
+t2 = _mm_blend_epi16(t1,t0,0x0F); \
+buf = _mm_shuffle_epi32(t2,_MM_SHUFFLE(1,3,0,2));
+
+#define LOAD_MSG_9_2(buf) \
+t0 = _mm_slli_si128(m0,4); \
+t1 = _mm_blend_epi16(m1,t0,0xC0); \
+buf = _mm_shuffle_epi32(t1,_MM_SHUFFLE(1,2,0,3));
+
+#define LOAD_MSG_9_3(buf) \
+t0 = _mm_unpackhi_epi32(m0,m3); \
+t1 = _mm_unpacklo_epi32(m2,m3); \
+t2 = _mm_unpackhi_epi64(t0,t1); \
+buf = _mm_shuffle_epi32(t2,_MM_SHUFFLE(3,0,2,1));
+
+#define LOAD_MSG_9_4(buf) \
+t0 = _mm_blend_epi16(m3,m2,0xC0); \
+t1 = _mm_unpacklo_epi32(m0,m3); \
+t2 = _mm_blend_epi16(t0,t1,0x0F); \
+buf = _mm_shuffle_epi32(t2,_MM_SHUFFLE(0,1,2,3));
+
+#endif
+
diff --git a/include/blake2s-load-xop.h b/include/blake2s-load-xop.h
new file mode 100644
--- /dev/null
+++ b/include/blake2s-load-xop.h
@@ -0,0 +1,189 @@
+/*
+   BLAKE2 reference source code package - optimized C implementations
+
+   Written in 2012 by Samuel Neves <sneves@dei.uc.pt>
+
+   To the extent possible under law, the author(s) have dedicated all copyright
+   and related and neighboring rights to this software to the public domain
+   worldwide. This software is distributed without any warranty.
+
+   You should have received a copy of the CC0 Public Domain Dedication along with
+   this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
+*/
+#pragma once
+#ifndef __BLAKE2S_LOAD_XOP_H__
+#define __BLAKE2S_LOAD_XOP_H__
+
+#define TOB(x) ((x)*4*0x01010101 + 0x03020100) // ..or not TOB
+
+/* Basic VPPERM emulation, for testing purposes */
+/*static __m128i _mm_perm_epi8(const __m128i src1, const __m128i src2, const __m128i sel)
+{
+   const __m128i sixteen = _mm_set1_epi8(16);
+   const __m128i t0 = _mm_shuffle_epi8(src1, sel);
+   const __m128i s1 = _mm_shuffle_epi8(src2, _mm_sub_epi8(sel, sixteen));
+   const __m128i mask = _mm_or_si128(_mm_cmpeq_epi8(sel, sixteen),
+                                     _mm_cmpgt_epi8(sel, sixteen)); // (>=16) = 0xff : 00
+   return _mm_blendv_epi8(t0, s1, mask);
+}*/
+
+#define LOAD_MSG_0_1(buf) \
+buf = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(6),TOB(4),TOB(2),TOB(0)) );
+
+#define LOAD_MSG_0_2(buf) \
+buf = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(7),TOB(5),TOB(3),TOB(1)) );
+
+#define LOAD_MSG_0_3(buf) \
+buf = _mm_perm_epi8(m2, m3, _mm_set_epi32(TOB(6),TOB(4),TOB(2),TOB(0)) );
+
+#define LOAD_MSG_0_4(buf) \
+buf = _mm_perm_epi8(m2, m3, _mm_set_epi32(TOB(7),TOB(5),TOB(3),TOB(1)) );
+
+#define LOAD_MSG_1_1(buf) \
+t0 = _mm_perm_epi8(m1, m2, _mm_set_epi32(TOB(0),TOB(5),TOB(0),TOB(0)) ); \
+buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(5),TOB(2),TOB(1),TOB(6)) );
+
+#define LOAD_MSG_1_2(buf) \
+t1 = _mm_perm_epi8(m1, m2, _mm_set_epi32(TOB(2),TOB(0),TOB(4),TOB(6)) ); \
+buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(3),TOB(7),TOB(1),TOB(0)) );
+
+#define LOAD_MSG_1_3(buf) \
+t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(5),TOB(0),TOB(0),TOB(1)) ); \
+buf = _mm_perm_epi8(t0, m2, _mm_set_epi32(TOB(3),TOB(7),TOB(1),TOB(0)) );
+
+#define LOAD_MSG_1_4(buf) \
+t1 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(3),TOB(7),TOB(2),TOB(0)) ); \
+buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(3),TOB(2),TOB(1),TOB(4)) );
+
+#define LOAD_MSG_2_1(buf) \
+t0 = _mm_perm_epi8(m1, m2, _mm_set_epi32(TOB(0),TOB(1),TOB(0),TOB(7)) ); \
+buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(7),TOB(2),TOB(4),TOB(0)) );
+
+#define LOAD_MSG_2_2(buf) \
+t1 = _mm_perm_epi8(m0, m2, _mm_set_epi32(TOB(0),TOB(2),TOB(0),TOB(4)) ); \
+buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(5),TOB(2),TOB(1),TOB(0)) );
+
+#define LOAD_MSG_2_3(buf) \
+t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(7),TOB(3),TOB(0)) ); \
+buf = _mm_perm_epi8(t0, m2, _mm_set_epi32(TOB(5),TOB(2),TOB(1),TOB(6)) );
+
+#define LOAD_MSG_2_4(buf) \
+t1 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(4),TOB(1),TOB(6),TOB(0)) ); \
+buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(3),TOB(2),TOB(1),TOB(6)) );
+
+#define LOAD_MSG_3_1(buf) \
+t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(0),TOB(3),TOB(7)) ); \
+t0 = _mm_perm_epi8(t0, m2, _mm_set_epi32(TOB(7),TOB(2),TOB(1),TOB(0)) ); \
+buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(3),TOB(5),TOB(1),TOB(0)) );
+
+#define LOAD_MSG_3_2(buf) \
+t1 = _mm_perm_epi8(m0, m2, _mm_set_epi32(TOB(0),TOB(0),TOB(1),TOB(5)) ); \
+buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(6),TOB(4),TOB(1),TOB(0)) );
+
+#define LOAD_MSG_3_3(buf) \
+t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(4),TOB(5),TOB(2)) ); \
+buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(7),TOB(2),TOB(1),TOB(0)) );
+
+#define LOAD_MSG_3_4(buf) \
+t1 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(0),TOB(0),TOB(6)) ); \
+buf = _mm_perm_epi8(t1, m2, _mm_set_epi32(TOB(4),TOB(2),TOB(6),TOB(0)) );
+
+#define LOAD_MSG_4_1(buf) \
+t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(2),TOB(5),TOB(0)) ); \
+buf = _mm_perm_epi8(t0, m2, _mm_set_epi32(TOB(6),TOB(2),TOB(1),TOB(5)) );
+
+#define LOAD_MSG_4_2(buf) \
+t1 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(4),TOB(7),TOB(0)) ); \
+buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(7),TOB(2),TOB(1),TOB(0)) );
+
+#define LOAD_MSG_4_3(buf) \
+t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(3),TOB(6),TOB(0),TOB(0)) ); \
+t0 = _mm_perm_epi8(t0, m2, _mm_set_epi32(TOB(3),TOB(2),TOB(7),TOB(0)) ); \
+buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(3),TOB(2),TOB(1),TOB(6)) );
+
+#define LOAD_MSG_4_4(buf) \
+t1 = _mm_perm_epi8(m0, m2, _mm_set_epi32(TOB(0),TOB(4),TOB(0),TOB(1)) ); \
+buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(5),TOB(2),TOB(4),TOB(0)) );
+
+#define LOAD_MSG_5_1(buf) \
+t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(0),TOB(6),TOB(2)) ); \
+buf = _mm_perm_epi8(t0, m2, _mm_set_epi32(TOB(4),TOB(2),TOB(1),TOB(0)) );
+
+#define LOAD_MSG_5_2(buf) \
+t1 = _mm_perm_epi8(m0, m2, _mm_set_epi32(TOB(3),TOB(7),TOB(6),TOB(0)) ); \
+buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(3),TOB(2),TOB(1),TOB(4)) );
+
+#define LOAD_MSG_5_3(buf) \
+t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(1),TOB(0),TOB(7),TOB(4)) ); \
+buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(3),TOB(7),TOB(1),TOB(0)) );
+
+#define LOAD_MSG_5_4(buf) \
+t1 = _mm_perm_epi8(m1, m2, _mm_set_epi32(TOB(5),TOB(0),TOB(1),TOB(0)) ); \
+buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(3),TOB(6),TOB(1),TOB(5)) );
+
+#define LOAD_MSG_6_1(buf) \
+t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(4),TOB(0),TOB(1),TOB(0)) ); \
+buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(3),TOB(6),TOB(1),TOB(4)) );
+
+#define LOAD_MSG_6_2(buf) \
+t1 = _mm_perm_epi8(m1, m2, _mm_set_epi32(TOB(6),TOB(0),TOB(0),TOB(1)) ); \
+buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(3),TOB(5),TOB(7),TOB(0)) );
+
+#define LOAD_MSG_6_3(buf) \
+t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(0),TOB(6),TOB(0)) ); \
+buf = _mm_perm_epi8(t0, m2, _mm_set_epi32(TOB(4),TOB(5),TOB(1),TOB(0)) );
+
+#define LOAD_MSG_6_4(buf) \
+t1 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(2),TOB(3),TOB(7)) ); \
+buf = _mm_perm_epi8(t1, m2, _mm_set_epi32(TOB(7),TOB(2),TOB(1),TOB(0)) );
+
+#define LOAD_MSG_7_1(buf) \
+t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(3),TOB(0),TOB(7),TOB(0)) ); \
+buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(3),TOB(4),TOB(1),TOB(5)) );
+
+#define LOAD_MSG_7_2(buf) \
+t1 = _mm_perm_epi8(m0, m2, _mm_set_epi32(TOB(5),TOB(1),TOB(0),TOB(7)) ); \
+buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(3),TOB(2),TOB(6),TOB(0)) );
+
+#define LOAD_MSG_7_3(buf) \
+t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(2),TOB(0),TOB(0),TOB(5)) ); \
+t0 = _mm_perm_epi8(t0, m2, _mm_set_epi32(TOB(3),TOB(4),TOB(1),TOB(0)) ); \
+buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(3),TOB(2),TOB(7),TOB(0)) );
+
+#define LOAD_MSG_7_4(buf) \
+t1 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(6),TOB(4),TOB(0)) ); \
+buf = _mm_perm_epi8(t1, m2, _mm_set_epi32(TOB(6),TOB(2),TOB(1),TOB(0)) );
+
+#define LOAD_MSG_8_1(buf) \
+t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(0),TOB(0),TOB(0),TOB(6)) ); \
+t0 = _mm_perm_epi8(t0, m2, _mm_set_epi32(TOB(3),TOB(7),TOB(1),TOB(0)) ); \
+buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(3),TOB(2),TOB(6),TOB(0)) );
+
+#define LOAD_MSG_8_2(buf) \
+t1 = _mm_perm_epi8(m0, m2, _mm_set_epi32(TOB(4),TOB(3),TOB(5),TOB(0)) ); \
+buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(3),TOB(2),TOB(1),TOB(7)) );
+
+#define LOAD_MSG_8_3(buf) \
+t0 = _mm_perm_epi8(m0, m2, _mm_set_epi32(TOB(6),TOB(1),TOB(0),TOB(0)) ); \
+buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(3),TOB(2),TOB(5),TOB(4)) ); \
+ 
+#define LOAD_MSG_8_4(buf) \
+buf = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(5),TOB(4),TOB(7),TOB(2)) );
+
+#define LOAD_MSG_9_1(buf) \
+t0 = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(1),TOB(7),TOB(0),TOB(0)) ); \
+buf = _mm_perm_epi8(t0, m2, _mm_set_epi32(TOB(3),TOB(2),TOB(4),TOB(6)) );
+
+#define LOAD_MSG_9_2(buf) \
+buf = _mm_perm_epi8(m0, m1, _mm_set_epi32(TOB(5),TOB(6),TOB(4),TOB(2)) );
+
+#define LOAD_MSG_9_3(buf) \
+t0 = _mm_perm_epi8(m0, m2, _mm_set_epi32(TOB(0),TOB(3),TOB(5),TOB(0)) ); \
+buf = _mm_perm_epi8(t0, m3, _mm_set_epi32(TOB(5),TOB(2),TOB(1),TOB(7)) );
+
+#define LOAD_MSG_9_4(buf) \
+t1 = _mm_perm_epi8(m0, m2, _mm_set_epi32(TOB(0),TOB(0),TOB(0),TOB(7)) ); \
+buf = _mm_perm_epi8(t1, m3, _mm_set_epi32(TOB(3),TOB(4),TOB(6),TOB(0)) );
+
+#endif
+
diff --git a/include/blake2s-round.h b/include/blake2s-round.h
new file mode 100644
--- /dev/null
+++ b/include/blake2s-round.h
@@ -0,0 +1,88 @@
+/*
+   BLAKE2 reference source code package - optimized C implementations
+
+   Written in 2012 by Samuel Neves <sneves@dei.uc.pt>
+
+   To the extent possible under law, the author(s) have dedicated all copyright
+   and related and neighboring rights to this software to the public domain
+   worldwide. This software is distributed without any warranty.
+
+   You should have received a copy of the CC0 Public Domain Dedication along with
+   this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
+*/
+#pragma once
+#ifndef __BLAKE2S_ROUND_H__
+#define __BLAKE2S_ROUND_H__
+
+#define LOADU(p)  _mm_loadu_si128( (const __m128i *)(p) )
+#define STOREU(p,r) _mm_storeu_si128((__m128i *)(p), r)
+
+#define TOF(reg) _mm_castsi128_ps((reg))
+#define TOI(reg) _mm_castps_si128((reg))
+
+#define LIKELY(x) __builtin_expect((x),1)
+
+
+/* Microarchitecture-specific macros */
+#ifndef HAVE_XOP
+#ifdef HAVE_SSSE3
+#define _mm_roti_epi32(r, c) ( \
+                (8==-(c)) ? _mm_shuffle_epi8(r,r8) \
+              : (16==-(c)) ? _mm_shuffle_epi8(r,r16) \
+              : _mm_xor_si128(_mm_srli_epi32( (r), -(c) ),_mm_slli_epi32( (r), 32-(-(c)) )) )
+#else
+#define _mm_roti_epi32(r, c) _mm_xor_si128(_mm_srli_epi32( (r), -(c) ),_mm_slli_epi32( (r), 32-(-(c)) ))
+#endif
+#else
+/* ... */
+#endif
+
+
+#define G1(row1,row2,row3,row4,buf) \
+  row1 = _mm_add_epi32( _mm_add_epi32( row1, buf), row2 ); \
+  row4 = _mm_xor_si128( row4, row1 ); \
+  row4 = _mm_roti_epi32(row4, -16); \
+  row3 = _mm_add_epi32( row3, row4 );   \
+  row2 = _mm_xor_si128( row2, row3 ); \
+  row2 = _mm_roti_epi32(row2, -12);
+
+#define G2(row1,row2,row3,row4,buf) \
+  row1 = _mm_add_epi32( _mm_add_epi32( row1, buf), row2 ); \
+  row4 = _mm_xor_si128( row4, row1 ); \
+  row4 = _mm_roti_epi32(row4, -8); \
+  row3 = _mm_add_epi32( row3, row4 );   \
+  row2 = _mm_xor_si128( row2, row3 ); \
+  row2 = _mm_roti_epi32(row2, -7);
+
+#define DIAGONALIZE(row1,row2,row3,row4) \
+  row4 = _mm_shuffle_epi32( row4, _MM_SHUFFLE(2,1,0,3) ); \
+  row3 = _mm_shuffle_epi32( row3, _MM_SHUFFLE(1,0,3,2) ); \
+  row2 = _mm_shuffle_epi32( row2, _MM_SHUFFLE(0,3,2,1) );
+
+#define UNDIAGONALIZE(row1,row2,row3,row4) \
+  row4 = _mm_shuffle_epi32( row4, _MM_SHUFFLE(0,3,2,1) ); \
+  row3 = _mm_shuffle_epi32( row3, _MM_SHUFFLE(1,0,3,2) ); \
+  row2 = _mm_shuffle_epi32( row2, _MM_SHUFFLE(2,1,0,3) );
+
+#if defined(HAVE_XOP)
+#include "blake2s-load-xop.h"
+#elif defined(HAVE_SSE41)
+#include "blake2s-load-sse41.h"
+#else
+#include "blake2s-load-sse2.h"
+#endif
+
+#define ROUND(r)  \
+  LOAD_MSG_ ##r ##_1(buf1); \
+  G1(row1,row2,row3,row4,buf1); \
+  LOAD_MSG_ ##r ##_2(buf2); \
+  G2(row1,row2,row3,row4,buf2); \
+  DIAGONALIZE(row1,row2,row3,row4); \
+  LOAD_MSG_ ##r ##_3(buf3); \
+  G1(row1,row2,row3,row4,buf3); \
+  LOAD_MSG_ ##r ##_4(buf4); \
+  G2(row1,row2,row3,row4,buf4); \
+  UNDIAGONALIZE(row1,row2,row3,row4); \
+ 
+#endif
+
diff --git a/src/Crypto/Hash/BLAKE2/BLAKE2b.hsc b/src/Crypto/Hash/BLAKE2/BLAKE2b.hsc
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Hash/BLAKE2/BLAKE2b.hsc
@@ -0,0 +1,95 @@
+----------------------------------------------------------------
+-- |
+-- Module      : Crypto.Hash.BLAKE2.BLAKE2b
+-- Maintainer  : John Galt <jgalt@centromere.net>
+-- Stability   : experimental
+-- Portability : POSIX
+
+module Crypto.Hash.BLAKE2.BLAKE2b
+  ( -- * Types
+    BLAKE2bState,
+    -- * Functions
+    initialize,
+    initialize',
+    update,
+    finalize,
+    hash
+  ) where
+
+import Data.ByteString    (ByteString)
+import Foreign.C.Types    (CInt(..))
+import Foreign.ForeignPtr (ForeignPtr)
+import Foreign.Storable   (Storable(..))
+
+import Crypto.Hash.BLAKE2.Internal
+
+#include "blake2.h"
+
+data BLAKE2bStruct
+
+instance Storable BLAKE2bStruct where
+  sizeOf    _ = #{size blake2b_state}
+  alignment _ = #{alignment blake2b_state}
+  peek        = error "peek not implemented"
+  poke        = error "poke not implemented"
+
+#let alignment t = "%lu", (unsigned long)offsetof(struct {char x__; t (y__); }, y__)
+
+-- | The hash state.
+type BLAKE2bState = ForeignPtr BLAKE2bStruct
+
+-- | Create a new hashing state.
+initialize :: Int
+           -- ^ Output length
+           -> BLAKE2bState
+initialize = initializer c_blake2b_init
+{-# INLINE initialize #-}
+
+-- | Create a new keyed hashing state.
+initialize' :: Int
+            -- ^ Output length
+            -> ByteString
+            -- ^ Key
+            -> BLAKE2bState
+initialize' = initializer' c_blake2b_init_key
+{-# INLINE initialize' #-}
+
+-- | Add data to the hashing state.
+update :: ByteString
+       -- ^ Data to hash
+       -> BLAKE2bState
+       -- ^ Hashing state
+       -> BLAKE2bState
+update = updater c_blake2b_update
+{-# INLINE update #-}
+
+-- | Finalize the hashing state.
+finalize :: Int
+         -- ^ Output length
+         -> BLAKE2bState
+         -- ^ Hashing state
+         -> ByteString
+finalize = finalizer c_blake2b_final
+{-# INLINE finalize #-}
+
+-- | Perform hashing all in one step.
+hash :: Int
+     -- ^ Output length
+     -> ByteString
+     -- ^ Key
+     -> ByteString
+     -- ^ Data to hash
+     -> ByteString
+hash = hasher c_blake2b
+{-# INLINE hash #-}
+
+foreign import ccall unsafe "blake2.h blake2b"
+  c_blake2b :: HashFunc
+foreign import ccall unsafe "blake2.h blake2b_init"
+  c_blake2b_init :: InitFunc BLAKE2bStruct
+foreign import ccall unsafe "blake2.h blake2b_init_key"
+  c_blake2b_init_key :: InitKeyFunc BLAKE2bStruct
+foreign import ccall unsafe "blake2.h blake2b_update"
+  c_blake2b_update :: UpdateFunc BLAKE2bStruct
+foreign import ccall unsafe "blake2.h blake2b_final"
+  c_blake2b_final :: FinalFunc BLAKE2bStruct
diff --git a/src/Crypto/Hash/BLAKE2/BLAKE2bp.hsc b/src/Crypto/Hash/BLAKE2/BLAKE2bp.hsc
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Hash/BLAKE2/BLAKE2bp.hsc
@@ -0,0 +1,95 @@
+----------------------------------------------------------------
+-- |
+-- Module      : Crypto.Hash.BLAKE2.BLAKE2bp
+-- Maintainer  : John Galt <jgalt@centromere.net>
+-- Stability   : experimental
+-- Portability : POSIX
+
+module Crypto.Hash.BLAKE2.BLAKE2bp
+  ( -- * Types
+    BLAKE2bpState,
+    -- * Functions
+    initialize,
+    initialize',
+    update,
+    finalize,
+    hash
+  ) where
+
+import Data.ByteString    (ByteString)
+import Foreign.C.Types    (CInt(..))
+import Foreign.ForeignPtr (ForeignPtr)
+import Foreign.Storable   (Storable(..))
+
+import Crypto.Hash.BLAKE2.Internal
+
+#include "blake2.h"
+
+data BLAKE2bpStruct
+
+instance Storable BLAKE2bpStruct where
+  sizeOf    _ = #{size blake2bp_state}
+  alignment _ = #{alignment blake2bp_state}
+  peek        = error "peek not implemented"
+  poke        = error "poke not implemented"
+
+#let alignment t = "%lu", (unsigned long)offsetof(struct {char x__; t (y__); }, y__)
+
+-- | The hash state.
+type BLAKE2bpState = ForeignPtr BLAKE2bpStruct
+
+-- | Create a new hashing state.
+initialize :: Int
+           -- ^ Output length
+           -> BLAKE2bpState
+initialize = initializer c_blake2bp_init
+{-# INLINE initialize #-}
+
+-- | Create a new keyed hashing state.
+initialize' :: Int
+            -- ^ Output length
+            -> ByteString
+            -- ^ Key
+            -> BLAKE2bpState
+initialize' = initializer' c_blake2bp_init_key
+{-# INLINE initialize' #-}
+
+-- | Add data to the hashing state.
+update :: ByteString
+       -- ^ Data to hash
+       -> BLAKE2bpState
+       -- ^ Hashing state
+       -> BLAKE2bpState
+update = updater c_blake2bp_update
+{-# INLINE update #-}
+
+-- | Finalize the hashing state.
+finalize :: Int
+         -- ^ Output length
+         -> BLAKE2bpState
+         -- ^ Hashing state
+         -> ByteString
+finalize = finalizer c_blake2bp_final
+{-# INLINE finalize #-}
+
+-- | Perform hashing all in one step.
+hash :: Int
+     -- ^ Output length
+     -> ByteString
+     -- ^ Key
+     -> ByteString
+     -- ^ Data to hash
+     -> ByteString
+hash = hasher c_blake2bp
+{-# INLINE hash #-}
+
+foreign import ccall unsafe "blake2.h blake2bp"
+  c_blake2bp :: HashFunc
+foreign import ccall unsafe "blake2.h blake2bp_init"
+  c_blake2bp_init :: InitFunc BLAKE2bpStruct
+foreign import ccall unsafe "blake2.h blake2bp_init_key"
+  c_blake2bp_init_key :: InitKeyFunc BLAKE2bpStruct
+foreign import ccall unsafe "blake2.h blake2bp_update"
+  c_blake2bp_update :: UpdateFunc BLAKE2bpStruct
+foreign import ccall unsafe "blake2.h blake2bp_final"
+  c_blake2bp_final :: FinalFunc BLAKE2bpStruct
diff --git a/src/Crypto/Hash/BLAKE2/BLAKE2s.hsc b/src/Crypto/Hash/BLAKE2/BLAKE2s.hsc
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Hash/BLAKE2/BLAKE2s.hsc
@@ -0,0 +1,95 @@
+----------------------------------------------------------------
+-- |
+-- Module      : Crypto.Hash.BLAKE2.BLAKE2s
+-- Maintainer  : John Galt <jgalt@centromere.net>
+-- Stability   : experimental
+-- Portability : POSIX
+
+module Crypto.Hash.BLAKE2.BLAKE2s
+  ( -- * Types
+    BLAKE2sState,
+    -- * Functions
+    initialize,
+    initialize',
+    update,
+    finalize,
+    hash
+  ) where
+
+import Data.ByteString    (ByteString)
+import Foreign.C.Types    (CInt(..))
+import Foreign.ForeignPtr (ForeignPtr)
+import Foreign.Storable   (Storable(..))
+
+import Crypto.Hash.BLAKE2.Internal
+
+#include "blake2.h"
+
+data BLAKE2sStruct
+
+instance Storable BLAKE2sStruct where
+  sizeOf    _ = #{size blake2s_state}
+  alignment _ = #{alignment blake2s_state}
+  peek        = error "peek not implemented"
+  poke        = error "poke not implemented"
+
+#let alignment t = "%lu", (unsigned long)offsetof(struct {char x__; t (y__); }, y__)
+
+-- | The hash state.
+type BLAKE2sState = ForeignPtr BLAKE2sStruct
+
+-- | Create a new hashing state.
+initialize :: Int
+           -- ^ Output length
+           -> BLAKE2sState
+initialize = initializer c_blake2s_init
+{-# INLINE initialize #-}
+
+-- | Create a new keyed hashing state.
+initialize' :: Int
+            -- ^ Output length
+            -> ByteString
+            -- ^ Key
+            -> BLAKE2sState
+initialize' = initializer' c_blake2s_init_key
+{-# INLINE initialize' #-}
+
+-- | Add data to the hashing state.
+update :: ByteString
+       -- ^ Data to hash
+       -> BLAKE2sState
+       -- ^ Hashing state
+       -> BLAKE2sState
+update = updater c_blake2s_update
+{-# INLINE update #-}
+
+-- | Finalize the hashing state.
+finalize :: Int
+         -- ^ Output length
+         -> BLAKE2sState
+         -- ^ Hashing state
+         -> ByteString
+finalize = finalizer c_blake2s_final
+{-# INLINE finalize #-}
+
+-- | Perform hashing all in one step.
+hash :: Int
+     -- ^ Output length
+     -> ByteString
+     -- ^ Key
+     -> ByteString
+     -- ^ Data to hash
+     -> ByteString
+hash = hasher c_blake2s
+{-# INLINE hash #-}
+
+foreign import ccall unsafe "blake2.h blake2s"
+  c_blake2s :: HashFunc
+foreign import ccall unsafe "blake2.h blake2s_init"
+  c_blake2s_init :: InitFunc BLAKE2sStruct
+foreign import ccall unsafe "blake2.h blake2s_init_key"
+  c_blake2s_init_key :: InitKeyFunc BLAKE2sStruct
+foreign import ccall unsafe "blake2.h blake2s_update"
+  c_blake2s_update :: UpdateFunc BLAKE2sStruct
+foreign import ccall unsafe "blake2.h blake2s_final"
+  c_blake2s_final :: FinalFunc BLAKE2sStruct
diff --git a/src/Crypto/Hash/BLAKE2/BLAKE2sp.hsc b/src/Crypto/Hash/BLAKE2/BLAKE2sp.hsc
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Hash/BLAKE2/BLAKE2sp.hsc
@@ -0,0 +1,95 @@
+----------------------------------------------------------------
+-- |
+-- Module      : Crypto.Hash.BLAKE2.BLAKE2sp
+-- Maintainer  : John Galt <jgalt@centromere.net>
+-- Stability   : experimental
+-- Portability : POSIX
+
+module Crypto.Hash.BLAKE2.BLAKE2sp
+  ( -- * Types
+    BLAKE2spState,
+    -- * Functions
+    initialize,
+    initialize',
+    update,
+    finalize,
+    hash
+  ) where
+
+import Data.ByteString    (ByteString)
+import Foreign.C.Types    (CInt(..))
+import Foreign.ForeignPtr (ForeignPtr)
+import Foreign.Storable   (Storable(..))
+
+import Crypto.Hash.BLAKE2.Internal
+
+#include "blake2.h"
+
+data BLAKE2spStruct
+
+instance Storable BLAKE2spStruct where
+  sizeOf    _ = #{size blake2sp_state}
+  alignment _ = #{alignment blake2sp_state}
+  peek        = error "peek not implemented"
+  poke        = error "poke not implemented"
+
+#let alignment t = "%lu", (unsigned long)offsetof(struct {char x__; t (y__); }, y__)
+
+-- | The hash state.
+type BLAKE2spState = ForeignPtr BLAKE2spStruct
+
+-- | Create a new hashing state.
+initialize :: Int
+           -- ^ Output length
+           -> BLAKE2spState
+initialize = initializer c_blake2sp_init
+{-# INLINE initialize #-}
+
+-- | Create a new keyed hashing state.
+initialize' :: Int
+            -- ^ Output length
+            -> ByteString
+            -- ^ Key
+            -> BLAKE2spState
+initialize' = initializer' c_blake2sp_init_key
+{-# INLINE initialize' #-}
+
+-- | Add data to the hashing state.
+update :: ByteString
+       -- ^ Data to hash
+       -> BLAKE2spState
+       -- ^ Hashing state
+       -> BLAKE2spState
+update = updater c_blake2sp_update
+{-# INLINE update #-}
+
+-- | Finalize the hashing state.
+finalize :: Int
+         -- ^ Output length
+         -> BLAKE2spState
+         -- ^ Hashing state
+         -> ByteString
+finalize = finalizer c_blake2sp_final
+{-# INLINE finalize #-}
+
+-- | Perform hashing all in one step.
+hash :: Int
+     -- ^ Output length
+     -> ByteString
+     -- ^ Key
+     -> ByteString
+     -- ^ Data to hash
+     -> ByteString
+hash = hasher c_blake2sp
+{-# INLINE hash #-}
+
+foreign import ccall unsafe "blake2.h blake2sp"
+  c_blake2sp :: HashFunc
+foreign import ccall unsafe "blake2.h blake2sp_init"
+  c_blake2sp_init :: InitFunc BLAKE2spStruct
+foreign import ccall unsafe "blake2.h blake2sp_init_key"
+  c_blake2sp_init_key :: InitKeyFunc BLAKE2spStruct
+foreign import ccall unsafe "blake2.h blake2sp_update"
+  c_blake2sp_update :: UpdateFunc BLAKE2spStruct
+foreign import ccall unsafe "blake2.h blake2sp_final"
+  c_blake2sp_final :: FinalFunc BLAKE2spStruct
diff --git a/src/Crypto/Hash/BLAKE2/Internal.hs b/src/Crypto/Hash/BLAKE2/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Hash/BLAKE2/Internal.hs
@@ -0,0 +1,130 @@
+----------------------------------------------------------------
+-- |
+-- Module      : Crypto.Hash.BLAKE2.Internal
+-- Maintainer  : John Galt <jgalt@centromere.net>
+-- Stability   : experimental
+-- Portability : POSIX
+
+module Crypto.Hash.BLAKE2.Internal
+  ( -- * Types
+    InitFunc,
+    InitKeyFunc,
+    UpdateFunc,
+    FinalFunc,
+    HashFunc,
+    -- * Functions
+    initializer,
+    initializer',
+    updater,
+    finalizer,
+    hasher
+  ) where
+
+import Control.Monad            (void)
+import Data.ByteString          (ByteString)
+import Data.ByteString.Internal (create, toForeignPtr)
+import Data.ByteString.Unsafe   (unsafeUseAsCStringLen)
+import Data.Word                (Word8, Word64)
+import Foreign.C.Types          (CInt(..), CChar)
+import Foreign.ForeignPtr       (ForeignPtr, mallocForeignPtr, withForeignPtr)
+import Foreign.Marshal.Array    (copyArray)
+import Foreign.Ptr              (Ptr)
+import Foreign.Storable         (Storable(..))
+import System.IO.Unsafe         (unsafePerformIO)
+
+-- int blake2X_init( blake2s_state *S, const uint8_t outlen );
+type InitFunc a = Ptr a -> Int -> IO CInt
+
+-- int blake2X_init_key( blake2s_state *S, const uint8_t outlen, const void
+-- *key, const uint8_t keylen );
+type InitKeyFunc a = Ptr a -> Word8 -> Ptr CChar -> Word8 -> IO CInt
+
+-- int blake2X_update( blake2s_state *S, const uint8_t *in,
+-- uint64_t inlen );
+type UpdateFunc a = Ptr a -> Ptr Word8 -> Word64 -> IO CInt
+
+-- int blake2X_final( blake2s_state *S, uint8_t *out, uint8_t outlen );
+type FinalFunc a = Ptr a -> Ptr Word8 -> Word8 -> IO CInt
+
+-- int blake2X( uint8_t *out, const void *in, const void *key, const
+-- uint8_t outlen, const uint64_t inlen, uint8_t keylen );
+type HashFunc = Ptr Word8
+             -> Ptr CChar
+             -> Ptr CChar
+             -> Word8
+             -> Word64
+             -> Word8
+             -> IO CInt
+
+initializer :: Storable a
+            => InitFunc a
+            -> Int
+            -> ForeignPtr a
+initializer f outlen = unsafePerformIO $ do
+  fptr <- mallocForeignPtr
+  withForeignPtr fptr $ \ptr -> do
+    ret <- f ptr outlen
+    if ret == 0
+    then return fptr
+    else error "initialization failure"
+
+initializer' :: Storable a
+             => InitKeyFunc a
+             -> Int
+             -> ByteString
+             -> ForeignPtr a
+initializer' f outlen key = unsafePerformIO $ do
+  fptr <- mallocForeignPtr
+  withForeignPtr fptr $ \ptr ->
+    unsafeUseAsCStringLen key $ \(kptr, klen) -> do
+      let klen' = fromIntegral klen
+          outlen' = fromIntegral outlen
+      ret <- f ptr outlen' kptr klen'
+      if ret == 0
+      then return fptr
+      else error "initialization failure"
+
+updater :: Storable a
+        => UpdateFunc a
+        -> ByteString
+        -> ForeignPtr a
+        -> ForeignPtr a
+updater f d state = unsafePerformIO $ do
+  newState <- mallocForeignPtr
+  withForeignPtr newState $ \nsptr -> do
+    let (dfp, _, dlen) = toForeignPtr d
+        dlen' = fromIntegral dlen
+    withForeignPtr dfp $ \dptr ->
+      withForeignPtr state $ \sptr -> do
+        copyArray nsptr sptr 1
+        void $ f nsptr dptr dlen'
+  return newState
+
+finalizer :: Storable a
+          => FinalFunc a
+          -> Int
+          -> ForeignPtr a
+          -> ByteString
+finalizer f outlen state = unsafePerformIO $ do
+  newState <- mallocForeignPtr
+  withForeignPtr newState $ \nsptr ->
+    create outlen $ \optr ->
+      withForeignPtr state $ \sptr -> do
+        let outlen' = fromIntegral outlen
+        copyArray nsptr sptr 1
+        void $ f nsptr optr outlen'
+
+hasher :: HashFunc
+       -> Int
+       -> ByteString
+       -> ByteString
+       -> ByteString
+hasher h outlen key input =
+  unsafePerformIO . create outlen $ \out ->
+    unsafeUseAsCStringLen key $ \(kstr, klen) ->
+      unsafeUseAsCStringLen input $ \(istr, ilen) ->
+        let outlen' = fromIntegral outlen
+            ilen'   = fromIntegral ilen
+            klen'   = fromIntegral klen
+        in void $ h out istr kstr outlen' ilen' klen'
+{-# INLINE hasher #-}
diff --git a/tests/Imports.hs b/tests/Imports.hs
new file mode 100644
--- /dev/null
+++ b/tests/Imports.hs
@@ -0,0 +1,7 @@
+module Imports
+  (
+    module X
+  ) where
+
+import Test.Tasty as X
+import Test.Tasty.QuickCheck as X
diff --git a/tests/hlint.hs b/tests/hlint.hs
new file mode 100644
--- /dev/null
+++ b/tests/hlint.hs
@@ -0,0 +1,16 @@
+module Main where
+
+import Control.Monad
+import Language.Haskell.HLint
+import System.Environment
+import System.Exit
+
+main :: IO ()
+main = do
+  args <- getArgs
+  hints <- hlint $ [ "src"
+                   , "benchmarks"
+                   , "tests"
+                   , "--cpp-define=HLINT"
+                   ] `mappend` args
+  unless (null hints) exitFailure
diff --git a/tests/properties.hs b/tests/properties.hs
new file mode 100644
--- /dev/null
+++ b/tests/properties.hs
@@ -0,0 +1,103 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Main where
+
+import Data.ByteString (ByteString)
+import qualified Data.ByteString.Base16 as Hex
+import Prelude hiding (compare)
+
+import qualified Crypto.Hash.BLAKE2.BLAKE2b as B2b
+import qualified Crypto.Hash.BLAKE2.BLAKE2bp as B2bp
+import qualified Crypto.Hash.BLAKE2.BLAKE2s as B2s
+import qualified Crypto.Hash.BLAKE2.BLAKE2sp as B2sp
+
+import Imports
+
+key32 :: ByteString
+key32 = fst . Hex.decode $ "000102030405060708090a0b0c0d0e0f\
+                           \101112131415161718191a1b1c1d1e1f"
+
+key64 :: ByteString
+key64 = fst . Hex.decode $ "000102030405060708090a0b0c0d0e0f\
+                           \101112131415161718191a1b1c1d1e1f\
+                           \202122232425262728292a2b2c2d2e2f\
+                           \303132333435363738393a3b3c3d3e3f"
+
+compare :: (ByteString -> ByteString)
+        -> ByteString
+        -> ByteString
+        -> Property
+compare f input expectation = property $ result === expectation
+  where
+    result = Hex.encode . f . fst . Hex.decode $ input
+
+test0s :: Property
+test0s = compare (B2s.hash 32 key32) "" "48a8997da407876b3d79c0d92325ad3b89cbb754d86ab71aee047ad345fd2c49"
+
+test1s :: Property
+test1s = compare (B2s.hash 32 key32) "00" "40d15fee7c328830166ac3f918650f807e7e01e177258cdc0a39b11f598066f1"
+
+test2s :: Property
+test2s = compare (B2s.hash 32 key32) "0001" "6bb71300644cd3991b26ccd4d274acd1adeab8b1d7914546c1198bbe9fc9d803"
+
+test3s :: Property
+test3s = compare (B2s.hash 32 key32) "000102" "1d220dbe2ee134661fdf6d9e74b41704710556f2f6e5a091b227697445dbea6b"
+
+test0sp :: Property
+test0sp = compare (B2sp.hash 32 key32) "" "715cb13895aeb678f6124160bff21465b30f4f6874193fc851b4621043f09cc6"
+
+test1sp :: Property
+test1sp = compare (B2sp.hash 32 key32) "00" "40578ffa52bf51ae1866f4284d3a157fc1bcd36ac13cbdcb0377e4d0cd0b6603"
+
+test2sp :: Property
+test2sp = compare (B2sp.hash 32 key32) "0001" "67e3097545bad7e852d74d4eb548eca7c219c202a7d088db0efeac0eac304249"
+
+test3sp :: Property
+test3sp = compare (B2sp.hash 32 key32) "000102" "8dbcc0589a3d17296a7a58e2f1eff0e2aa4210b58d1f88b86d7ba5f29dd3b583"
+
+test0b :: Property
+test0b = compare (B2b.hash 64 key64) "" "10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e996e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568"
+
+test1b :: Property
+test1b = compare (B2b.hash 64 key64) "00" "961f6dd1e4dd30f63901690c512e78e4b45e4742ed197c3c5e45c549fd25f2e4187b0bc9fe30492b16b0d0bc4ef9b0f34c7003fac09a5ef1532e69430234cebd"
+
+test2b :: Property
+test2b = compare (B2b.hash 64 key64) "0001" "da2cfbe2d8409a0f38026113884f84b50156371ae304c4430173d08a99d9fb1b983164a3770706d537f49e0c916d9f32b95cc37a95b99d857436f0232c88a965"
+
+test3b :: Property
+test3b = compare (B2b.hash 64 key64) "000102" "33d0825dddf7ada99b0e7e307104ad07ca9cfd9692214f1561356315e784f3e5a17e364ae9dbb14cb2036df932b77f4b292761365fb328de7afdc6d8998f5fc1"
+
+test0bp :: Property
+test0bp = compare (B2bp.hash 64 key64) "" "9d9461073e4eb640a255357b839f394b838c6ff57c9b686a3f76107c1066728f3c9956bd785cbc3bf79dc2ab578c5a0c063b9d9c405848de1dbe821cd05c940a"
+
+test1bp :: Property
+test1bp = compare (B2bp.hash 64 key64) "00" "ff8e90a37b94623932c59f7559f26035029c376732cb14d41602001cbb73adb79293a2dbda5f60703025144d158e2735529596251c73c0345ca6fccb1fb1e97e"
+
+test2bp :: Property
+test2bp = compare (B2bp.hash 64 key64) "0001" "d6220ca195a0f356a4795e071cee1f5412ecd95d8a5e01d7c2b86750ca53d7f64c29cbb3d289c6f4ecc6c01e3ca9338971170388e3e40228479006d1bbebad51"
+
+test3bp :: Property
+test3bp = compare (B2bp.hash 64 key64) "000102" "30302c3fc999065d10dc982c8feef41bbb6642718f624af6e3eabea083e7fe785340db4b0897efff39cee1dc1eb737cd1eea0fe75384984e7d8f446faa683b80"
+
+main :: IO ()
+main = defaultMain $ testGroup "blake2"
+  [ testGroup "blake2s" [ testProperty "0" test0s
+                        , testProperty "1" test1s
+                        , testProperty "2" test2s
+                        , testProperty "3" test3s
+                        ]
+  , testGroup "blake2sp" [ testProperty "0" test0sp
+                         , testProperty "1" test1sp
+                         , testProperty "2" test2sp
+                         , testProperty "3" test3sp
+                         ]
+  , testGroup "blake2b" [ testProperty "0" test0b
+                        , testProperty "1" test1b
+                        , testProperty "2" test2b
+                        , testProperty "3" test3b
+                        ]
+  , testGroup "blake2bp" [ testProperty "0" test0bp
+                         , testProperty "1" test1bp
+                         , testProperty "2" test2bp
+                         , testProperty "3" test3bp
+                         ]
+  ]
