diff --git a/hwsl2.cabal b/hwsl2.cabal
--- a/hwsl2.cabal
+++ b/hwsl2.cabal
@@ -10,7 +10,7 @@
 -- PVP summary:      +-+------- breaking API changes
 --                   | | +----- non-breaking API additions
 --                   | | | +--- code changes with no API change
-version:             0.1.0.0
+version:             0.1.1.0
 
 -- A short (one-line) description of the package.
 synopsis:            Hashing with SL2
@@ -78,7 +78,7 @@
   hs-source-dirs:      src
 
   -- Options for foreign source files.
-  c-sources:           src/tillich-zemor.c
+  c-sources:           src/tillich-zemor.c src/tillich-zemor.h src/sl2-inl.h src/gf2p127-inl.h
   cc-options:          -msse2 -msse4.1 -mpclmul
   
   -- Base language which the package is written in.
diff --git a/src/Data/Hash/SL2.hs b/src/Data/Hash/SL2.hs
--- a/src/Data/Hash/SL2.hs
+++ b/src/Data/Hash/SL2.hs
@@ -20,7 +20,7 @@
 -- All operations in this package are implemented in a very efficient manner using SSE instructions.
 --
 
-module Data.Hash.SL2 (Hash, hash, (<+), (+>), parse) where
+module Data.Hash.SL2 (Hash, hash, (<+), (+>), (<|), (|>), parse) where
 
 import Prelude hiding (concat)
 
@@ -38,7 +38,10 @@
 
 import Data.Monoid
 import Data.Functor
+import Data.Foldable
 
+-- Foreign Imports
+
 foreign import ccall "tillich-zemor.h tz_hash_eq"
   tzHashEq :: Ptr () -> Ptr () -> IO CInt
 
@@ -60,9 +63,19 @@
 foreign import ccall "tillich-zemor.h tz_hash_unserialize"
   tzHashUnserialize :: Ptr () -> Ptr CChar -> IO ()
 
+-- Mutable Helpers
+
+append :: ByteString -> Ptr () -> IO ()
+append s p = unsafeUseAsCStringLen s $ \(s', len) -> tzHashAppend p s' (fromIntegral len)
+
+prepend :: ByteString -> Ptr () -> IO ()
+prepend s p = unsafeUseAsCStringLen s $ \(s', len) -> tzHashPrepend p s' (fromIntegral len)
+
 -- | Opaque representation of a 512 bit hash.
 newtype Hash = H (ForeignPtr ())
 
+-- Foreign Pointer Helpers
+
 tzHashSize = 64
 tzHashLen = 86
 
@@ -78,6 +91,8 @@
 withHashPtrCopy :: Hash -> (Ptr () -> IO a) -> IO (Hash, a)
 withHashPtrCopy h f = withHashPtr h $ \hp -> withHashPtrNew $ \hp' -> copyBytes hp' hp tzHashSize >> f hp'
 
+-- Instances
+
 instance Show Hash where
   show h = unsafePerformIO $ allocaBytes tzHashLen $ \p -> withHashPtr h (flip tzHashSerialize p) >> peekCStringLen (p, tzHashLen)
 
@@ -88,21 +103,35 @@
   mempty = fst $ unsafePerformIO $ withHashPtrNew tzHashUnit
   mappend a b = fst $ unsafePerformIO $ withHashPtrNew (withHashPtr2 a b . tzHashConcat)
 
+-- Interface
+
 -- | /O(n)/ Calculate the hash of the 'ByteString'. Alias for @('mempty' '<+')@.
 hash :: ByteString -> Hash
 hash = (<+) mempty
 
 -- | /O(n)/ Append the hash of the 'ByteString' to the existing 'Hash'.
 -- A significantly faster equivalent of @(flip ('<>') . 'hash')@.
+infixl 7 <+
 (<+) :: Hash -> ByteString -> Hash
-(<+) h s = fst $ unsafePerformIO $ unsafeUseAsCStringLen s $ \(s', len) ->
-  withHashPtrCopy h $ \hp -> tzHashAppend hp s' $ fromIntegral len
+(<+) h s = fst $ unsafePerformIO $ withHashPtrCopy h $ append s
 
 -- | /O(n)/ Prepend the hash of the 'ByteString' to the existing 'Hash'.
 -- A significantly faster equivalent of @(('<>') . 'hash')@.
+infixr 7 +>
 (+>) :: ByteString -> Hash -> Hash
-(+>) s h = fst $ unsafePerformIO $ unsafeUseAsCStringLen s $ \(s', len) ->
-  withHashPtrCopy h $ \hp -> tzHashPrepend hp s' $ fromIntegral len
+(+>) s h = fst $ unsafePerformIO $ withHashPtrCopy h $ prepend s
+
+-- | /O(n)/ Append the hash of the every 'ByteString' to the existing 'Hash', from left to right.
+-- A significantly faster equivalent of @('foldl' ('<+'))@.
+infixl 7 <|
+(<|) :: Foldable t => Hash -> t ByteString -> Hash
+(<|) h ss = fst $ unsafePerformIO $ withHashPtrCopy h $ \hp -> foldlM (\p s -> p <$ append s p) hp ss
+
+-- | /O(n)/ Prepend the hash of every 'ByteString' to the existing 'Hash', from right to left.
+-- A significantly faster equivalent of @('foldr' ('+>'))@.
+infixr 7 |>
+(|>) :: Foldable t => t ByteString -> Hash -> Hash
+(|>) ss h = fst $ unsafePerformIO $ withHashPtrCopy h $ \hp -> foldrM (\s p -> p <$ prepend s p) hp ss
 
 -- | /O(1)/ Parse the representation generated by 'show'.
 parse :: String -> Maybe Hash
diff --git a/src/gf2p127-inl.h b/src/gf2p127-inl.h
new file mode 100644
--- /dev/null
+++ b/src/gf2p127-inl.h
@@ -0,0 +1,140 @@
+#pragma once
+
+#include <stdio.h>
+#include <smmintrin.h>
+#include <wmmintrin.h>
+
+typedef __m128i gf2p127_t;
+
+static const inline
+_Bool gf2p127_eq(const gf2p127_t a, const gf2p127_t b) {
+  _Bool lo = _mm_extract_epi64(a, 0) == _mm_extract_epi64(b, 0);
+  _Bool hi = _mm_extract_epi64(a, 1) == _mm_extract_epi64(b, 1);
+  return lo && hi;
+}
+
+static const inline
+gf2p127_t gf2p127_zero() {
+  return _mm_setzero_si128();
+}
+
+static const inline
+gf2p127_t gf2p127_from_int(int a) {
+  return _mm_cvtsi32_si128(a);
+}
+
+static const inline
+gf2p127_t gf2p127_mul_bit(const gf2p127_t a, const _Bool bit) {
+  return _mm_slli_epi64(a, !bit * 64);
+}
+
+static const inline
+gf2p127_t gf2p127_add(const gf2p127_t a, const gf2p127_t b) {
+  return _mm_xor_si128(a, b);
+}
+
+static const inline
+gf2p127_t gf2p127_mul_00(const gf2p127_t a) {
+  return _mm_setzero_si128();
+}
+
+static const inline
+gf2p127_t gf2p127_mul_01(const gf2p127_t a) {
+  return a;
+}
+
+static const uint32_t x127[4] = {0, 0, 0, 1 << 31};
+
+static const inline
+gf2p127_t gf2p127_mul_10_left(const gf2p127_t a) {
+  // Shift lower and upper halves left by one bit,
+  // resembling a multiplication by two.
+  gf2p127_t sl = _mm_slli_epi64(a, 1);
+  // Check for a x^127 overflow, and add the polynom and carry bit.
+  gf2p127_t one = _mm_srli_epi64(_mm_alignr_epi8(a, sl, 8), 63);
+  gf2p127_t over = _mm_and_si128(sl, _mm_loadu_si128(x127));
+  gf2p127_t x127x63 = _mm_unpackhi_epi64(over, over);
+  return _mm_xor_si128(_mm_xor_si128(sl, one), x127x63);
+}
+
+static const inline
+gf2p127_t gf2p127_mul_10(const gf2p127_t a) {
+  // Shift lower and upper halves left by one bit,
+  // resembling a multiplication by two.
+  gf2p127_t sl = _mm_slli_epi64(a, 1);
+  // Check for a x^127 overflow, and add the polynom and carry bit.
+  gf2p127_t one = _mm_srli_epi64(_mm_alignr_epi8(a, sl, 8), 63);
+  gf2p127_t x127x63 = _mm_slli_epi64(_mm_unpacklo_epi64(one, one), 63);
+  return _mm_xor_si128(_mm_xor_si128(sl, one), x127x63);
+}
+
+static const inline
+gf2p127_t gf2p127_mul_11(const gf2p127_t a) {
+  gf2p127_t mul01 = gf2p127_mul_01(a);
+  gf2p127_t mul10 = gf2p127_mul_10(a);
+  return _mm_xor_si128(mul01, mul10);
+};
+
+static const inline
+gf2p127_t gf2p127_mul(const gf2p127_t a, const gf2p127_t b) {
+  gf2p127_t tmp, lo, hi;
+
+  // Multiplication (Karatsuba):
+  //   tmp <- (a0 + a1) * (b0 + b1)
+  tmp = _mm_xor_si128(_mm_unpacklo_epi64(a, b), _mm_unpackhi_epi64(a, b));
+  tmp = _mm_clmulepi64_si128(tmp, tmp, 0x10);
+  //   lo <- a0 * b0
+  lo = _mm_clmulepi64_si128(a, b, 0x00);
+  //   hi <- a1 * b1
+  hi = _mm_clmulepi64_si128(a, b, 0x11);
+  //   tmp <- (a0 + a1) * (b0 + b1) + a0 * b0 + a1 * b1
+  tmp = _mm_xor_si128(tmp, _mm_xor_si128(lo, hi));
+  //   lo <- a0 * b0 + low64[(a0 + a1) * (b0 + b1) + a0 * b0 + a1 * b1] * x^64
+  lo = _mm_xor_si128(lo, _mm_slli_si128(tmp, 8));
+  //   hi <- a1 * b1 + high64[(a0 + a1) * (b0 + b1) + a0 * b0 + a1 * b1]
+  hi = _mm_xor_si128(hi, _mm_srli_si128(tmp, 8));
+
+  // Reduction (modulo f(x) = x^127 + x^63 + 1)
+  //   tmp <- low64[hi] * x^64 + high64[lo]
+  tmp = _mm_alignr_epi8(hi, lo, 8);
+  tmp = _mm_xor_si128(tmp, hi);
+  hi = _mm_slli_epi64(hi, 1);
+  lo = _mm_xor_si128(lo, hi);
+  hi = _mm_unpackhi_epi64(hi, tmp);
+  lo = _mm_xor_si128(lo, hi);
+  tmp = _mm_srli_epi64(tmp, 63);
+  lo = _mm_xor_si128(lo, tmp);
+  hi = _mm_unpacklo_epi64(tmp, tmp);
+  lo = _mm_xor_si128(lo, _mm_slli_epi64(hi, 63));
+
+  return lo;
+}
+
+static inline
+char *gf2p127_show(char *buf, const gf2p127_t m) {
+  __uint128_t a = (__uint128_t)m;
+  unsigned int k;
+  int n;
+  char *str = buf;
+  if (a & 1) {
+    str += sprintf(str, "1");
+  } else {
+    str += sprintf(str, "0");
+  }
+  a >>= 1;
+  for (k = 1; k < 128; k++) {
+    if (a & 1) {
+      n = sprintf(str, " + 2^%i", k);
+      str += n;
+    }
+    a >>= 1;
+  }
+  return buf;
+}
+
+static inline
+char *gf2p127_hex(char *str, const gf2p127_t m) {
+  sprintf(str, "%.16llx%.16llx", _mm_extract_epi64(m, 1),
+                                 _mm_extract_epi64(m, 0));
+  return str;
+}
diff --git a/src/sl2-inl.h b/src/sl2-inl.h
new file mode 100644
--- /dev/null
+++ b/src/sl2-inl.h
@@ -0,0 +1,189 @@
+#pragma once
+
+#include "gf2p127-inl.h"
+
+typedef gf2p127_t sl2_t[2][2];
+
+static inline
+_Bool sl2_eq(sl2_t a, sl2_t b) {
+  return gf2p127_eq(a[0][0], b[0][0]) &&
+         gf2p127_eq(a[0][1], b[0][1]) &&
+         gf2p127_eq(a[1][0], b[1][0]) &&
+         gf2p127_eq(a[1][1], b[1][1]);
+}
+
+static inline
+void sl2_copy(sl2_t dst, sl2_t src) {
+  dst[0][0] = src[0][0];
+  dst[0][1] = src[0][1];
+  dst[1][0] = src[1][0];
+  dst[1][1] = src[1][1];
+}
+
+static inline
+void sl2_mul_bit_left(sl2_t b, int bit) {
+  // A: {00 = 10, 01 = 01, 10 = 01, 11 = 00}
+  // B: {00 = 10, 01 = 11, 10 = 01, 11 = 01}
+  gf2p127_t b00 = b[0][0];
+  gf2p127_t b10 = b[1][0];
+  gf2p127_t b01 = b[0][1];
+  gf2p127_t b11 = b[1][1];
+  b[0][0] = gf2p127_zero();
+  b[0][1] = gf2p127_zero();
+  b[1][0] = gf2p127_add(b00, b[bit][0]);
+  b[1][1] = gf2p127_add(b01, b[bit][1]);
+  b[0][0] = gf2p127_add(b10, gf2p127_mul_10_left(b[1][0]));
+  b[0][1] = gf2p127_add(b11, gf2p127_mul_10_left(b[1][1]));
+}
+
+static inline
+void sl2_mul_bits_left(sl2_t b, unsigned char byte) {
+  sl2_mul_bit_left(b, (byte >> 0) & 1);
+  sl2_mul_bit_left(b, (byte >> 1) & 1);
+  sl2_mul_bit_left(b, (byte >> 2) & 1);
+  sl2_mul_bit_left(b, (byte >> 3) & 1);
+  sl2_mul_bit_left(b, (byte >> 4) & 1);
+  sl2_mul_bit_left(b, (byte >> 5) & 1);
+  sl2_mul_bit_left(b, (byte >> 6) & 1);
+  sl2_mul_bit_left(b, (byte >> 7) & 1);
+}
+
+static inline
+void sl2_mul_bit_right(sl2_t a, int bit) {
+  // A: {00 = 10, 01 = 01, 10 = 01, 11 = 00}
+  // B: {00 = 10, 01 = 11, 10 = 01, 11 = 01}
+  gf2p127_t a00 = a[0][0];
+  gf2p127_t a10 = a[1][0];
+  gf2p127_t a01 = a[0][1];
+  gf2p127_t a11 = a[1][1];
+  a[0][1] = gf2p127_zero();
+  a[1][1] = gf2p127_zero();
+  a[0][0] = gf2p127_add(gf2p127_mul_10(a00), a01);
+  a[1][0] = gf2p127_add(gf2p127_mul_10(a10), a11);
+  a[0][1] = gf2p127_add(a00, a[0][!bit]);
+  a[1][1] = gf2p127_add(a10, a[1][!bit]);
+}
+
+static inline
+void sl2_mul_bits_right(sl2_t a, unsigned char byte) {
+  sl2_mul_bit_right(a, (byte >> 7) & 1);
+  sl2_mul_bit_right(a, (byte >> 6) & 1);
+  sl2_mul_bit_right(a, (byte >> 5) & 1);
+  sl2_mul_bit_right(a, (byte >> 4) & 1);
+  sl2_mul_bit_right(a, (byte >> 3) & 1);
+  sl2_mul_bit_right(a, (byte >> 2) & 1);
+  sl2_mul_bit_right(a, (byte >> 1) & 1);
+  sl2_mul_bit_right(a, (byte >> 0) & 1);
+}
+
+static inline
+void sl2_mul(sl2_t c, sl2_t a, sl2_t b) {
+  // Strassen algorithm
+  gf2p127_t m0, m1, m2, m3, m4, m5, m6;
+  m0 = gf2p127_mul(gf2p127_add(a[0][0], a[1][1]),
+                   gf2p127_add(b[0][0], b[1][1]));
+  m1 = gf2p127_mul(gf2p127_add(a[1][0], a[1][1]), b[0][0]);
+  m2 = gf2p127_mul(a[0][0], gf2p127_add(b[0][1], b[1][1]));
+  m3 = gf2p127_mul(a[1][1], gf2p127_add(b[1][0], b[0][0]));
+  m4 = gf2p127_mul(gf2p127_add(a[0][0], a[0][1]), b[1][1]);
+  m5 = gf2p127_mul(gf2p127_add(a[1][0], a[0][0]),
+                   gf2p127_add(b[0][0], b[0][1]));
+  m6 = gf2p127_mul(gf2p127_add(a[0][1], a[1][1]),
+                   gf2p127_add(b[1][0], b[1][1]));
+  c[0][0] = gf2p127_add(gf2p127_add(m0, m3), gf2p127_add(m4, m6));
+  c[0][1] = gf2p127_add(m2, m4);
+  c[1][0] = gf2p127_add(m1, m3);
+  c[1][1] = gf2p127_add(gf2p127_add(m0, m1), gf2p127_add(m2, m5));
+}
+
+static inline
+void sl2_mul_byte_left(sl2_t b, unsigned char byte, sl2_t m[256]) {
+  sl2_mul(m[byte], b, b);
+}
+
+static inline
+void sl2_mul_byte_right(sl2_t a, unsigned char byte, sl2_t m[256]) {
+  sl2_mul(a, m[byte], a);
+}
+
+static inline
+void sl2_init(sl2_t m[2]) {
+  m[0][0][0] = gf2p127_from_int(2);
+  m[0][0][1] = gf2p127_from_int(1);
+  m[0][1][0] = gf2p127_from_int(1);
+  m[0][1][1] = gf2p127_from_int(0);
+  m[1][0][0] = gf2p127_from_int(2);
+  m[1][0][1] = gf2p127_from_int(3);
+  m[1][1][0] = gf2p127_from_int(1);
+  m[1][1][1] = gf2p127_from_int(1);
+}
+
+static inline
+void sl2_unit(sl2_t a) {
+  a[0][0] = gf2p127_from_int(1);
+  a[0][1] = gf2p127_from_int(0);
+  a[1][0] = gf2p127_from_int(0);
+  a[1][1] = gf2p127_from_int(1);
+}
+
+static inline
+char *sl2_hex(char *buf, sl2_t a) {
+  gf2p127_hex(&buf[0],  a[0][0]);
+  gf2p127_hex(&buf[32], a[0][1]);
+  gf2p127_hex(&buf[64], a[1][0]);
+  gf2p127_hex(&buf[96], a[1][1]);
+  return buf;
+}
+
+static const unsigned char b64[64] =
+  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+
+static const unsigned char unb64[256] = {
+  ['A'] =  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12,
+          13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+  ['a'] = 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
+          39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
+  ['0'] = 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
+  ['-'] = 62, ['_'] = 63
+};
+
+static inline
+void sl2_serialize(sl2_t m, unsigned char buf[86]) {
+
+  int i, j;
+  unsigned char a, b, c, *data = (unsigned char *)m;
+
+  for (i = j = 0; i <= 64 - 3; i += 3, j += 4) {
+    a = data[i + 0];
+    b = data[i + 1];
+    c = data[i + 2];
+    buf[j + 0] = b64[a >> 2];
+    buf[j + 1] = b64[((0x03 & a) << 4) + (b >> 4)];
+    buf[j + 2] = b64[((0x0f & b) << 2) + (c >> 6)];
+    buf[j + 3] = b64[0x3f & c];
+  }
+
+  buf[84] = b64[data[i] >> 2];
+  buf[85] = b64[(0x3 & data[i]) << 4];
+
+}
+
+static inline
+void sl2_unserialize(sl2_t m, unsigned char buf[86]) {
+
+  int i, j;
+  unsigned char a, b, c, d, *data = (unsigned char *)m;
+
+  for (i = j = 0; i <= 86 - 4; i += 4, j += 3) {
+    a = unb64[buf[i + 0]];
+    b = unb64[buf[i + 1]];
+    c = unb64[buf[i + 2]];
+    d = unb64[buf[i + 3]];
+    data[j + 0] = (a << 2) | (b >> 4);
+    data[j + 1] = (b << 4) | (c >> 2);
+    data[j + 2] = (c << 6) | (d);
+  }
+
+  data[63] = (unb64[buf[i]] << 2) | (unb64[buf[i + 1]] >> 4);
+
+}
diff --git a/src/tillich-zemor.h b/src/tillich-zemor.h
new file mode 100644
--- /dev/null
+++ b/src/tillich-zemor.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <stddef.h>
+
+#define TZ_HASH_SIZE 64
+typedef struct tz_hash_struct *tz_hash_t;
+
+void tz_hash_copy(tz_hash_t dst, tz_hash_t src);
+
+int tz_hash_eq(tz_hash_t a, tz_hash_t b);
+
+void tz_hash_unit(tz_hash_t);
+
+void tz_hash_append(tz_hash_t h, unsigned char *buf, size_t n);
+void tz_hash_prepend(tz_hash_t h, unsigned char *buf, size_t n);
+
+void tz_hash_concat(tz_hash_t c, tz_hash_t a, tz_hash_t b);
+
+void tz_hash_serialize(tz_hash_t h, unsigned char str[86]);
+void tz_hash_unserialize(tz_hash_t h, unsigned char str[86]);
