diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,17 @@
+# 1.0.0.0
+
+* Merged omnibus PR doing a variety of things in ([#10](https://github.com/haskell/base16-bytestring/pull/10)):
+  - Improves performance by 3-4x for encode, 4-5x for decode.
+  - The `decode` signature returning the tuple and actually returns an error message with offset. The signature will now be `ByteString -> Either String ByteString`.
+  - Actually tests using the test vectors defined in the RFC, and uses property tests to ensure invariants hold.
+  - Adds lenient decoders to the API
+  - Adds `-XTrustworthy` annotations to the relevant exposed modules
+  - Rewrites the haddocks to be more up to date and fancy-styled.
+  - Adds benchmarks to the `.cabal` file so they can be run at toplevel, and make them better.
+  - Bumps the Cabal version to 1.12
+
+Because of the breadth of this change, we are calling this a new epoch for the `base16-bytestring` library. Hence, the version `1.0.0.0`.
+
 # 0.1.1.7
 
 * Fix some bugs in lazy decoding
diff --git a/Data/ByteString/Base16.hs b/Data/ByteString/Base16.hs
--- a/Data/ByteString/Base16.hs
+++ b/Data/ByteString/Base16.hs
@@ -1,144 +1,112 @@
-{-# LANGUAGE BangPatterns, MagicHash #-}
-
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE MagicHash #-}
+#if __GLASGOW_HASKELL__ >= 702
+{-# LANGUAGE Trustworthy #-}
+#endif
 -- |
 -- Module      : Data.ByteString.Base16
 -- Copyright   : (c) 2011 MailRank, Inc.
 --
 -- License     : BSD
--- Maintainer  : bos@serpentine.com
--- Stability   : experimental
--- Portability : GHC
+-- Maintainer  : Herbert Valerio Riedel <hvr@gnu.org>,
+--               Mikhail Glushenkov <mikhail.glushenkov@gmail.com>,
+--               Emily Pillmore <emilypi@cohomolo.gy>
+-- Stability   : stable
+-- Portability : non-portable
 --
--- Fast and efficient encoding and decoding of base16-encoded strings.
-
+-- RFC 4648-compliant Base16 (Hexadecimal) encoding for 'ByteString' values.
+-- For a complete Base16 encoding specification, please see <https://tools.ietf.org/html/rfc4648#section-8 RFC-4648 section 8>.
+--
 module Data.ByteString.Base16
-    (
-      encode
-    , decode
-    ) where
+( encode
+, decode
+, decodeLenient
+) where
 
-import Data.ByteString.Char8 (empty)
-import Data.ByteString.Internal (ByteString(..), createAndTrim', unsafeCreate)
-import Data.Bits (shiftL)
-import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)
-import Foreign.Ptr (Ptr, minusPtr, plusPtr)
-import Foreign.Storable (peek, poke)
-import System.IO.Unsafe (unsafePerformIO)
-import GHC.Prim
-import GHC.Types
-import GHC.Word
+import Data.ByteString (empty)
+import Data.ByteString.Base16.Internal
+import Data.ByteString.Internal
 
--- | Encode a string into base16 form.  The result will always be a
--- multiple of 2 bytes in length.
+import Foreign.ForeignPtr
+import Foreign.Ptr
+
+import GHC.ForeignPtr
+#if __GLASGOW_HASKELL__ >= 702
+import System.IO.Unsafe (unsafeDupablePerformIO)
+#else
+import GHC.IO (unsafeDupablePerformIO)
+#endif
+
+
+-- | Encode a 'ByteString' value in base16 (i.e. hexadecimal).
+-- Encoded values will always have a length that is a multiple of 2.
 --
--- Example:
+-- === __Examples__:
 --
 -- > encode "foo"  == "666f6f"
+--
 encode :: ByteString -> ByteString
 encode (PS sfp soff slen)
     | slen > maxBound `div` 2 =
-        error "Data.ByteString.Base16.encode: input too long"
-    | otherwise = unsafeCreate (slen*2) $ \dptr ->
-                    withForeignPtr sfp $ \sptr ->
-                      enc (sptr `plusPtr` soff) dptr
- where
-  enc sptr = go sptr where
-    e = sptr `plusPtr` slen
-    go s d | s == e = return ()
-           | otherwise = do
-      x <- peek8 s
-      poke d (tlookup tableHi x)
-      poke (d `plusPtr` 1) (tlookup tableLo x)
-      go (s `plusPtr` 1) (d `plusPtr` 2)
-    tlookup :: Addr# -> Int -> Word8
-    tlookup table (I# index) = W8# (indexWord8OffAddr# table index)
-    !tableLo =
-      "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x61\x62\x63\x64\x65\x66\
-      \\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x61\x62\x63\x64\x65\x66\
-      \\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x61\x62\x63\x64\x65\x66\
-      \\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x61\x62\x63\x64\x65\x66\
-      \\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x61\x62\x63\x64\x65\x66\
-      \\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x61\x62\x63\x64\x65\x66\
-      \\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x61\x62\x63\x64\x65\x66\
-      \\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x61\x62\x63\x64\x65\x66\
-      \\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x61\x62\x63\x64\x65\x66\
-      \\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x61\x62\x63\x64\x65\x66\
-      \\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x61\x62\x63\x64\x65\x66\
-      \\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x61\x62\x63\x64\x65\x66\
-      \\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x61\x62\x63\x64\x65\x66\
-      \\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x61\x62\x63\x64\x65\x66\
-      \\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x61\x62\x63\x64\x65\x66\
-      \\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x61\x62\x63\x64\x65\x66"#
-    !tableHi =
-      "\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\
-      \\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\
-      \\x32\x32\x32\x32\x32\x32\x32\x32\x32\x32\x32\x32\x32\x32\x32\x32\
-      \\x33\x33\x33\x33\x33\x33\x33\x33\x33\x33\x33\x33\x33\x33\x33\x33\
-      \\x34\x34\x34\x34\x34\x34\x34\x34\x34\x34\x34\x34\x34\x34\x34\x34\
-      \\x35\x35\x35\x35\x35\x35\x35\x35\x35\x35\x35\x35\x35\x35\x35\x35\
-      \\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\
-      \\x37\x37\x37\x37\x37\x37\x37\x37\x37\x37\x37\x37\x37\x37\x37\x37\
-      \\x38\x38\x38\x38\x38\x38\x38\x38\x38\x38\x38\x38\x38\x38\x38\x38\
-      \\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\
-      \\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\
-      \\x62\x62\x62\x62\x62\x62\x62\x62\x62\x62\x62\x62\x62\x62\x62\x62\
-      \\x63\x63\x63\x63\x63\x63\x63\x63\x63\x63\x63\x63\x63\x63\x63\x63\
-      \\x64\x64\x64\x64\x64\x64\x64\x64\x64\x64\x64\x64\x64\x64\x64\x64\
-      \\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\
-      \\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66"#
+      error "Data.ByteString.Base16.encode: input too long"
+    | otherwise = unsafeCreate (slen * 2) $ \dptr ->
+        withForeignPtr sfp $ \sptr ->
+          encodeLoop dptr
+          (sptr `plusPtr` soff)
+          (sptr `plusPtr` (soff + slen))
 
--- | Decode a string from base16 form. The first element of the
--- returned tuple contains the decoded data. The second element starts
--- at the first invalid base16 sequence in the original string.
+-- | Decode a base16-encoded 'ByteString' value.
+-- If errors are encountered during the decoding process,
+-- then an error message and character offset will be returned in
+-- the @Left@ clause of the coproduct.
 --
--- Examples:
+-- === __Examples__:
 --
--- > decode "666f6f"  == ("foo", "")
--- > decode "66quux"  == ("f", "quux")
--- > decode "666quux" == ("f", "6quux")
-decode :: ByteString -> (ByteString, ByteString)
-decode (PS sfp soff slen) =
-  unsafePerformIO . createAndTrim' (slen `div` 2) $ \dptr ->
-      withForeignPtr sfp $ \sptr ->
-        dec (sptr `plusPtr` soff) dptr
- where
-  dec sptr = go sptr where
-    e = sptr `plusPtr` if odd slen then slen - 1 else slen
-    go s d | s == e = let len = e `minusPtr` sptr
-                      in return (0, len `div` 2, ps sfp (soff+len) (slen-len))
-           | otherwise = do
-      hi <- hex `fmap` peek8 s
-      lo <- hex `fmap` peek8 (s `plusPtr` 1)
-      if lo == 0xff || hi == 0xff
-        then let len = s `minusPtr` sptr
-             in return (0, len `div` 2, ps sfp (soff+len) (slen-len))
-        else do
-          poke d . fromIntegral $ lo + (hi `shiftL` 4)
-          go (s `plusPtr` 2) (d `plusPtr` 1)
-
-    hex (I# index) = W8# (indexWord8OffAddr# table index)
-    !table =
-        "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
-        \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
-        \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
-        \\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\xff\xff\xff\xff\xff\xff\
-        \\xff\x0a\x0b\x0c\x0d\x0e\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\
-        \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
-        \\xff\x0a\x0b\x0c\x0d\x0e\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\
-        \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
-        \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
-        \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
-        \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
-        \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
-        \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
-        \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
-        \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
-        \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"#
-
-peek8 :: Ptr Word8 -> IO Int
-peek8 p = fromIntegral `fmap` peek p
+-- > decode "666f6f"  == Right "foo"
+-- > decode "66quux"  == Left "invalid character at offset: 2"
+-- > decode "666quux" == Left "invalid character at offset: 3"
+--
+-- @since 1.0.0.0
+--
+decode :: ByteString -> Either String ByteString
+decode (PS sfp soff slen)
+    | slen == 0 = Right empty
+    | r /= 0 = Left "invalid bytestring size"
+    | otherwise = unsafeDupablePerformIO $ do
+      dfp <- mallocPlainForeignPtrBytes q
+      withForeignPtr dfp $ \dptr ->
+        withForeignPtr sfp $ \sptr ->
+          decodeLoop dfp dptr
+          (plusPtr sptr soff)
+          (plusPtr sptr (soff + slen))
+  where
+    !q = slen `quot` 2
+    !r = slen `rem` 2
 
-ps :: ForeignPtr Word8 -> Int -> Int -> ByteString
-ps fp off len
-    | len <= 0 = empty
-    | otherwise = PS fp off len
+-- | Decode a Base16-encoded 'ByteString' value leniently, using a
+-- strategy that never fails.
+--
+-- /N.B./: this is not RFC 4648-compliant
+--
+-- === __Examples__:
+--
+-- > decodeLenient "666f6f"  == "foo"
+-- > decodeLenient "66quuxx" == "f"
+-- > decodeLenient "666quux" == "f"
+-- > decodeLenient "666fquu" -- "fo"
+--
+-- @since 1.0.0.0
+--
+decodeLenient :: ByteString -> ByteString
+decodeLenient (PS !sfp !soff !slen)
+    | slen == 0 = empty
+    | otherwise = unsafeDupablePerformIO $ do
+      dfp <- mallocPlainForeignPtrBytes (q * 2)
+      withForeignPtr dfp $ \dptr ->
+        withForeignPtr sfp $ \sptr ->
+          lenientLoop dfp dptr
+          (plusPtr sptr soff)
+          (plusPtr sptr (soff + slen))
+  where
+    !q = slen `quot` 2
diff --git a/Data/ByteString/Base16/Internal.hs b/Data/ByteString/Base16/Internal.hs
new file mode 100644
--- /dev/null
+++ b/Data/ByteString/Base16/Internal.hs
@@ -0,0 +1,158 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE MagicHash #-}
+module Data.ByteString.Base16.Internal
+( -- * worker loops
+  encodeLoop
+, decodeLoop
+, lenientLoop
+  -- * utils
+, c2w
+, aix
+, reChunk
+, unsafeShiftR
+) where
+
+
+import Data.Bits ((.&.), (.|.))
+import qualified Data.ByteString as B
+import Data.ByteString.Internal (ByteString(..))
+import Data.Char (ord)
+
+import Foreign.ForeignPtr
+import Foreign.Ptr
+import Foreign.Storable
+
+import GHC.Word
+import GHC.Exts
+  (Int(I#), Addr#, indexWord8OffAddr#, word2Int#, uncheckedShiftRL#)
+
+
+-- ------------------------------------------------------------------ --
+-- Loops
+
+encodeLoop
+    :: Ptr Word8
+    -> Ptr Word8
+    -> Ptr Word8
+    -> IO ()
+encodeLoop !dptr !sptr !end = go dptr sptr
+  where
+    !hex = "0123456789abcdef"#
+
+    go !dst !src
+      | src == end = return ()
+      | otherwise = do
+        !t <- peek src
+
+        poke dst (aix (unsafeShiftR t 4) hex)
+        poke (plusPtr dst 1) (aix (t .&. 0x0f) hex)
+
+        go (plusPtr dst 2) (plusPtr src 1)
+{-# INLINE encodeLoop #-}
+
+decodeLoop
+  :: ForeignPtr Word8
+  -> Ptr Word8
+  -> Ptr Word8
+  -> Ptr Word8
+  -> IO (Either String ByteString)
+decodeLoop !dfp !dptr !sptr !end = go dptr sptr
+  where
+    err !src = return . Left
+      $ "invalid character at offset: "
+      ++ show (src `minusPtr` sptr)
+
+    !lo = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\xff\xff\xff\xff\xff\xff\xff\x0a\x0b\x0c\x0d\x0e\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x0a\x0b\x0c\x0d\x0e\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"#
+
+    !hi = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x10\x20\x30\x40\x50\x60\x70\x80\x90\xff\xff\xff\xff\xff\xff\xff\xa0\xb0\xc0\xd0\xe0\xf0\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xa0\xb0\xc0\xd0\xe0\xf0\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"#
+
+    go !dst !src
+      | src == end = return (Right (PS dfp 0 (dst `minusPtr` dptr)))
+      | otherwise = do
+        !x <- peek src
+        !y <- peek (plusPtr src 1)
+
+        let !a = aix x hi
+            !b = aix y lo
+
+        if a == 0xff
+        then err src
+        else
+          if b == 0xff
+          then err (plusPtr src 1)
+          else do
+            poke dst (a .|. b)
+            go (plusPtr dst 1) (plusPtr src 2)
+{-# INLINE decodeLoop #-}
+
+lenientLoop
+  :: ForeignPtr Word8
+  -> Ptr Word8
+  -> Ptr Word8
+  -> Ptr Word8
+  -> IO ByteString
+lenientLoop !dfp !dptr !sptr !end = goHi dptr sptr 0
+  where
+    !lo = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\xff\xff\xff\xff\xff\xff\xff\x0a\x0b\x0c\x0d\x0e\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x0a\x0b\x0c\x0d\x0e\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"#
+
+    !hi = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x10\x20\x30\x40\x50\x60\x70\x80\x90\xff\xff\xff\xff\xff\xff\xff\xa0\xb0\xc0\xd0\xe0\xf0\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xa0\xb0\xc0\xd0\xe0\xf0\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"#
+
+    goHi !dst !src !n
+      | src == end = return (PS dfp 0 n)
+      | otherwise = do
+        !x <- peek src
+
+        let !a = aix x hi
+
+        if a == 0xff
+        then goHi dst (plusPtr src 1) n
+        else goLo dst (plusPtr src 1) a n
+
+    goLo !dst !src !a !n
+      | src == end = return (PS dfp 0 n)
+      | otherwise = do
+        !y <- peek src
+
+        let !b = aix y lo
+
+        if b == 0xff
+        then goLo dst (plusPtr src 1) a n
+        else do
+          poke dst (a .|. b)
+          goHi (plusPtr dst 1) (plusPtr src 1) (n + 1)
+{-# INLINE lenientLoop #-}
+
+
+-- ------------------------------------------------------------------ --
+-- Utils
+
+aix :: Word8 -> Addr# -> Word8
+aix (W8# w) table = W8# (indexWord8OffAddr# table (word2Int# w))
+{-# INLINE aix #-}
+
+-- | Form a list of chunks, and rechunk the list of bytestrings
+-- into length multiples of 2
+--
+reChunk :: [ByteString] -> [ByteString]
+reChunk [] = []
+reChunk (c:cs) = case B.length c `divMod` 2 of
+    (_, 0) -> c : reChunk cs
+    (n, _) -> case B.splitAt (n * 2) c of
+      ~(m, q) -> m : cont_ q cs
+  where
+    cont_ q [] = [q]
+    cont_ q (a:as) = case B.splitAt 1 a of
+      ~(x, y) -> let q' = B.append q x
+        in if B.length q' == 2
+          then
+            let as' = if B.null y then as else y:as
+            in q' : reChunk as'
+          else cont_ q' as
+
+unsafeShiftR :: Word8 -> Int -> Word8
+unsafeShiftR (W8# x#) (I# i#) = W8# (x# `uncheckedShiftRL#` i#)
+{-# INLINE unsafeShiftR #-}
+
+c2w :: Char -> Word8
+c2w = fromIntegral . ord
+{-# INLINE c2w #-}
diff --git a/Data/ByteString/Base16/Lazy.hs b/Data/ByteString/Base16/Lazy.hs
--- a/Data/ByteString/Base16/Lazy.hs
+++ b/Data/ByteString/Base16/Lazy.hs
@@ -1,68 +1,85 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE OverloadedStrings #-}
-
+#if __GLASGOW_HASKELL__ >= 702
+{-# LANGUAGE Trustworthy #-}
+#endif
 -- |
 -- Module      : Data.ByteString.Base16.Lazy
 -- Copyright   : (c) 2011 MailRank, Inc.
 --
 -- License     : BSD
--- Maintainer  : bos@serpentine.com
--- Stability   : experimental
--- Portability : GHC
+-- Maintainer  : Herbert Valerio Riedel <hvr@gnu.org>,
+--               Mikhail Glushenkov <mikhail.glushenkov@gmail.com>,
+--               Emily Pillmore <emilypi@cohomolo.gy>
+-- Stability   : stable
+-- Portability : non-portable
 --
--- Fast and efficient encoding and decoding of base16-encoded strings.
-
+-- RFC 4648-compliant Base16 (Hexadecimal) encoding for lazy 'ByteString' values.
+-- For a complete Base16 encoding specification, please see <https://tools.ietf.org/html/rfc4648#section-8 RFC-4648 section 8>.
+--
 module Data.ByteString.Base16.Lazy
-    (
-      encode
-    , decode
-    ) where
+( encode
+, decode
+, decodeLenient
+) where
 
-import Data.Word (Word8)
+
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Lazy as LBS
 import qualified Data.ByteString.Base16 as B16
-import qualified Data.ByteString as B
-import qualified Data.ByteString.Unsafe as B
-import qualified Data.ByteString.Lazy as BL
-import Data.ByteString.Lazy.Internal
+import Data.ByteString.Base16.Internal
+import Data.ByteString.Lazy.Internal (ByteString(..))
 
--- | Encode a string into base16 form.  The result will always be a
--- multiple of 2 bytes in length.
+-- | Encode a 'ByteString' value in base16 (i.e. hexadecimal).
+-- Encoded values will always have a length that is a multiple of 2.
 --
--- Example:
 --
+-- === __Examples__:
+--
 -- > encode "foo"  == "666f6f"
+--
 encode :: ByteString -> ByteString
+encode Empty = Empty
 encode (Chunk c cs) = Chunk (B16.encode c) (encode cs)
-encode Empty        = Empty
 
--- | Decode a string from base16 form. The first element of the
--- returned tuple contains the decoded data. The second element starts
--- at the first invalid base16 sequence in the original string.
+-- | Decode a base16-encoded 'ByteString' value.
+-- If errors are encountered during the decoding process,
+-- then an error message and character offset will be returned in
+-- the @Left@ clause of the coproduct.
 --
--- This function operates as lazily as possible over the input chunks.
+-- === __Examples__:
 --
--- Examples:
+-- > decode "666f6f" == Right "foo"
+-- > decode "66quux" == Left "invalid character at offset: 2"
+-- > decode "666quu" == Left "invalid character at offset: 3"
 --
--- > decode "666f6f"  == ("foo", "")
--- > decode "66quux"  == ("f", "quux")
--- > decode "666quux" == ("f", "6quux")
-decode :: ByteString -> (ByteString, ByteString)
-decode = go Nothing
+-- @since 1.0.0.0
+--
+decode :: ByteString -> Either String ByteString
+decode = f . B16.decode . BS.concat . LBS.toChunks
   where
-      go :: Maybe Word8 -> ByteString -> (ByteString, ByteString)
-      go Nothing Empty = (Empty, Empty)
-      go (Just w) Empty = (Empty, BL.singleton w)
-      go (Just w) (Chunk c z) =
-           go Nothing (chunk (B.pack [w, B.unsafeHead c]) (chunk (B.unsafeTail c) z))
-      go Nothing (Chunk c z)
-           | len == 0 =
-                 let ~(res,tail') = go Nothing z
-                 in (chunk h res, tail')
-           | len == 1 && isHex (B.unsafeHead t) =
-                 let ~(res,tail') = go (Just (B.unsafeHead t)) z
-                 in (chunk h res, tail')
-           | otherwise = (chunk h Empty, chunk t z)
-            where (h,t) = B16.decode c
-                  len = B.length t
+    f (Left t) = Left t
+    f (Right bs') = Right (LBS.fromChunks [bs'])
 
-isHex :: Word8 -> Bool
-isHex w = (w >= 48 && w <= 57) || (w >= 97 && w <= 102) || (w >= 65 && w <= 70)
+-- | Decode a Base16-encoded 'ByteString' value leniently, using a
+-- strategy that never fails.
+--
+-- /N.B./: this is not RFC 4648-compliant
+--
+-- === __Examples__:
+--
+-- > decodeLenient "666f6f" == "foo"
+-- > decodeLenient "66quux" == "f"
+-- > decodeLenient "666quu" == "f"
+-- > decodeLenient "666fqu" == "fo"
+--
+-- @since 1.0.0.0
+--
+decodeLenient :: ByteString -> ByteString
+decodeLenient = LBS.fromChunks
+    . fmap B16.decodeLenient
+    . reChunk
+    . fmap (BS.filter (flip BS.elem extendedHex))
+    . LBS.toChunks
+  where
+    extendedHex = BS.pack (fmap c2w "0123456789abcdefABCDEF")
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -5,18 +5,6 @@
 This package provides a Haskell library for working with base16-encoded
 data quickly and efficiently, using the `ByteString` type.
 
-
-# Performance
-
-This library is written in pure Haskell, and it's fast:
-
-* 250 MB/sec encoding
-
-* 200 MB/sec strict decoding (per RFC 4648)
-
-* 100 MB/sec lenient decoding
-
-
 # Get involved!
 
 Please report bugs via the
@@ -29,6 +17,7 @@
 
 # Authors
 
-This library is written by [Bryan O'Sullivan](mailto:bos@serpentine.com). It
-is maintained by [Emily Pillmore](mailto:emilypi@cohomolo.gy), [Herbert Valerio Riedel](mailto:hvr@gnu.org) and [Mikhail
+This library is written by [Bryan O'Sullivan](mailto:bos@serpentine.com). 
+
+It is currently maintained by [Emily Pillmore](mailto:emilypi@cohomolo.gy), [Herbert Valerio Riedel](mailto:hvr@gnu.org) and [Mikhail
 Glushenkov](mailto:mikhail.glushenkov@gmail.com).
diff --git a/base16-bytestring.cabal b/base16-bytestring.cabal
--- a/base16-bytestring.cabal
+++ b/base16-bytestring.cabal
@@ -1,53 +1,93 @@
-cabal-version:       1.12
-name:                base16-bytestring
-version:             0.1.1.7
-synopsis:            Fast base16 (hex) encoding and decoding for ByteStrings
-description:         This package provides support for encoding and decoding binary data according
-                     to @base16@ (see also <https://tools.ietf.org/html/rfc4648 RFC 4648>) for
-                     strict (see "Data.ByteString.Base16") and lazy @ByteString@s (see "Data.ByteString.Base16.Lazy").
-                     .
-                     See also the <https://hackage.haskell.org/package/base-encoding base-encoding> package which
-                     provides an uniform API providing conversion paths between more binary and textual types.
-homepage:            http://github.com/haskell/base16-bytestring
-bug-reports:         http://github.com/haskell/base16-bytestring/issues
-license:             BSD3
-license-file:        LICENSE
-copyright:           Copyright 2011 MailRank, Inc.;
-                     Copyright 2010-2020 Bryan O'Sullivan et al.
-author:              Bryan O'Sullivan <bos@serpentine.com>
-maintainer:          Herbert Valerio Riedel <hvr@gnu.org>,
-                     Mikhail Glushenkov <mikhail.glushenkov@gmail.com>,
-                     Emily Pillmore <emilypi@cohomolo.gy>
-category:            Data
-build-type:          Simple
-extra-source-files:  README.md CHANGELOG.md
-tested-with:         GHC==8.10.1, GHC==8.8.3, GHC==8.6.5,
-                     GHC==8.4.4,  GHC==8.2.2, GHC==8.0.2,
-                     GHC==7.10.3, GHC==7.8.4, GHC==7.6.3,
-                     GHC==7.4.2,  GHC==7.2.2, GHC==7.0.4
+cabal-version:      1.12
+name:               base16-bytestring
+version:            1.0.0.0
+synopsis:           RFC 4648-compliant Base16 encodings for ByteStrings
+description:
+  This package provides support for encoding and decoding binary data according
+  to @base16@ (see also <https://tools.ietf.org/html/rfc4648 RFC 4648>) for
+  strict (see "Data.ByteString.Base16") and lazy @ByteString@s (see "Data.ByteString.Base16.Lazy").
+  .
+  See the <https://hackage.haskell.org/package/base16 base16> package which provides superior encoding and decoding performance as well as support for lazy, short, and strict variants of 'Text' and 'ByteString' values. Additionally, see the <https://hackage.haskell.org/package/base-encoding base-encoding> package which
+  provides an uniform API providing conversion paths between more binary and textual types.
 
+homepage:           http://github.com/haskell/base16-bytestring
+bug-reports:        http://github.com/haskell/base16-bytestring/issues
+license:            BSD3
+license-file:       LICENSE
+copyright:
+  Copyright 2011 MailRank, Inc.;
+  Copyright 2010-2020 Bryan O'Sullivan et al.
+
+author:             Bryan O'Sullivan <bos@serpentine.com>
+maintainer:
+  Herbert Valerio Riedel <hvr@gnu.org>,
+  Mikhail Glushenkov <mikhail.glushenkov@gmail.com>,
+  Emily Pillmore <emilypi@cohomolo.gy>
+
+category:           Data
+build-type:         Simple
+extra-source-files:
+  README.md
+  CHANGELOG.md
+
+tested-with:
+  GHC ==7.0.4
+   || ==7.2.2
+   || ==7.4.2
+   || ==7.6.3
+   || ==7.8.4
+   || ==7.10.3
+   || ==8.0.2
+   || ==8.2.2
+   || ==8.4.4
+   || ==8.6.5
+   || ==8.8.3
+   || ==8.8.4
+   || ==8.10.1
+   || ==8.10.2
+
+source-repository head
+  type:     git
+  location: http://github.com/haskell/base16-bytestring
+
 library
+  other-modules:    Data.ByteString.Base16.Internal
   exposed-modules:
     Data.ByteString.Base16
     Data.ByteString.Base16.Lazy
 
   build-depends:
-    base == 4.*,
-    bytestring >= 0.9,
-    ghc-prim
+      base        >=4   && <5
+    , bytestring  >=0.9 && <0.11
 
-  ghc-options: -Wall -funbox-strict-fields
+  ghc-options:      -Wall -funbox-strict-fields
   default-language: Haskell2010
 
-source-repository head
-  type:     git
-  location: http://github.com/haskell/base16-bytestring
-
 test-suite test
-  type: exitcode-stdio-1.0
-  hs-source-dirs: tests
-  main-is: Tests.hs
+  type:             exitcode-stdio-1.0
+  hs-source-dirs:   tests
+  main-is:          Tests.hs
+  build-depends:
+      base
+    , base16-bytestring
+    , bytestring
+    , HUnit
+    , QuickCheck
+    , test-framework
+    , test-framework-hunit
+    , test-framework-quickcheck2
+
   default-language: Haskell2010
-  build-depends: base
-               , base16-bytestring
-               , bytestring
+
+benchmark bench
+  type:             exitcode-stdio-1.0
+  hs-source-dirs:   benchmarks
+  main-is:          Benchmarks.hs
+  build-depends:
+      base               >=4 && <5
+    , base16-bytestring
+    , bytestring
+    , criterion
+    , deepseq
+
+  default-language: Haskell2010
diff --git a/benchmarks/Benchmarks.hs b/benchmarks/Benchmarks.hs
new file mode 100644
--- /dev/null
+++ b/benchmarks/Benchmarks.hs
@@ -0,0 +1,49 @@
+module Main
+( main
+) where
+
+
+import Criterion
+import Criterion.Main
+
+import qualified Data.ByteString.Base16 as B16
+import qualified Data.ByteString as B
+
+generate :: Int -> B.ByteString
+generate n = B.pack . take n . cycle $ [0..255]
+
+main = defaultMain
+  [ case bs of
+      ~(a,b,c,d,e) -> bgroup "encode"
+        [ bench "25" $ whnf B16.encode a
+        , bench "100" $ whnf B16.encode b
+        , bench "1000" $ whnf B16.encode c
+        , bench "10000" $ whnf B16.encode d
+        , bench "100000" $ whnf B16.encode e
+        ]
+  , case bs of
+      ~(a,b,c,d,e) -> bgroup "decode"
+        [ bench "25" $ whnf B16.decode a
+        , bench "100" $ whnf B16.decode b
+        , bench "1000" $ whnf B16.decode c
+        , bench "10000" $ whnf B16.decode d
+        , bench "100000" $ whnf B16.decode e
+        ]
+  ]
+  where
+    bs =
+      let a = generate 25
+          b = generate 100
+          c = generate 1000
+          d = generate 10000
+          e = generate 100000
+      in (a,b,c,d,e)
+
+    bs' =
+      let a = generate 25
+          b = generate 100
+          c = generate 1000
+          d = generate 10000
+          e = generate 100000
+          f = B16.encode
+      in (f a, f b, f c, f d, f e)
diff --git a/tests/Tests.hs b/tests/Tests.hs
--- a/tests/Tests.hs
+++ b/tests/Tests.hs
@@ -1,68 +1,139 @@
-import Data.Char (ord)
-import Data.Word (Word8)
-import qualified Data.ByteString as B
-import qualified Data.ByteString.Lazy as BL
-import qualified Data.ByteString.Base16 as Base16
-import qualified Data.ByteString.Base16.Lazy as Base16L
+{-# LANGUAGE OverloadedStrings #-}
+module Main
+( main
+) where
 
--- Three-line "test framework" for Haskell.  Crude, but at least it tells you
--- the line number of the failure without you having to specify it.
-shouldBe :: (Eq a, Show a) => a -> a -> IO Bool
-shouldBe a b = return (a == b)
-infix 0 `shouldBe`
 
-c2w :: Char -> Word8
-c2w = fromIntegral . ord
+import Control.Monad (liftM)
 
+import qualified Data.ByteString as BS
+import Data.ByteString.Internal (c2w, w2c)
+import Data.ByteString.Char8 ()
+import qualified Data.ByteString.Base16 as B16
+import qualified Data.ByteString.Base16.Lazy as LB16
+import qualified Data.ByteString.Lazy as LBS
+import Data.ByteString.Lazy.Char8 ()
+import Data.Char (toUpper)
+import Data.String
+
+import Test.Framework (Test, defaultMain, testGroup)
+import Test.Framework.Providers.QuickCheck2 (testProperty)
+import Test.Framework.Providers.HUnit (testCase)
+import Test.HUnit hiding (Test)
+import Test.QuickCheck (Arbitrary(..))
+
+
+
 main :: IO ()
-main = do
-    let hexL  = map c2w "0123456789abcdef"
-        hexU  = map c2w "0123456789ABCDEF"
-        hexUL = map c2w "0123456789ABCDEFabcdef"
-        notHex = [c | c <- [0..255], c `notElem` hexUL]
-        hexL2 = do a <- hexL; b <- hexL; [a,b]
-        hexU2 = do a <- hexU; b <- hexU; [a,b]
-        bytes = B.pack [0..255]
+main = defaultMain tests
 
-    -- Encode every byte
-    True <- Base16.encode bytes `shouldBe` B.pack hexL2
+tests =
+  [ testGroup "property tests"
+    [ properties b16
+    , properties lb16
+    ]
+  , testGroup "unit tests"
+    [ units b16
+    , units lb16
+    , lenientUnits b16
+    , lenientUnits lb16
+    ]
+  ]
 
-    -- Decode every valid hex pair
-    True <- Base16.decode (B.pack hexL2) `shouldBe` (bytes, B.empty)
-    True <- Base16.decode (B.pack hexU2) `shouldBe` (bytes, B.empty)
+properties
+  :: ( IsString bs
+     , Show bs
+     , Eq bs
+     , Arbitrary bs
+     )
+  => Impl bs
+  -> Test
+properties (Impl label e d l _ u) = testGroup label
+  [ testProperty "decode-encode-lower" $ \a -> Right a == d (e a)
+  , testProperty "decode-encode-upper" $ \a -> Right a == d (u . e $ a)
+  , testProperty "lenient-encode-lower" $ \a -> a == l (e a)
+  , testProperty "lenient-encode-upper" $ \a -> a == l (u . e $ a)
+  , testProperty "decode-encode-encode" $ \a -> Right (e a) == d (e (e a))
+  , testProperty "lenient-encode-encode" $ \a -> e a == l (e (e a))
+  ]
 
-    -- Decode every invalid byte paired with a correct byte
-    let bads1 = [B.pack [a,b] | a <- notHex, b <- hexUL]
-    let bads2 = [B.pack [a,b] | a <- hexUL, b <- notHex]
-    True <- map Base16.decode bads1 `shouldBe` map (\s -> (B.empty, s)) bads1
-    True <- map Base16.decode bads2 `shouldBe` map (\s -> (B.empty, s)) bads2
+units
+  :: ( IsString bs
+     , Show bs
+     , Eq bs
+     )
+  => Impl bs
+  -> Test
+units (Impl label e d l td u) = testGroup label $ encs ++ decs ++ lens
+  where
+    encs =
+      [ testCase ("encode: " ++ show raw) $ do enc @?= rawEnc
+      | (raw, rawEnc) <- td
+      , let enc = e raw
+      ]
 
-    -- Like above, but start with a correct byte
-    let correctHex   = B.pack [97,98]
-        correctBytes = B.pack [171]
-    True <- map (Base16.decode . (correctHex `B.append`)) bads1
-            `shouldBe` map (\s -> (correctBytes, s)) bads1
-    True <- map (Base16.decode . (correctHex `B.append`)) bads2
-            `shouldBe` map (\s -> (correctBytes, s)) bads2
+    decs =
+      [ testCase ("decode: " ++ show rawEnc) $ do dec_enc @?= Right raw; dec_upp @?= Right raw
+      | (raw, rawEnc) <- td
+      , let dec_enc = d rawEnc
+      , let dec_upp = d (u rawEnc)
+      ]
 
-    -- Like above, but end with a correct byte
-    True <- map (Base16.decode . (`B.append` correctHex)) bads1
-            `shouldBe` map (\s -> (B.empty, s `B.append` correctHex)) bads1
-    True <- map (Base16.decode . (`B.append` correctHex)) bads2
-            `shouldBe` map (\s -> (B.empty, s `B.append` correctHex)) bads2
+    lens =
+      [ testCase ("lenient: " ++ show rawEnc) $ do len_enc @?= raw; len_upp @?= raw
+      | (raw, rawEnc) <- td
+      , let len_enc = l rawEnc
+      , let len_upp = l (u rawEnc)
+      ]
 
-    -- Lazy decoding also works with odd length chunks
-    let encodedLazy = BL.fromChunks $ map (B.pack . map c2w) ["614","239","6","142","39"]
-    True <- Base16L.decode encodedLazy `shouldBe` (BL.pack . map c2w $ "aB9aB9",BL.empty)
+lenientUnits :: (IsString bs, Show bs, Eq bs) => Impl bs -> Test
+lenientUnits (Impl label e d l _ _) = testGroup (label ++ " lenient unit tests")
+  [ testCaseB16 "" ""
+  , testCaseB16 "f" "6+++++++____++++++======*%$@#%#^*$^6"
+  , testCaseB16 "fo" "6$6+6|f"
+  , testCaseB16 "foo" "==========6$$66()*f6f"
+  , testCaseB16 "foob" "66^%$&^6f6f62"
+  , testCaseB16 "fooba" "666f()*#@6f#)(@*)6()*)2()61"
+  , testCaseB16 "foobar" "6@6@6@f@6@f@6@2@6@1@7@2++++++++++++++++++++++++"
+  ]
+  where
+    testCaseB16 s t = testCase (show $ if s == "" then "empty" else s) $ do
+      let t0 = d (e s)
+          t1 = l t
 
-    -- Lazy decoding is lazy on success
-    let encodedLazy = BL.iterate id 48
-    True <- (BL.unpack . BL.take 8 . fst . Base16L.decode $ encodedLazy)
-            `shouldBe` [0,0,0,0,0,0,0,0]
+      (d (e s)) @=? Right (l t)
 
-    -- Lazy decoding is lazy on failure
-    let encodedLazy = BL.iterate id 47
-    True <- (BL.unpack . BL.take 8 . fst . Base16L.decode $ encodedLazy)
-            `shouldBe` []
+-- ------------------------------------------------------------------ --
+-- Test data
 
-    return ()
+rfcVectors :: IsString bs => [(bs,bs)]
+rfcVectors =
+  [ ("","")
+  , ("fo", "666f")
+  , ("foo", "666f6f")
+  , ("foob", "666f6f62")
+  , ("fooba", "666f6f6261")
+  , ("foobar", "666f6f626172")
+  ]
+
+data Impl bs = Impl
+  { _label :: String
+  , _encode :: bs -> bs
+  , _decode :: bs -> Either String bs
+  , _lenient :: bs -> bs
+  , _data :: [(bs, bs)]
+  , _upper :: bs -> bs
+  }
+
+b16 :: Impl BS.ByteString
+b16 = Impl "base16-strict" B16.encode B16.decode B16.decodeLenient rfcVectors (BS.map (c2w . toUpper . w2c))
+
+
+lb16 :: Impl LBS.ByteString
+lb16 = Impl "base16-lazy" LB16.encode LB16.decode LB16.decodeLenient rfcVectors (LBS.map (c2w . toUpper . w2c))
+
+instance Arbitrary BS.ByteString where
+  arbitrary = liftM BS.pack arbitrary
+
+instance Arbitrary LBS.ByteString where
+  arbitrary = liftM LBS.pack arbitrary
