diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,7 @@
+# Revision history for mono-hash
+
+## 0.1.0.0 -- 2026-10-25
+
+* First release.
+* FNV-1 and FNV-1a for monomorphic containers.
+* SipHash-c-d and HalfSipHash-c-d for monomorphic containers.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+Copyright (c) 2025, Philipp Hagenlocher
+
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of the copyright holder nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,160 @@
+# mono-hash
+
+[![Hackage](https://img.shields.io/hackage/v/mono-hash.svg)](https://hackage.haskell.org/package/mono-hash)
+[![Haskell-CI](https://github.com/phagenlocher/mono-hash/actions/workflows/haskell-ci.yml/badge.svg?branch=main)](https://github.com/phagenlocher/mono-hash/actions/workflows/haskell-ci.yml)
+
+Hashing for monomorphic containers: FNV-1/FNV-1a, SipHash, and HalfSipHash for
+any [`MonoFoldable`](https://hackage.haskell.org/package/mono-traversable/docs/Data-MonoTraversable.html#t:MonoFoldable),
+including `ByteString`, lazy `ByteString`, `Text`, `String`, and lists of
+fixed-width words.
+
+## Motivation
+
+This library hashes data in monomorphic containers irrespective of their
+structure. Because only the concatenated element bytes matter, changing the
+container while keeping the same bytes leaves the hash unchanged. Below, the
+`ByteString` and `Vector Word64` hold exactly the same bytes; each formatted
+line of the `ByteString` is one `Word64` in the vector:
+
+```haskell
+{-# LANGUAGE NumericUnderscores #-}
+{-# LANGUAGE TypeApplications #-}
+
+import qualified Data.ByteString as BS
+import qualified Data.Vector as V
+import Data.Hash.Mono.SipHash (sipHash)
+import Data.Tree (Tree (..))
+import Data.WideWord.Word128 (Word128)
+import Data.Word (Word32, Word64)
+
+key :: Word128
+key = 0x000102030405060708090a0b0c0d0e0f
+
+bytes :: BS.ByteString
+bytes =
+    BS.pack
+        [ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77
+        , 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff
+        , 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef
+        ]
+
+vector :: V.Vector Word64
+vector =
+    V.fromList
+        [ 0x00_11_22_33_44_55_66_77
+        , 0x88_99_aa_bb_cc_dd_ee_ff
+        , 0x01_23_45_67_89_ab_cd_ef
+        ]
+
+sipHash @Word64 2 4 key bytes
+-- 0x6d003ad777121d05
+
+sipHash @Word64 2 4 key vector
+-- 0x6d003ad777121d05
+```
+
+The same works for any `MonoFoldable`. `Data.Tree.Tree` from `containers`
+already has a `MonoFoldable` instance in `mono-traversable`, and it folds in
+pre-order:
+
+```haskell
+-- Pre-order traversal: the same 24 bytes as six Word32s.
+tree :: Tree Word32
+tree =
+    Node
+        0x00_11_22_33
+        [ Node 0x44_55_66_77 []
+        , Node 0x88_99_aa_bb []
+        , Node 0xcc_dd_ee_ff
+            [ Node 0x01_23_45_67 []
+            , Node 0x89_ab_cd_ef []
+            ]
+        ]
+
+sipHash @Word64 2 4 key tree
+-- 0x6d003ad777121d05
+```
+
+## Usage
+
+```haskell
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeApplications #-}
+
+import Data.Hash.Mono.FNV (fnv1a)
+import Data.Hash.Mono.HalfSipHash (halfSipHash)
+import Data.Hash.Mono.SipHash (sipHash)
+import Data.Text (Text)
+import Data.WideWord.Word128 (Word128)
+import Data.Word (Word32, Word64)
+
+-- FNV-1a with a 64-bit result
+fnv1a @Word64 ("hello" :: Text)
+
+-- SipHash-2-4 with a 128-bit key and a 64-bit result
+sipHash @Word64 2 4 (0x000102030405060708090a0b0c0d0e0f :: Word128) ("hello" :: Text)
+
+-- HalfSipHash-2-4 with a 64-bit key and a 32-bit result
+halfSipHash @Word32 2 4 (0x0001020304050607 :: Word64) ("hello" :: Text)
+```
+
+The result type is selected with a type application (or type inference of course):
+
+| Function       | Result type                              |
+| -------------- | ---------------------------------------- |
+| `fnv1`/`fnv1a` | `Word32`, `Word64`, `Word128`, `Word256` |
+| `sipHash`      | `Word64`, `Word128`                      |
+| `halfSipHash`  | `Word32`, `Word64`                       |
+
+## Encoding and byte order
+
+Hashing is defined over the concatenation of the elements' byte encodings.
+The encoding is not type-tagged, so `[0x01, 0x02 :: Word8]` and
+`[0x0102 :: Word16]` hash the same bytes.
+
+`Word8` and `Int8` contribute one byte. Wider integers contribute
+their bit pattern most significant byte first; signed integers use two's
+complement. `Char` contributes UTF-8; a lone surrogate, which `String` can
+contain but UTF-8 cannot represent, uses the 3-byte
+[WTF-8](https://wtf-8.codeberg.page) form. `String` and `Text` are
+hashed character by character, while `ByteString` contributes its raw bytes.
+The library provides instances for `Word8`, `Word16`, `Word32`, `Word64`,
+`Word128`, `Word256`, `Int8`, `Int16`, `Int32`, `Int64`, and `Char`.
+
+## Custom element types
+
+Give the element type a `ByteFoldable` instance to hash containers of your
+own type:
+
+```haskell
+import Data.Hash.Mono.FNV (fnv1a)
+import Data.Hash.Mono.Internal.ByteFoldable (ByteFoldable (..))
+import Data.Word (Word8, Word64)
+
+data Rgb = Rgb Word8 Word8 Word8
+
+instance ByteFoldable Rgb where
+    foldlBytes f z (Rgb r g b) = f (f (f z r) g) b
+
+-- Hashes [Rgb 0x12 0x34 0x56] as the bytes 0x12 0x34 0x56.
+fnv1a @Word64 [Rgb 0x12 0x34 0x56]
+```
+
+The bytes an instance emits are the bytes hashed, so changing an instance
+changes every hash of containers holding that element type.
+
+## Formatting and linting
+
+Sources are formatted with [fourmolu](https://github.com/fourmolu/fourmolu) and
+linted with [hlint](https://github.com/ndmitchell/hlint). CI checks both, using
+the versions pinned in `.github/workflows/ci.yml`, so run them before opening
+a pull request:
+
+```console
+fourmolu --mode inplace $(git ls-files '*.hs')
+hlint src test bench
+```
+
+## License
+
+BSD-3-Clause. See [LICENSE](LICENSE).
diff --git a/bench/Main.hs b/bench/Main.hs
new file mode 100644
--- /dev/null
+++ b/bench/Main.hs
@@ -0,0 +1,92 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications #-}
+
+module Main (main) where
+
+import Control.DeepSeq (NFData, force)
+import Control.Exception (evaluate)
+import Criterion (Benchmark, bench, bgroup, whnf)
+import Criterion.Main (defaultMain)
+import qualified Data.ByteString as BS
+import Data.Hash.Mono.FNV (fnv1, fnv1a)
+import Data.Hash.Mono.HalfSipHash (halfSipHash)
+import Data.Hash.Mono.SipHash (sipHash)
+import qualified Data.Text as T
+import Data.WideWord.Word128 (Word128)
+import Data.WideWord.Word256 (Word256)
+import Data.Word (Word32, Word64)
+
+newtype Sizes a = Sizes [(String, a)]
+
+mkSizes :: (Int -> a) -> Sizes a
+mkSizes f =
+    Sizes
+        [ ("small input", f 10)
+        , ("medium input", f 1000)
+        , ("large input", f 100000)
+        , ("huge input", f 10000000)
+        ]
+
+-- | Smaller sizes for element types with a large in-memory footprint.
+mkElementSizes :: (Int -> a) -> Sizes a
+mkElementSizes f =
+    Sizes
+        [ ("small input", f 100)
+        , ("medium input", f 1000)
+        , ("large input", f 10000)
+        ]
+
+forceSizes :: (NFData a) => Sizes a -> IO (Sizes a)
+forceSizes sizes@(Sizes xs) = do
+    _ <- evaluate $ force xs
+    pure sizes
+
+mkBench :: (a -> b) -> Sizes a -> [Benchmark]
+mkBench fun (Sizes xs) = [bench name (whnf fun x) | (name, x) <- xs]
+
+main :: IO ()
+main = do
+    strings <- forceSizes $ mkSizes (`replicate` 'a')
+    bytes <- forceSizes $ mkSizes (`BS.replicate` 97)
+    texts <- forceSizes $ mkSizes (\n -> T.replicate n (T.singleton 'a'))
+    word128s <- forceSizes $ mkElementSizes (\n -> [fromIntegral i :: Word128 | i <- [0 .. n - 1]])
+    word256s <- forceSizes $ mkElementSizes (\n -> [fromIntegral i :: Word256 | i <- [0 .. n - 1]])
+
+    boundaryInputs <-
+        evaluate $
+            force
+                [ (n, BS.replicate n 97)
+                | n <- [254, 255, 256, 257, 258, 511, 512, 513]
+                ]
+
+    defaultMain
+        [ bgroup "FNV1 Word32" $ mkBench (fnv1 @Word32) strings
+        , bgroup "FNV1a Word32" $ mkBench (fnv1a @Word32) strings
+        , bgroup "FNV1 Word64" $ mkBench (fnv1 @Word64) strings
+        , bgroup "FNV1a Word64" $ mkBench (fnv1a @Word64) strings
+        , bgroup "FNV1 Word128" $ mkBench (fnv1 @Word128) strings
+        , bgroup "FNV1a Word128" $ mkBench (fnv1a @Word128) strings
+        , bgroup "FNV1 Word256" $ mkBench (fnv1 @Word256) strings
+        , bgroup "FNV1a Word256" $ mkBench (fnv1a @Word256) strings
+        , bgroup "SipHash-2-4 Word64" $ mkBench (sipHash @Word64 2 4 0xc0ffee) strings
+        , bgroup "SipHash-2-4 Word128" $ mkBench (sipHash @Word128 2 4 0xc0ffee) strings
+        , bgroup "HalfSipHash-2-4 Word32" $ mkBench (halfSipHash @Word32 2 4 0xc0ffee) strings
+        , bgroup "HalfSipHash-2-4 Word64" $ mkBench (halfSipHash @Word64 2 4 0xc0ffee) strings
+        , bgroup "FNV1a Word64/ByteString" $ mkBench (fnv1a @Word64) bytes
+        , bgroup "FNV1a Word64/Text" $ mkBench (fnv1a @Word64) texts
+        , bgroup "SipHash-2-4 Word64/ByteString" $ mkBench (sipHash @Word64 2 4 0xc0ffee) bytes
+        , bgroup "SipHash-2-4 Word64/Text" $ mkBench (sipHash @Word64 2 4 0xc0ffee) texts
+        , bgroup "HalfSipHash-2-4 Word32/ByteString" $ mkBench (halfSipHash @Word32 2 4 0xc0ffee) bytes
+        , bgroup "HalfSipHash-2-4 Word32/Text" $ mkBench (halfSipHash @Word32 2 4 0xc0ffee) texts
+        , bgroup
+            "SipHash-2-4 Word64 length boundary"
+            [ bench (show n) $ whnf (sipHash @Word64 2 4 0xc0ffee) input
+            | (n, input) <- boundaryInputs
+            ]
+        , bgroup "FNV1a Word64/[Word128]" $ mkBench (fnv1a @Word64) word128s
+        , bgroup "FNV1a Word256/[Word128]" $ mkBench (fnv1a @Word256) word128s
+        , bgroup "SipHash-2-4 Word64/[Word128]" $ mkBench (sipHash @Word64 2 4 0xc0ffee) word128s
+        , bgroup "FNV1a Word64/[Word256]" $ mkBench (fnv1a @Word64) word256s
+        , bgroup "FNV1a Word256/[Word256]" $ mkBench (fnv1a @Word256) word256s
+        , bgroup "SipHash-2-4 Word64/[Word256]" $ mkBench (sipHash @Word64 2 4 0xc0ffee) word256s
+        ]
diff --git a/mono-hash.cabal b/mono-hash.cabal
new file mode 100644
--- /dev/null
+++ b/mono-hash.cabal
@@ -0,0 +1,102 @@
+cabal-version: 3.0
+name: mono-hash
+version: 0.1.0.0
+synopsis: Hashing for monomorphic containers.
+description:
+  FNV, SipHash, and HalfSipHash hashing for monomorphic containers.
+license: BSD-3-Clause
+license-file: LICENSE
+author: Philipp Hagenlocher
+maintainer: me@philipphagenlocher.de
+category: Data
+build-type: Simple
+homepage: https://github.com/phagenlocher/mono-hash#readme
+bug-reports: https://github.com/phagenlocher/mono-hash/issues
+extra-doc-files:
+  CHANGELOG.md
+  README.md
+tested-with:
+  ghc ==8.10.7 || ==9.0.2 || ==9.2.8 || ==9.4.8 || ==9.6.7
+    || ==9.8.4 || ==9.10.3 || ==9.12.4 || ==9.14.1
+
+source-repository head
+  type: git
+  location: https://github.com/phagenlocher/mono-hash
+
+common warnings
+  ghc-options: -Wall
+
+library
+  import: warnings
+  ghc-options: -O2
+
+  exposed-modules:
+    Data.Hash.Mono.FNV
+    Data.Hash.Mono.HalfSipHash
+    Data.Hash.Mono.Internal.ByteFoldable
+    Data.Hash.Mono.SipHash
+
+  other-modules:
+    Data.Hash.Mono.Internal
+
+  build-depends:
+    base >=4.14.3.0 && <5,
+    mono-traversable >=1.0.21 && <1.1,
+    wide-word >=0.1.7 && <0.2
+
+  hs-source-dirs: src
+
+  default-language: Haskell2010
+
+  default-extensions:
+    ConstraintKinds
+    FlexibleContexts
+    ImportQualifiedPost
+    InstanceSigs
+    ScopedTypeVariables
+
+test-suite mono-hash-test
+  import: warnings
+
+  default-language: Haskell2010
+
+  other-modules:
+    Test.Data.ByteFoldable
+    Test.Data.FNV
+    Test.Data.SipHash
+    Test.Data.HalfSipHash
+
+  type: exitcode-stdio-1.0
+
+  hs-source-dirs: test
+
+  main-is: Main.hs
+
+  build-depends:
+    base >=4.14.3.0 && <5,
+    bytestring >=0.10 && <0.13,
+    mono-hash,
+    tasty >=1.4 && <1.6,
+    tasty-hunit >=0.10 && <0.11,
+    text >=1.2 && <2.2,
+    wide-word >=0.1.7 && <0.2,
+
+benchmark mono-hash-bench
+  type: exitcode-stdio-1.0
+
+  hs-source-dirs: bench
+
+  default-language: Haskell2010
+
+  ghc-options: -O2
+
+  main-is: Main.hs
+
+  build-depends:
+    base >=4.14.3.0 && <5,
+    bytestring >=0.10 && <0.13,
+    criterion >=1.5 && <1.7,
+    deepseq >=1.4 && <1.6,
+    mono-hash,
+    text >=1.2 && <2.2,
+    wide-word >=0.1.7 && <0.2,
diff --git a/src/Data/Hash/Mono/FNV.hs b/src/Data/Hash/Mono/FNV.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Hash/Mono/FNV.hs
@@ -0,0 +1,92 @@
+{-# LANGUAGE BangPatterns #-}
+
+-- | FNV-1 and FNV-1a hashes for any 'MonoFoldable' container.
+module Data.Hash.Mono.FNV (
+    FNVData,
+    FNVResult,
+    HasFNVBasis,
+    fnv1,
+    fnv1a,
+)
+where
+
+import Data.Bits (Bits (xor))
+import Data.Hash.Mono.Internal.ByteFoldable (ByteFoldable (..))
+import Data.MonoTraversable (Element, MonoFoldable (ofoldl'))
+import Data.WideWord.Word128 (Word128)
+import Data.WideWord.Word256 (Word256)
+import Data.Word (Word32, Word64, Word8)
+
+-- | Offset basis and prime for an FNV result width.
+class HasFNVBasis a where
+    -- | Offset basis used to seed the hash.
+    fnvOffsetBasis :: a
+
+    -- | Prime applied once per input byte.
+    fnvPrime :: a
+
+instance HasFNVBasis Word32 where
+    fnvOffsetBasis :: Word32
+    fnvOffsetBasis = 2166136261
+
+    fnvPrime :: Word32
+    fnvPrime = 16777619
+
+instance HasFNVBasis Word64 where
+    fnvOffsetBasis :: Word64
+    fnvOffsetBasis = 14695981039346656037
+
+    fnvPrime :: Word64
+    fnvPrime = 1099511628211
+
+instance HasFNVBasis Word128 where
+    fnvOffsetBasis :: Word128
+    fnvOffsetBasis = 144066263297769815596495629667062367629
+
+    fnvPrime :: Word128
+    fnvPrime = 309485009821345068724781371
+
+instance HasFNVBasis Word256 where
+    fnvOffsetBasis :: Word256
+    fnvOffsetBasis = 100029257958052580907070968620625704837092796014241193945225284501741471925557
+
+    fnvPrime :: Word256
+    fnvPrime = 374144419156711147060143317175368453031918731002211
+
+-- | Constraint for types that can be used as an FNV result.
+type FNVResult a = (HasFNVBasis a, Bits a, Num a)
+
+-- | Constraint for containers hashed with FNV.
+type FNVData a = (MonoFoldable a, ByteFoldable (Element a))
+
+fnvGeneric :: forall result mono. (FNVData mono, FNVResult result) => (result -> Word8 -> result) -> mono -> result
+fnvGeneric fnvRound = ofoldl' (foldlBytes fnvRound) fnvOffsetBasis
+{-# INLINE fnvGeneric #-}
+
+{- | FNV-1 hash of a monomorphic container.
+
+Example:
+@
+fnv1 \@'Word64' \"hello\"
+@
+-}
+fnv1 :: forall result mono. (FNVData mono, FNVResult result) => mono -> result
+fnv1 = fnvGeneric fnvRound
+  where
+    fnvRound :: result -> Word8 -> result
+    fnvRound !acc !octet = (acc * fnvPrime) `xor` fromIntegral octet
+{-# INLINEABLE fnv1 #-}
+
+{- | FNV-1a hash of a monomorphic container.
+
+Example:
+@
+fnv1a \@'Word64' \"hello\"
+@
+-}
+fnv1a :: forall result mono. (FNVData mono, FNVResult result) => mono -> result
+fnv1a = fnvGeneric fnvRound
+  where
+    fnvRound :: result -> Word8 -> result
+    fnvRound !acc !octet = (acc `xor` fromIntegral octet) * fnvPrime
+{-# INLINEABLE fnv1a #-}
diff --git a/src/Data/Hash/Mono/HalfSipHash.hs b/src/Data/Hash/Mono/HalfSipHash.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Hash/Mono/HalfSipHash.hs
@@ -0,0 +1,170 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE StrictData #-}
+{-# LANGUAGE TypeApplications #-}
+
+-- | HalfSipHash-@c@-@d@ for any 'MonoFoldable' container.
+module Data.Hash.Mono.HalfSipHash (
+    HalfSipHashData,
+    HalfSipHashResult,
+    halfSipHash,
+)
+where
+
+import Data.Bits (Bits (rotateL, shiftL, shiftR, xor, (.&.), (.|.)))
+import Data.Hash.Mono.Internal (byteSwap32)
+import Data.Hash.Mono.Internal.ByteFoldable (ByteFoldable (..))
+import Data.MonoTraversable (Element, MonoFoldable (ofoldl'))
+import Data.Word (Word32, Word64, Word8)
+
+-- | Result types that 'halfSipHash' can produce.
+class HalfSipHashResult a where
+    -- | Adjust the initial state for the selected result type.
+    initialize :: HalfSipData -> HalfSipData
+
+    -- | Run the finalization rounds and return the hash.
+    finalization :: Word -> HalfSipData -> a
+
+instance HalfSipHashResult Word32 where
+    initialize :: HalfSipData -> HalfSipData
+    initialize = id
+
+    finalization :: Word -> HalfSipData -> Word32
+    finalization d (HalfSipData !v0 !v1 !v2 !v3) =
+        let v2' = v2 `xor` 0xff
+            (HalfSipData !_ v1' _ !v3') = halfSipRounds d (HalfSipData v0 v1 v2' v3)
+         in v1' `xor` v3'
+
+instance HalfSipHashResult Word64 where
+    initialize :: HalfSipData -> HalfSipData
+    initialize (HalfSipData !v0 !v1 !v2 !v3) = HalfSipData v0 (v1 `xor` 0xee) v2 v3
+
+    finalization :: Word -> HalfSipData -> Word64
+    finalization d (HalfSipData !v0 !v1 !v2 !v3) =
+        let v2' = v2 `xor` 0xee
+            (HalfSipData !v0' !v1' !v2'' !v3') = halfSipRounds d (HalfSipData v0 v1 v2' v3)
+            word0 = v1' `xor` v3'
+            v1'' = v1' `xor` 0xdd
+            (HalfSipData _ !v1''' _ !v3'') = halfSipRounds d (HalfSipData v0' v1'' v2'' v3')
+            word1 = v1''' `xor` v3''
+         in (fromIntegral word1 `shiftL` 32) + fromIntegral word0
+
+-- | Constraint for containers hashed with HalfSipHash.
+type HalfSipHashData a = (MonoFoldable a, ByteFoldable (Element a))
+
+-- | HalfSipHash internal state, consisting of four 32-bit words.
+data HalfSipData = HalfSipData
+    { hv0 :: {-# UNPACK #-} !Word32
+    , hv1 :: {-# UNPACK #-} !Word32
+    , hv2 :: {-# UNPACK #-} !Word32
+    , hv3 :: {-# UNPACK #-} !Word32
+    }
+
+-- | Internal HalfSipHash accumulator.
+data HalfSipStream = HalfSipStream
+    { hsData :: {-# UNPACK #-} !HalfSipData
+    -- ^ HalfSipHash state.
+    , hsWord :: {-# UNPACK #-} !Word32
+    -- ^ Input bytes buffered so far, in little-endian order.
+    , hsCount :: {-# UNPACK #-} !Int
+    -- ^ Number of bytes buffered in 'hsWord' (0-3).
+    , hsLen :: {-# UNPACK #-} !Word8
+    -- ^ Total number of input bytes, modulo 256.
+    }
+
+{- | Compute HalfSipHash-@c@-@d@ of a monomorphic container.
+
+@c@ and @d@ are the compression and finalization round counts;
+HalfSipHash-2-4 is the standard variant.
+
+Byte order: the 'Word64' key holds the 8 reference key bytes in
+big-endian order, and each 32-bit half is then read as a little-endian
+word. The result is the reference implementation's output word: a
+'Word32' result is the 32-bit tag. A 'Word64' result packs the first
+32-bit tag word in the low half and the second in the high half; its
+little-endian serialization is the 8-byte reference digest.
+
+Example:
+@
+halfSipHash \@'Word32' 2 4 0xc0ffee \"hello\"
+@
+-}
+halfSipHash :: forall result mono. (HalfSipHashData mono, HalfSipHashResult result) => Word -> Word -> Word64 -> mono -> result
+halfSipHash c d key mono =
+    case ofoldl' (foldlBytes (step c)) initStream mono of
+        HalfSipStream{hsData = state, hsWord = word, hsLen = len} ->
+            finalization d (compress c state (word .|. (fromIntegral len `shiftL` 24)))
+  where
+    initData :: HalfSipData
+    initData =
+        HalfSipData
+            { hv0 = k0
+            , hv1 = k1
+            , hv2 = k0 `xor` 0x6c796765
+            , hv3 = k1 `xor` 0x74656462
+            }
+      where
+        k0 :: Word32
+        k0 = byteSwap32 . fromIntegral $ key `shiftR` 32
+
+        k1 :: Word32
+        k1 = byteSwap32 . fromIntegral $ key .&. 0xffffffff
+
+    initStream :: HalfSipStream
+    initStream =
+        HalfSipStream
+            { hsData = initialize @result initData
+            , hsWord = 0
+            , hsCount = 0
+            , hsLen = 0
+            }
+{-# INLINEABLE halfSipHash #-}
+
+step :: Word -> HalfSipStream -> Word8 -> HalfSipStream
+step c HalfSipStream{hsData = d, hsWord = word, hsCount = n, hsLen = len} !b =
+    let word' = word .|. (fromIntegral b `shiftL` (8 * n))
+        len' = len + 1
+     in if n == 3
+            then HalfSipStream{hsData = compress c d word', hsWord = 0, hsCount = 0, hsLen = len'}
+            else HalfSipStream{hsData = d, hsWord = word', hsCount = n + 1, hsLen = len'}
+{-# INLINE step #-}
+
+compress :: Word -> HalfSipData -> Word32 -> HalfSipData
+compress c (HalfSipData !v0 !v1 !v2 !v3) m =
+    let v3' = v3 `xor` m
+        (HalfSipData !v0' !v1' !v2' !v3'') = halfSipRounds c (HalfSipData v0 v1 v2 v3')
+        v0'' = v0' `xor` m
+     in HalfSipData v0'' v1' v2' v3''
+{-# INLINE compress #-}
+
+halfSipRound :: HalfSipData -> HalfSipData
+halfSipRound (HalfSipData !v0 !v1 !v2 !v3) =
+    let v0' = v0 + v1
+        v1' = v1 `rotateL` 5
+        v1'' = v1' `xor` v0'
+        v0'' = v0' `rotateL` 16
+        v2' = v2 + v3
+        v3' = v3 `rotateL` 8
+        v3'' = v3' `xor` v2'
+        v0''' = v0'' + v3''
+        v3''' = v3'' `rotateL` 7
+        v3'''' = v3''' `xor` v0'''
+        v2'' = v2' + v1''
+        v1''' = v1'' `rotateL` 13
+        v1'''' = v1''' `xor` v2''
+        v2''' = v2'' `rotateL` 16
+     in HalfSipData v0''' v1'''' v2''' v3''''
+{-# INLINE halfSipRound #-}
+
+halfSipRoundN :: Word -> HalfSipData -> HalfSipData
+halfSipRoundN 0 !x = x
+halfSipRoundN n !x = halfSipRoundN (n - 1) $! halfSipRound x
+
+-- | Unrolled variant of 'halfSipRoundN' for the small counts used in practice.
+halfSipRounds :: Word -> HalfSipData -> HalfSipData
+halfSipRounds 1 !x = halfSipRound x
+halfSipRounds 2 !x = halfSipRound (halfSipRound x)
+halfSipRounds 3 !x = halfSipRound (halfSipRound (halfSipRound x))
+halfSipRounds 4 !x = halfSipRound (halfSipRound (halfSipRound (halfSipRound x)))
+halfSipRounds n !x = halfSipRoundN n x
+{-# INLINE halfSipRounds #-}
diff --git a/src/Data/Hash/Mono/Internal.hs b/src/Data/Hash/Mono/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Hash/Mono/Internal.hs
@@ -0,0 +1,43 @@
+-- | Internal helpers shared by the hash implementations.
+module Data.Hash.Mono.Internal (
+    byteSwap64,
+    byteSwap32,
+)
+where
+
+import Data.Bits (Bits (shiftL, shiftR, (.&.)))
+import Data.Word (Word32, Word64)
+
+-- | Byte-swap a 'Word64'.
+byteSwap64 :: Word64 -> Word64
+byteSwap64 w =
+    let c0 = (w `shiftR` 56) .&. 0xff
+        c1 = (w `shiftR` 48) .&. 0xff
+        c2 = (w `shiftR` 40) .&. 0xff
+        c3 = (w `shiftR` 32) .&. 0xff
+        c4 = (w `shiftR` 24) .&. 0xff
+        c5 = (w `shiftR` 16) .&. 0xff
+        c6 = (w `shiftR` 8) .&. 0xff
+        c7 = w .&. 0xff
+     in c0
+            + (c1 `shiftL` 8)
+            + (c2 `shiftL` 16)
+            + (c3 `shiftL` 24)
+            + (c4 `shiftL` 32)
+            + (c5 `shiftL` 40)
+            + (c6 `shiftL` 48)
+            + (c7 `shiftL` 56)
+{-# INLINEABLE byteSwap64 #-}
+
+-- | Byte-swap a 'Word32'.
+byteSwap32 :: Word32 -> Word32
+byteSwap32 w =
+    let c0 = (w `shiftR` 24) .&. 0xff
+        c1 = (w `shiftR` 16) .&. 0xff
+        c2 = (w `shiftR` 8) .&. 0xff
+        c3 = w .&. 0xff
+     in c0
+            + (c1 `shiftL` 8)
+            + (c2 `shiftL` 16)
+            + (c3 `shiftL` 24)
+{-# INLINEABLE byteSwap32 #-}
diff --git a/src/Data/Hash/Mono/Internal/ByteFoldable.hs b/src/Data/Hash/Mono/Internal/ByteFoldable.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Hash/Mono/Internal/ByteFoldable.hs
@@ -0,0 +1,163 @@
+{-# LANGUAGE InstanceSigs #-}
+
+-- | Byte encoding for types.
+module Data.Hash.Mono.Internal.ByteFoldable (
+    ByteFoldable (..),
+)
+where
+
+import Data.Bits (Bits (shiftR, (.&.)))
+import Data.Char qualified as Char
+import Data.Int (Int16, Int32, Int64, Int8)
+import Data.WideWord.Word128 (Word128 (..))
+import Data.WideWord.Word256 (Word256 (..))
+import Data.Word (Word16, Word32, Word64, Word8)
+
+{- | Types with a fixed byte encoding.
+
+An instance must define a fixed order for the bytes to be folded.
+-}
+class ByteFoldable a where
+    -- | Fold over the bytes of a value.
+    foldlBytes :: (b -> Word8 -> b) -> b -> a -> b
+
+{- | 'Char' is encoded as
+<https://wtf-8.codeberg.page WTF-8>, the UTF-8 encoding extended to
+lone surrogates. For all other code points the bytes are ordinary UTF-8.
+
+'Char' can represent lone surrogates, which strict UTF-8 cannot encode.
+Replacing them, as @Text@ does with @U+FFFD@, would be lossy and would let
+distinct strings produce the same bytes. WTF-8 instead uses the ordinary
+three-byte shape for them, so every 'Char' has an encoding, the encoding
+stays injective, and valid text remains plain UTF-8. A 'String' and a
+@Text@ containing a lone surrogate therefore encode differently.
+-}
+instance ByteFoldable Char where
+    foldlBytes f z c
+        | n < 0x80 = f z (fromIntegral n)
+        | n < 0x800 =
+            f
+                (f z (fromIntegral (0xc0 + (n `shiftR` 6))))
+                (fromIntegral (0x80 + (n .&. 0x3f)))
+        | n < 0x10000 =
+            f
+                ( f
+                    (f z (fromIntegral (0xe0 + (n `shiftR` 12))))
+                    (fromIntegral (0x80 + ((n `shiftR` 6) .&. 0x3f)))
+                )
+                (fromIntegral (0x80 + (n .&. 0x3f)))
+        | otherwise =
+            f
+                ( f
+                    ( f
+                        (f z (fromIntegral (0xf0 + (n `shiftR` 18))))
+                        (fromIntegral (0x80 + ((n `shiftR` 12) .&. 0x3f)))
+                    )
+                    (fromIntegral (0x80 + ((n `shiftR` 6) .&. 0x3f)))
+                )
+                (fromIntegral (0x80 + (n .&. 0x3f)))
+      where
+        n :: Int
+        n = Char.ord c
+    {-# INLINE foldlBytes #-}
+
+instance ByteFoldable Word8 where
+    foldlBytes f = f
+    {-# INLINE foldlBytes #-}
+
+{- | Encoded as its two's-complement bit pattern, the same bytes as the
+corresponding 'Word8'.
+-}
+instance ByteFoldable Int8 where
+    foldlBytes f z i = foldlBytes f z (fromIntegral i :: Word8)
+    {-# INLINE foldlBytes #-}
+
+-- | Encoded as two bytes, most significant first.
+instance ByteFoldable Word16 where
+    foldlBytes f z w =
+        f (f z (fromIntegral (w `shiftR` 8))) (fromIntegral w)
+    {-# INLINE foldlBytes #-}
+
+{- | Encoded as its two's-complement bit pattern, the same bytes as the
+corresponding 'Word16'.
+-}
+instance ByteFoldable Int16 where
+    foldlBytes f z i = foldlBytes f z (fromIntegral i :: Word16)
+    {-# INLINE foldlBytes #-}
+
+-- | Encoded as four bytes, most significant first.
+instance ByteFoldable Word32 where
+    foldlBytes f z w =
+        f
+            ( f
+                ( f
+                    (f z (fromIntegral (w `shiftR` 24)))
+                    (fromIntegral (w `shiftR` 16))
+                )
+                (fromIntegral (w `shiftR` 8))
+            )
+            (fromIntegral w)
+    {-# INLINE foldlBytes #-}
+
+{- | Encoded as its two's-complement bit pattern, the same bytes as the
+corresponding 'Word32'.
+-}
+instance ByteFoldable Int32 where
+    foldlBytes f z i = foldlBytes f z (fromIntegral i :: Word32)
+    {-# INLINE foldlBytes #-}
+
+-- | Encoded as eight bytes, most significant first.
+instance ByteFoldable Word64 where
+    foldlBytes f z w =
+        f
+            ( f
+                ( f
+                    ( f
+                        ( f
+                            ( f
+                                ( f
+                                    (f z (fromIntegral (w `shiftR` 56)))
+                                    (fromIntegral (w `shiftR` 48))
+                                )
+                                (fromIntegral (w `shiftR` 40))
+                            )
+                            (fromIntegral (w `shiftR` 32))
+                        )
+                        (fromIntegral (w `shiftR` 24))
+                    )
+                    (fromIntegral (w `shiftR` 16))
+                )
+                (fromIntegral (w `shiftR` 8))
+            )
+            (fromIntegral w)
+    {-# INLINE foldlBytes #-}
+
+{- | Encoded as its two's-complement bit pattern, the same bytes as the
+corresponding 'Word64'.
+-}
+instance ByteFoldable Int64 where
+    foldlBytes f z i = foldlBytes f z (fromIntegral i :: Word64)
+    {-# INLINE foldlBytes #-}
+
+-- | Encoded as sixteen bytes, most significant first.
+instance ByteFoldable Word128 where
+    foldlBytes f z w =
+        foldlBytes f (foldlBytes f z (word128Hi64 w)) (word128Lo64 w)
+    {-# INLINE foldlBytes #-}
+
+-- | Encoded as thirty-two bytes, most significant first.
+instance ByteFoldable Word256 where
+    foldlBytes f z w =
+        foldlBytes
+            f
+            ( foldlBytes
+                f
+                ( foldlBytes
+                    f
+                    (foldlBytes f z (word256hi w))
+                    (word256m1 w)
+                )
+                (word256m0 w)
+            )
+            (word256lo w)
+    {-# INLINE foldlBytes #-}
diff --git a/src/Data/Hash/Mono/SipHash.hs b/src/Data/Hash/Mono/SipHash.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Hash/Mono/SipHash.hs
@@ -0,0 +1,171 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE StrictData #-}
+{-# LANGUAGE TypeApplications #-}
+
+-- | SipHash-@c@-@d@ for any 'MonoFoldable' container.
+module Data.Hash.Mono.SipHash (
+    SipHashData,
+    SipHashResult,
+    sipHash,
+)
+where
+
+import Data.Bits (Bits (rotateL, shiftL, xor, (.|.)))
+import Data.Hash.Mono.Internal (byteSwap64)
+import Data.Hash.Mono.Internal.ByteFoldable (ByteFoldable (..))
+import Data.MonoTraversable (Element, MonoFoldable (ofoldl'))
+import Data.WideWord.Word128 (Word128 (..))
+import Data.Word (Word64, Word8)
+
+-- | Result types that 'sipHash' can produce.
+class SipHashResult a where
+    -- | Adjust the initial state for the selected result type.
+    initialize :: SipData -> SipData
+
+    -- | Run the finalization rounds and return the hash.
+    finalization :: Word -> SipData -> a
+
+instance SipHashResult Word64 where
+    initialize :: SipData -> SipData
+    initialize = id
+
+    finalization :: Word -> SipData -> Word64
+    finalization d (SipData !v0 !v1 !v2 !v3) =
+        let v2' = v2 `xor` 0xff
+            (SipData !v0' !v1' !v2'' !v3') = sipRounds d (SipData v0 v1 v2' v3)
+         in v0' `xor` v1' `xor` v2'' `xor` v3'
+
+instance SipHashResult Word128 where
+    initialize :: SipData -> SipData
+    initialize (SipData !v0 !v1 !v2 !v3) = SipData v0 (v1 `xor` 0xee) v2 v3
+
+    finalization :: Word -> SipData -> Word128
+    finalization d (SipData !v0 !v1 !v2 !v3) =
+        let v2' = v2 `xor` 0xee
+            (SipData !v0' !v1' !v2'' !v3') = sipRounds d (SipData v0 v1 v2' v3)
+            word0 = v0' `xor` v1' `xor` v2'' `xor` v3'
+            v1'' = v1' `xor` 0xdd
+            (SipData !v0'' !v1''' !v2''' !v3'') = sipRounds d (SipData v0' v1'' v2'' v3')
+            word1 = v0'' `xor` v1''' `xor` v2''' `xor` v3''
+         in Word128 word1 word0
+
+-- | Constraint for containers hashable with SipHash.
+type SipHashData a = (MonoFoldable a, ByteFoldable (Element a))
+
+-- | SipHash internal state.
+data SipData = SipData
+    { sv0 :: {-# UNPACK #-} !Word64
+    , sv1 :: {-# UNPACK #-} !Word64
+    , sv2 :: {-# UNPACK #-} !Word64
+    , sv3 :: {-# UNPACK #-} !Word64
+    }
+
+-- | Internal SipHash accumulator.
+data SipStream = SipStream
+    { ssData :: {-# UNPACK #-} !SipData
+    -- ^ SipHash state.
+    , ssWord :: {-# UNPACK #-} !Word64
+    -- ^ Input bytes buffered so far, in little-endian order.
+    , ssCount :: {-# UNPACK #-} !Int
+    -- ^ Number of bytes buffered in 'ssWord'.
+    , ssLen :: {-# UNPACK #-} !Word8
+    -- ^ Total number of input bytes, modulo 256.
+    }
+
+{- | Compute SipHash-@c@-@d@ of a monomorphic container.
+
+@c@ and @d@ are the compression and finalization round counts;
+SipHash-2-4 is the standard variant.
+
+Byte order: the @Word128@ key holds the 16 reference key bytes in
+big-endian order, and each 64-bit half is then read as a little-endian
+word. The result is the reference implementation's output word: a
+'Word64' result is the 64-bit tag. A @Word128@ result packs the first
+64-bit tag word in the low half and the second in the high half; its
+little-endian serialization is the 16-byte reference digest.
+
+Example:
+@
+sipHash \@'Word64' 2 4 0xc0ffee \"hello\"
+@
+-}
+sipHash :: forall result mono. (SipHashData mono, SipHashResult result) => Word -> Word -> Word128 -> mono -> result
+sipHash c d key mono =
+    case ofoldl' (foldlBytes (step c)) initStream mono of
+        SipStream{ssData = state, ssWord = word, ssLen = len} ->
+            finalization d (compress c state (word .|. (fromIntegral len `shiftL` 56)))
+  where
+    initData :: SipData
+    initData =
+        SipData
+            { sv0 = k0 `xor` 0x736f6d6570736575
+            , sv1 = k1 `xor` 0x646f72616e646f6d
+            , sv2 = k0 `xor` 0x6c7967656e657261
+            , sv3 = k1 `xor` 0x7465646279746573
+            }
+      where
+        k0 :: Word64
+        k0 = byteSwap64 $ word128Hi64 key
+
+        k1 :: Word64
+        k1 = byteSwap64 $ word128Lo64 key
+
+    initStream :: SipStream
+    initStream =
+        SipStream
+            { ssData = initialize @result initData
+            , ssWord = 0
+            , ssCount = 0
+            , ssLen = 0
+            }
+{-# INLINEABLE sipHash #-}
+
+step :: Word -> SipStream -> Word8 -> SipStream
+step c SipStream{ssData = d, ssWord = word, ssCount = n, ssLen = len} !b =
+    let word' = word .|. (fromIntegral b `shiftL` (8 * n))
+        len' = len + 1
+     in if n == 7
+            then SipStream{ssData = compress c d word', ssWord = 0, ssCount = 0, ssLen = len'}
+            else SipStream{ssData = d, ssWord = word', ssCount = n + 1, ssLen = len'}
+{-# INLINE step #-}
+
+compress :: Word -> SipData -> Word64 -> SipData
+compress c (SipData !v0 !v1 !v2 !v3) m =
+    let v3' = v3 `xor` m
+        (SipData !v0' !v1' !v2' !v3'') = sipRounds c (SipData v0 v1 v2 v3')
+        v0'' = v0' `xor` m
+     in SipData v0'' v1' v2' v3''
+{-# INLINE compress #-}
+
+sipRound :: SipData -> SipData
+sipRound (SipData !v0 !v1 !v2 !v3) =
+    let v0' = v0 + v1
+        v1' = v1 `rotateL` 13
+        v1'' = v1' `xor` v0'
+        v0'' = v0' `rotateL` 32
+        v2' = v2 + v3
+        v3' = v3 `rotateL` 16
+        v3'' = v3' `xor` v2'
+        v0''' = v0'' + v3''
+        v3''' = v3'' `rotateL` 21
+        v3'''' = v3''' `xor` v0'''
+        v2'' = v2' + v1''
+        v1''' = v1'' `rotateL` 17
+        v1'''' = v1''' `xor` v2''
+        v2''' = v2'' `rotateL` 32
+     in SipData v0''' v1'''' v2''' v3''''
+{-# INLINE sipRound #-}
+
+sipRoundN :: Word -> SipData -> SipData
+sipRoundN 0 !x = x
+sipRoundN n !x = sipRoundN (n - 1) $! sipRound x
+
+-- | Unrolled variant of 'sipRoundN' for the small counts used in practice.
+sipRounds :: Word -> SipData -> SipData
+sipRounds 1 !x = sipRound x
+sipRounds 2 !x = sipRound (sipRound x)
+sipRounds 3 !x = sipRound (sipRound (sipRound x))
+sipRounds 4 !x = sipRound (sipRound (sipRound (sipRound x)))
+sipRounds n !x = sipRoundN n x
+{-# INLINE sipRounds #-}
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,362 @@
+{-# LANGUAGE TypeApplications #-}
+
+module Main (main) where
+
+import Data.Bits (Bits, shiftL, shiftR, (.&.))
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Char8 as BSC
+import qualified Data.ByteString.Lazy as BSL
+import qualified Data.ByteString.Lazy.Char8 as BSLC
+import Data.Hash.Mono.FNV (fnv1, fnv1a)
+import Data.Hash.Mono.HalfSipHash
+import Data.Hash.Mono.SipHash
+import Data.Int (Int16, Int32, Int64, Int8)
+import Data.Text (Text)
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as TE
+import Data.WideWord.Word128 (Word128)
+import Data.WideWord.Word256 (Word256)
+import Data.Word (Word16, Word32, Word64, Word8)
+import qualified Test.Data.ByteFoldable
+import qualified Test.Data.FNV
+import qualified Test.Data.HalfSipHash
+import qualified Test.Data.SipHash
+import Test.Tasty (TestTree, defaultMain, testGroup)
+import Test.Tasty.HUnit (assertBool, testCase, (@?=))
+
+main :: IO ()
+main = defaultMain tests
+
+{- | Code points spanning the one- to four-byte UTF-8 ranges, including the
+boundaries between them.
+-}
+charEncodingInput :: String
+charEncodingInput = "\x00\x7f\x80\x7ff\x800\xffff\x10000\x1234\x1F600\x10FFFF"
+
+-- | UTF-8 bytes of a 'Char'; lone surrogates use the 3-byte WTF-8 form.
+charBytes :: Char -> [Word8]
+charBytes c
+    | n < 0x80 = [fromIntegral n]
+    | n < 0x800 = [byte 0xc0 6, byte 0x80 0]
+    | n < 0x10000 = [byte 0xe0 12, byte 0x80 6, byte 0x80 0]
+    | otherwise = [byte 0xf0 18, byte 0x80 12, byte 0x80 6, byte 0x80 0]
+  where
+    n = fromEnum c
+    byte mask shift = fromIntegral (mask + ((n `shiftR` shift) .&. 0x3f))
+
+-- | UTF-8 bytes of a 'String'.
+utf8 :: String -> BS.ByteString
+utf8 = TE.encodeUtf8 . T.pack
+
+-- | Big-endian bytes of a fixed-width word.
+wordBytes :: (Bits a, Integral a) => Int -> a -> [Word8]
+wordBytes width w =
+    [fromIntegral (w `shiftR` (8 * i)) | i <- [width - 1, width - 2 .. 0]]
+
+packWords :: (Bits a, Integral a) => Int -> [a] -> BS.ByteString
+packWords width = BS.pack . concatMap (wordBytes width)
+
+word16Samples :: [Word16]
+word16Samples = [0x0102, 0x0304, 0xfffe]
+
+word32Samples :: [Word32]
+word32Samples = [0x01020304, 0x05060708, 0xdeadbeef]
+
+word64Samples :: [Word64]
+word64Samples = [0x0102030405060708, 0x1112131415161718, 0xdeadbeefcafed00d]
+
+-- | Boundary values for the signed element types.
+int8Samples :: [Int8]
+int8Samples = [minBound, -1, 0, 1, maxBound]
+
+int16Samples :: [Int16]
+int16Samples = [minBound, -1, 0, 1, maxBound]
+
+int32Samples :: [Int32]
+int32Samples = [minBound, -1, 0, 1, maxBound]
+
+int64Samples :: [Int64]
+int64Samples = [minBound, -1, 0, 1, maxBound]
+
+-- | Big-endian two's-complement encoding of the signed sample lists.
+int8SampleBytes :: BS.ByteString
+int8SampleBytes = BS.pack [0x80, 0xff, 0x00, 0x01, 0x7f]
+
+int16SampleBytes :: BS.ByteString
+int16SampleBytes =
+    BS.pack [0x80, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x7f, 0xff]
+
+int32SampleBytes :: BS.ByteString
+int32SampleBytes =
+    BS.pack
+        [ 0x80
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0xff
+        , 0xff
+        , 0xff
+        , 0xff
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x01
+        , 0x7f
+        , 0xff
+        , 0xff
+        , 0xff
+        ]
+
+int64SampleBytes :: BS.ByteString
+int64SampleBytes =
+    BS.pack
+        [ 0x80
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0xff
+        , 0xff
+        , 0xff
+        , 0xff
+        , 0xff
+        , 0xff
+        , 0xff
+        , 0xff
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x00
+        , 0x01
+        , 0x7f
+        , 0xff
+        , 0xff
+        , 0xff
+        , 0xff
+        , 0xff
+        , 0xff
+        , 0xff
+        ]
+
+word128Sample :: Word128
+word128Sample =
+    (fromIntegral (0x0102030405060708 :: Word64) `shiftL` 64)
+        + fromIntegral (0x1112131415161718 :: Word64)
+
+word256Sample :: Word256
+word256Sample =
+    (fromIntegral (0x2122232425262728 :: Word64) `shiftL` 192)
+        + (fromIntegral (0x3132333435363738 :: Word64) `shiftL` 128)
+        + (fromIntegral (0x4142434445464748 :: Word64) `shiftL` 64)
+        + fromIntegral (0x5152535455565758 :: Word64)
+
+tests :: TestTree
+tests =
+    testGroup
+        "Tests"
+        [ goldenSamples
+        , inputEncoding
+        , encodingCollisions
+        , Test.Data.ByteFoldable.tests
+        ]
+
+{- | Hashes must match the reference vectors in 'Test.Data.SipHash',
+'Test.Data.HalfSipHash', and 'Test.Data.FNV'.
+-}
+goldenSamples :: TestTree
+goldenSamples =
+    testGroup
+        "Golden Samples"
+        [ testGroup
+            "FNV"
+            [ testCase "FNV1  Word32 String matches UTF-8 bytes" $ fmap (fnv1 @Word32) (Test.Data.FNV.inputs @String) @?= fmap (fnv1 @Word32 . utf8) (Test.Data.FNV.inputs @String)
+            , testCase "FNV1a Word32 String matches UTF-8 bytes" $ fmap (fnv1a @Word32) (Test.Data.FNV.inputs @String) @?= fmap (fnv1a @Word32 . utf8) (Test.Data.FNV.inputs @String)
+            , testCase "FNV1  Word64 String matches UTF-8 bytes" $ fmap (fnv1 @Word64) (Test.Data.FNV.inputs @String) @?= fmap (fnv1 @Word64 . utf8) (Test.Data.FNV.inputs @String)
+            , testCase "FNV1a Word64 String matches UTF-8 bytes" $ fmap (fnv1a @Word64) (Test.Data.FNV.inputs @String) @?= fmap (fnv1a @Word64 . utf8) (Test.Data.FNV.inputs @String)
+            , testCase "FNV1  Word32 Text matches UTF-8 bytes" $ fmap (fnv1 @Word32) (Test.Data.FNV.inputs @Text) @?= fmap (fnv1 @Word32 . TE.encodeUtf8) (Test.Data.FNV.inputs @Text)
+            , testCase "FNV1a Word32 Text matches UTF-8 bytes" $ fmap (fnv1a @Word32) (Test.Data.FNV.inputs @Text) @?= fmap (fnv1a @Word32 . TE.encodeUtf8) (Test.Data.FNV.inputs @Text)
+            , testCase "FNV1  Word64 Text matches UTF-8 bytes" $ fmap (fnv1 @Word64) (Test.Data.FNV.inputs @Text) @?= fmap (fnv1 @Word64 . TE.encodeUtf8) (Test.Data.FNV.inputs @Text)
+            , testCase "FNV1a Word64 Text matches UTF-8 bytes" $ fmap (fnv1a @Word64) (Test.Data.FNV.inputs @Text) @?= fmap (fnv1a @Word64 . TE.encodeUtf8) (Test.Data.FNV.inputs @Text)
+            , testCase "FNV1a Word32 non-ASCII String" $ fmap (fnv1a @Word32) Test.Data.FNV.utf8Inputs @?= Test.Data.FNV.fnv1aVectorsForWord32Utf8
+            , testCase "FNV1a Word32 non-ASCII Text" $ fmap ((fnv1a @Word32) . T.pack) Test.Data.FNV.utf8Inputs @?= Test.Data.FNV.fnv1aVectorsForWord32Utf8
+            , testCase "FNV1a Word64 non-ASCII String" $ fmap (fnv1a @Word64) Test.Data.FNV.utf8Inputs @?= Test.Data.FNV.fnv1aVectorsForWord64Utf8
+            , testCase "FNV1a Word64 non-ASCII Text" $ fmap ((fnv1a @Word64) . T.pack) Test.Data.FNV.utf8Inputs @?= Test.Data.FNV.fnv1aVectorsForWord64Utf8
+            , testCase "FNV1  Word32 ByteString" $ fmap (fnv1 @Word32) (Test.Data.FNV.inputs @BS.ByteString) @?= Test.Data.FNV.fnv1VectorsForWord32
+            , testCase "FNV1a Word32 ByteString" $ fmap (fnv1a @Word32) (Test.Data.FNV.inputs @BS.ByteString) @?= Test.Data.FNV.fnv1aVectorsForWord32
+            , testCase "FNV1  Word64 ByteString" $ fmap (fnv1 @Word64) (Test.Data.FNV.inputs @BS.ByteString) @?= Test.Data.FNV.fnv1VectorsForWord64
+            , testCase "FNV1a Word64 ByteString" $ fmap (fnv1a @Word64) (Test.Data.FNV.inputs @BS.ByteString) @?= Test.Data.FNV.fnv1aVectorsForWord64
+            , testCase "FNV1  Word32 Lazy ByteString" $ fmap (fnv1 @Word32) (Test.Data.FNV.inputs @BSL.ByteString) @?= Test.Data.FNV.fnv1VectorsForWord32
+            , testCase "FNV1a Word32 Lazy ByteString" $ fmap (fnv1a @Word32) (Test.Data.FNV.inputs @BSL.ByteString) @?= Test.Data.FNV.fnv1aVectorsForWord32
+            , testCase "FNV1  Word64 Lazy ByteString" $ fmap (fnv1 @Word64) (Test.Data.FNV.inputs @BSL.ByteString) @?= Test.Data.FNV.fnv1VectorsForWord64
+            , testCase "FNV1a Word64 Lazy ByteString" $ fmap (fnv1a @Word64) (Test.Data.FNV.inputs @BSL.ByteString) @?= Test.Data.FNV.fnv1aVectorsForWord64
+            , testCase "FNV1  Word32 ByteString Char8" $ fmap (fnv1 @Word32) (Test.Data.FNV.inputs @BSC.ByteString) @?= Test.Data.FNV.fnv1VectorsForWord32
+            , testCase "FNV1a Word32 ByteString Char8" $ fmap (fnv1a @Word32) (Test.Data.FNV.inputs @BSC.ByteString) @?= Test.Data.FNV.fnv1aVectorsForWord32
+            , testCase "FNV1  Word64 ByteString Char8" $ fmap (fnv1 @Word64) (Test.Data.FNV.inputs @BSC.ByteString) @?= Test.Data.FNV.fnv1VectorsForWord64
+            , testCase "FNV1a Word64 ByteString Char8" $ fmap (fnv1a @Word64) (Test.Data.FNV.inputs @BSC.ByteString) @?= Test.Data.FNV.fnv1aVectorsForWord64
+            , testCase "FNV1  Word32 Lazy ByteString Char8" $ fmap (fnv1 @Word32) (Test.Data.FNV.inputs @BSLC.ByteString) @?= Test.Data.FNV.fnv1VectorsForWord32
+            , testCase "FNV1a Word32 Lazy ByteString Char8" $ fmap (fnv1a @Word32) (Test.Data.FNV.inputs @BSLC.ByteString) @?= Test.Data.FNV.fnv1aVectorsForWord32
+            , testCase "FNV1  Word64 Lazy ByteString Char8" $ fmap (fnv1 @Word64) (Test.Data.FNV.inputs @BSLC.ByteString) @?= Test.Data.FNV.fnv1VectorsForWord64
+            , testCase "FNV1a Word64 Lazy ByteString Char8" $ fmap (fnv1a @Word64) (Test.Data.FNV.inputs @BSLC.ByteString) @?= Test.Data.FNV.fnv1aVectorsForWord64
+            ]
+        , testGroup
+            "SipHash"
+            [ testCase "SipHash-2-4 Word64 String" $ fmap (sipHash @Word64 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @String) @?= Test.Data.SipHash.sipHash24VectorsForWord64
+            , testCase "SipHash-2-4 Word64 Text" $ fmap (sipHash @Word64 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @Text) @?= Test.Data.SipHash.sipHash24VectorsForWord64
+            , testCase "SipHash-2-4 Word64 ByteString" $ fmap (sipHash @Word64 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @BS.ByteString) @?= Test.Data.SipHash.sipHash24VectorsForWord64
+            , testCase "SipHash-2-4 Word64 Lazy ByteString" $ fmap (sipHash @Word64 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @BSL.ByteString) @?= Test.Data.SipHash.sipHash24VectorsForWord64
+            , testCase "SipHash-2-4 Word64 ByteString Char8" $ fmap (sipHash @Word64 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @BSC.ByteString) @?= Test.Data.SipHash.sipHash24VectorsForWord64
+            , testCase "SipHash-2-4 Word64 Lazy ByteString Char8" $ fmap (sipHash @Word64 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @BSLC.ByteString) @?= Test.Data.SipHash.sipHash24VectorsForWord64
+            , testCase "SipHash-2-4 Word128 String" $ fmap (sipHash @Word128 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @String) @?= Test.Data.SipHash.sipHash24VectorsForWord128
+            , testCase "SipHash-2-4 Word128 Text" $ fmap (sipHash @Word128 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @Text) @?= Test.Data.SipHash.sipHash24VectorsForWord128
+            , testCase "SipHash-2-4 Word128 ByteString" $ fmap (sipHash @Word128 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @BS.ByteString) @?= Test.Data.SipHash.sipHash24VectorsForWord128
+            , testCase "SipHash-2-4 Word128 Lazy ByteString" $ fmap (sipHash @Word128 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @BSL.ByteString) @?= Test.Data.SipHash.sipHash24VectorsForWord128
+            , testCase "SipHash-2-4 Word128 ByteString Char8" $ fmap (sipHash @Word128 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @BSC.ByteString) @?= Test.Data.SipHash.sipHash24VectorsForWord128
+            , testCase "SipHash-2-4 Word128 Lazy ByteString Char8" $ fmap (sipHash @Word128 2 4 Test.Data.SipHash.key) (Test.Data.SipHash.inputs @BSLC.ByteString) @?= Test.Data.SipHash.sipHash24VectorsForWord128
+            , testCase "SipHash-2-4 Word64 boundary lengths" $ fmap (sipHash @Word64 2 4 Test.Data.SipHash.key) Test.Data.SipHash.boundaryInputs @?= Test.Data.SipHash.boundaryVectorsForWord64
+            ]
+        , testGroup
+            "HalfSipHash"
+            [ testCase "HalfSipHash-2-4 Word32 String" $ fmap (halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @String) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord32
+            , testCase "HalfSipHash-2-4 Word32 Text" $ fmap (halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @Text) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord32
+            , testCase "HalfSipHash-2-4 Word32 ByteString" $ fmap (halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @BS.ByteString) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord32
+            , testCase "HalfSipHash-2-4 Word32 Lazy ByteString" $ fmap (halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @BSL.ByteString) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord32
+            , testCase "HalfSipHash-2-4 Word32 ByteString Char8" $ fmap (halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @BSC.ByteString) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord32
+            , testCase "HalfSipHash-2-4 Word32 Lazy ByteString Char8" $ fmap (halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @BSLC.ByteString) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord32
+            , testCase "HalfSipHash-2-4 Word64 String" $ fmap (halfSipHash @Word64 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @String) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord64
+            , testCase "HalfSipHash-2-4 Word64 Text" $ fmap (halfSipHash @Word64 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @Text) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord64
+            , testCase "HalfSipHash-2-4 Word64 ByteString" $ fmap (halfSipHash @Word64 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @BS.ByteString) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord64
+            , testCase "HalfSipHash-2-4 Word64 Lazy ByteString" $ fmap (halfSipHash @Word64 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @BSL.ByteString) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord64
+            , testCase "HalfSipHash-2-4 Word64 ByteString Char8" $ fmap (halfSipHash @Word64 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @BSC.ByteString) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord64
+            , testCase "HalfSipHash-2-4 Word64 Lazy ByteString Char8" $ fmap (halfSipHash @Word64 2 4 Test.Data.HalfSipHash.key) (Test.Data.HalfSipHash.inputs @BSLC.ByteString) @?= Test.Data.HalfSipHash.halfSipHash24VectorsForWord64
+            , testCase "HalfSipHash-2-4 Word32 boundary lengths" $ fmap (halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key) Test.Data.HalfSipHash.boundaryInputs @?= Test.Data.HalfSipHash.boundaryVectorsForWord32
+            ]
+        ]
+
+{- | Every element type must feed the hash the same bytes its explicit
+byte encoding does, so typed inputs and byte inputs agree.
+-}
+inputEncoding :: TestTree
+inputEncoding =
+    testGroup
+        "Input Encoding"
+        [ testGroup
+            "FNV"
+            [ testCase "FNV1a Word64 Char encoding matches bytes" $ do
+                let bytes = BS.pack (concatMap charBytes charEncodingInput)
+                bytes @?= TE.encodeUtf8 (T.pack charEncodingInput)
+                fnv1a @Word64 charEncodingInput @?= fnv1a @Word64 bytes
+                fnv1a @Word64 (T.pack charEncodingInput) @?= fnv1a @Word64 bytes
+                fnv1a @Word64 "\xD800" @?= fnv1a @Word64 (BS.pack [0xed, 0xa0, 0x80])
+            , testCase "FNV1a Word64 word elements match bytes" $ do
+                fnv1a @Word64 [word128Sample] @?= fnv1a @Word64 (BS.pack (wordBytes 16 word128Sample))
+                fnv1a @Word64 [word256Sample] @?= fnv1a @Word64 (BS.pack (wordBytes 32 word256Sample))
+            , testCase "FNV1 Word64 small word elements match bytes" $ do
+                fnv1 @Word64 word16Samples @?= fnv1 @Word64 (packWords 2 word16Samples)
+                fnv1 @Word64 word32Samples @?= fnv1 @Word64 (packWords 4 word32Samples)
+                fnv1 @Word64 word64Samples @?= fnv1 @Word64 (packWords 8 word64Samples)
+            , testCase "FNV1a Word64 small word elements match bytes" $ do
+                fnv1a @Word64 word16Samples @?= fnv1a @Word64 (packWords 2 word16Samples)
+                fnv1a @Word64 word32Samples @?= fnv1a @Word64 (packWords 4 word32Samples)
+                fnv1a @Word64 word64Samples @?= fnv1a @Word64 (packWords 8 word64Samples)
+            , testCase "FNV1a Word64 signed elements match two's complement bytes" $ do
+                fnv1a @Word64 int8Samples @?= fnv1a @Word64 int8SampleBytes
+                fnv1a @Word64 int16Samples @?= fnv1a @Word64 int16SampleBytes
+                fnv1a @Word64 int32Samples @?= fnv1a @Word64 int32SampleBytes
+                fnv1a @Word64 int64Samples @?= fnv1a @Word64 int64SampleBytes
+            ]
+        , testGroup
+            "SipHash"
+            [ testCase "SipHash-2-4 Word64 Char encoding matches bytes" $ do
+                let bytes = BS.pack (concatMap charBytes charEncodingInput)
+                bytes @?= TE.encodeUtf8 (T.pack charEncodingInput)
+                sipHash @Word64 2 4 Test.Data.SipHash.key charEncodingInput @?= sipHash @Word64 2 4 Test.Data.SipHash.key bytes
+                sipHash @Word64 2 4 Test.Data.SipHash.key (T.pack charEncodingInput) @?= sipHash @Word64 2 4 Test.Data.SipHash.key bytes
+            , testCase "SipHash-2-4 Word64 word elements match bytes" $ do
+                sipHash @Word64 2 4 Test.Data.SipHash.key [word128Sample] @?= sipHash @Word64 2 4 Test.Data.SipHash.key (BS.pack (wordBytes 16 word128Sample))
+                sipHash @Word64 2 4 Test.Data.SipHash.key [word256Sample] @?= sipHash @Word64 2 4 Test.Data.SipHash.key (BS.pack (wordBytes 32 word256Sample))
+            , testCase "SipHash-2-4 Word64 small word elements match bytes" $ do
+                sipHash @Word64 2 4 Test.Data.SipHash.key word16Samples @?= sipHash @Word64 2 4 Test.Data.SipHash.key (packWords 2 word16Samples)
+                sipHash @Word64 2 4 Test.Data.SipHash.key word32Samples @?= sipHash @Word64 2 4 Test.Data.SipHash.key (packWords 4 word32Samples)
+                sipHash @Word64 2 4 Test.Data.SipHash.key word64Samples @?= sipHash @Word64 2 4 Test.Data.SipHash.key (packWords 8 word64Samples)
+            , testCase "SipHash-2-4 Word64 signed elements match two's complement bytes" $ do
+                sipHash @Word64 2 4 Test.Data.SipHash.key int8Samples @?= sipHash @Word64 2 4 Test.Data.SipHash.key int8SampleBytes
+                sipHash @Word64 2 4 Test.Data.SipHash.key int16Samples @?= sipHash @Word64 2 4 Test.Data.SipHash.key int16SampleBytes
+                sipHash @Word64 2 4 Test.Data.SipHash.key int32Samples @?= sipHash @Word64 2 4 Test.Data.SipHash.key int32SampleBytes
+                sipHash @Word64 2 4 Test.Data.SipHash.key int64Samples @?= sipHash @Word64 2 4 Test.Data.SipHash.key int64SampleBytes
+            ]
+        , testGroup
+            "HalfSipHash"
+            [ testCase "HalfSipHash-2-4 Word32 Char encoding matches bytes" $ do
+                let bytes = BS.pack (concatMap charBytes charEncodingInput)
+                bytes @?= TE.encodeUtf8 (T.pack charEncodingInput)
+                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key charEncodingInput @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key bytes
+                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key (T.pack charEncodingInput) @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key bytes
+            , testCase "HalfSipHash-2-4 Word32 word elements match bytes" $ do
+                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key [word128Sample] @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key (BS.pack (wordBytes 16 word128Sample))
+                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key [word256Sample] @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key (BS.pack (wordBytes 32 word256Sample))
+            , testCase "HalfSipHash-2-4 Word32 small word elements match bytes" $ do
+                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key word16Samples @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key (packWords 2 word16Samples)
+                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key word32Samples @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key (packWords 4 word32Samples)
+                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key word64Samples @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key (packWords 8 word64Samples)
+            , testCase "HalfSipHash-2-4 Word32 signed elements match two's complement bytes" $ do
+                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key int8Samples @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key int8SampleBytes
+                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key int16Samples @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key int16SampleBytes
+                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key int32Samples @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key int32SampleBytes
+                halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key int64Samples @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key int64SampleBytes
+            ]
+        ]
+
+{- | Distinct texts that the old, non-prefix-free 'Char' encoding mapped to
+the same bytes must hash differently.
+-}
+encodingCollisions :: TestTree
+encodingCollisions =
+    testGroup
+        "Encoding Collisions"
+        [ testGroup
+            "FNV"
+            [ testCase "FNV1a Word64 String" $ checkCollisions (fnv1a @Word64) id
+            , testCase "FNV1a Word64 Text" $ checkCollisions (fnv1a @Word64) T.pack
+            ]
+        , testGroup
+            "SipHash"
+            [ testCase "SipHash-2-4 Word64 String" $ checkCollisions (sipHash @Word64 2 4 Test.Data.SipHash.key) id
+            , testCase "SipHash-2-4 Word64 Text" $ checkCollisions (sipHash @Word64 2 4 Test.Data.SipHash.key) T.pack
+            ]
+        , testGroup
+            "HalfSipHash"
+            [ testCase "HalfSipHash-2-4 Word32 String" $ checkCollisions (halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key) id
+            , testCase "HalfSipHash-2-4 Word32 Text" $ checkCollisions (halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key) T.pack
+            ]
+        ]
+
+checkCollisions :: (Eq a, Show a) => (t -> a) -> (String -> t) -> IO ()
+checkCollisions h mk =
+    mapM_
+        (\(label, x, y) -> assertBool label (h (mk x) /= h (mk y)))
+        collisionPairs
+
+collisionPairs :: [(String, String, String)]
+collisionPairs =
+    [ ("U+0100 and U+0001 U+0000 must not collide", "\x0100", "\x01\x00")
+    , ("U+1234 and U+0012 U+0034 must not collide", "\x1234", "\x12\x34")
+    , ("U+1F600 and U+0001 U+00F6 U+0000 must not collide", "\x1F600", "\x01\xF6\x00")
+    ]
diff --git a/test/Test/Data/ByteFoldable.hs b/test/Test/Data/ByteFoldable.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Data/ByteFoldable.hs
@@ -0,0 +1,48 @@
+{-# LANGUAGE TypeApplications #-}
+
+{- | Tests for the public 'ByteFoldable' extension point: an element type
+defined outside the library must be hashable via its own instance, and must
+produce exactly the hash of the bytes it emits.
+-}
+module Test.Data.ByteFoldable (tests) where
+
+import qualified Data.ByteString as BS
+import Data.Hash.Mono.FNV (fnv1a)
+import Data.Hash.Mono.HalfSipHash (halfSipHash)
+import Data.Hash.Mono.Internal.ByteFoldable (ByteFoldable (..))
+import Data.Hash.Mono.SipHash (sipHash)
+import Data.Word (Word32, Word64, Word8)
+import qualified Test.Data.HalfSipHash
+import qualified Test.Data.SipHash
+import Test.Tasty (TestTree, testGroup)
+import Test.Tasty.HUnit (testCase, (@?=))
+
+-- | A user-defined element with a two-byte encoding, high byte first.
+data TwoBytes = TwoBytes !Word8 !Word8
+
+instance ByteFoldable TwoBytes where
+    foldlBytes f z (TwoBytes hi lo) = f (f z hi) lo
+
+-- | The bytes the instance above emits.
+sampleBytes :: BS.ByteString
+sampleBytes = BS.pack [0x01, 0x02]
+
+sample :: TwoBytes
+sample = TwoBytes 0x01 0x02
+
+samples :: [TwoBytes]
+samples = [sample]
+
+tests :: TestTree
+tests =
+    testGroup
+        "ByteFoldable"
+        [ testCase "custom instance matches raw bytes (FNV-1a)" $
+            fnv1a @Word64 samples @?= fnv1a @Word64 sampleBytes
+        , testCase "custom instance matches raw bytes (SipHash-2-4)" $
+            sipHash @Word64 2 4 Test.Data.SipHash.key samples
+                @?= sipHash @Word64 2 4 Test.Data.SipHash.key sampleBytes
+        , testCase "custom instance matches raw bytes (HalfSipHash-2-4)" $
+            halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key samples
+                @?= halfSipHash @Word32 2 4 Test.Data.HalfSipHash.key sampleBytes
+        ]
diff --git a/test/Test/Data/FNV.hs b/test/Test/Data/FNV.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Data/FNV.hs
@@ -0,0 +1,1078 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Test.Data.FNV where
+
+import Data.String (IsString)
+import Data.Word (Word32, Word64)
+
+inputs :: (IsString a, Monoid a) => [a]
+inputs =
+    [ ""
+    , "a"
+    , "b"
+    , "c"
+    , "d"
+    , "e"
+    , "f"
+    , "fo"
+    , "foo"
+    , "foob"
+    , "fooba"
+    , "foobar"
+    , "\x00"
+    , "a\x00"
+    , "b\x00"
+    , "c\x00"
+    , "d\x00"
+    , "e\x00"
+    , "f\x00"
+    , "fo\x00"
+    , "foo\x00"
+    , "foob\x00"
+    , "fooba\x00"
+    , "foobar\x00"
+    , "ch"
+    , "cho"
+    , "chon"
+    , "chong"
+    , "chongo"
+    , "chongo "
+    , "chongo w"
+    , "chongo wa"
+    , "chongo was"
+    , "chongo was "
+    , "chongo was h"
+    , "chongo was he"
+    , "chongo was her"
+    , "chongo was here"
+    , "chongo was here!"
+    , "chongo was here!\n"
+    , "ch\x00"
+    , "cho\x00"
+    , "chon\x00"
+    , "chong\x00"
+    , "chongo\x00"
+    , "chongo \x00"
+    , "chongo w\x00"
+    , "chongo wa\x00"
+    , "chongo was\x00"
+    , "chongo was \x00"
+    , "chongo was h\x00"
+    , "chongo was he\x00"
+    , "chongo was her\x00"
+    , "chongo was here\x00"
+    , "chongo was here!\x00"
+    , "chongo was here!\n\x00"
+    , "cu"
+    , "cur"
+    , "curd"
+    , "curds"
+    , "curds "
+    , "curds a"
+    , "curds an"
+    , "curds and"
+    , "curds and "
+    , "curds and w"
+    , "curds and wh"
+    , "curds and whe"
+    , "curds and whey"
+    , "curds and whey\n"
+    , "cu\x00"
+    , "cur\x00"
+    , "curd\x00"
+    , "curds\x00"
+    , "curds \x00"
+    , "curds a\x00"
+    , "curds an\x00"
+    , "curds and\x00"
+    , "curds and \x00"
+    , "curds and w\x00"
+    , "curds and wh\x00"
+    , "curds and whe\x00"
+    , "curds and whey\x00"
+    , "curds and whey\n\x00"
+    , "hi"
+    , "hi\x00"
+    , "hello"
+    , "hello\x00"
+    , "\xff\x00\x00\x01"
+    , "\x01\x00\x00\xff"
+    , "\xff\x00\x00\x02"
+    , "\x02\x00\x00\xff"
+    , "\xff\x00\x00\x03"
+    , "\x03\x00\x00\xff"
+    , "\xff\x00\x00\x04"
+    , "\x04\x00\x00\xff"
+    , "\x40\x51\x4e\x44"
+    , "\x44\x4e\x51\x40"
+    , "\x40\x51\x4e\x4a"
+    , "\x4a\x4e\x51\x40"
+    , "\x40\x51\x4e\x54"
+    , "\x54\x4e\x51\x40"
+    , "127.0.0.1"
+    , "127.0.0.1\x00"
+    , "127.0.0.2"
+    , "127.0.0.2\x00"
+    , "127.0.0.3"
+    , "127.0.0.3\x00"
+    , "64.81.78.68"
+    , "64.81.78.68\x00"
+    , "64.81.78.74"
+    , "64.81.78.74\x00"
+    , "64.81.78.84"
+    , "64.81.78.84\x00"
+    , "feedface"
+    , "feedface\x00"
+    , "feedfacedaffdeed"
+    , "feedfacedaffdeed\x00"
+    , "feedfacedeadbeef"
+    , "feedfacedeadbeef\x00"
+    , "line 1\nline 2\nline 3"
+    , "chongo <Landon Curt Noll> /\\../\\"
+    , "chongo <Landon Curt Noll> /\\../\\\x00"
+    , "chongo (Landon Curt Noll) /\\../\\"
+    , "chongo (Landon Curt Noll) /\\../\\\x00"
+    , "http://antwrp.gsfc.nasa.gov/apod/astropix.html"
+    , "http://en.wikipedia.org/wiki/Fowler_Noll_Vo_hash"
+    , "http://epod.usra.edu/"
+    , "http://exoplanet.eu/"
+    , "http://hvo.wr.usgs.gov/cam3/"
+    , "http://hvo.wr.usgs.gov/cams/HMcam/"
+    , "http://hvo.wr.usgs.gov/kilauea/update/deformation.html"
+    , "http://hvo.wr.usgs.gov/kilauea/update/images.html"
+    , "http://hvo.wr.usgs.gov/kilauea/update/maps.html"
+    , "http://hvo.wr.usgs.gov/volcanowatch/current_issue.html"
+    , "http://neo.jpl.nasa.gov/risk/"
+    , "http://norvig.com/21-days.html"
+    , "http://primes.utm.edu/curios/home.php"
+    , "http://slashdot.org/"
+    , "http://tux.wr.usgs.gov/Maps/155.25-19.5.html"
+    , "http://volcano.wr.usgs.gov/kilaueastatus.php"
+    , "http://www.avo.alaska.edu/activity/Redoubt.php"
+    , "http://www.dilbert.com/fast/"
+    , "http://www.fourmilab.ch/gravitation/orbits/"
+    , "http://www.fpoa.net/"
+    , "http://www.ioccc.org/index.html"
+    , "http://www.isthe.com/cgi-bin/number.cgi"
+    , "http://www.isthe.com/chongo/bio.html"
+    , "http://www.isthe.com/chongo/index.html"
+    , "http://www.isthe.com/chongo/src/calc/lucas-calc"
+    , "http://www.isthe.com/chongo/tech/astro/venus2004.html"
+    , "http://www.isthe.com/chongo/tech/astro/vita.html"
+    , "http://www.isthe.com/chongo/tech/comp/c/expert.html"
+    , "http://www.isthe.com/chongo/tech/comp/calc/index.html"
+    , "http://www.isthe.com/chongo/tech/comp/fnv/index.html"
+    , "http://www.isthe.com/chongo/tech/math/number/howhigh.html"
+    , "http://www.isthe.com/chongo/tech/math/number/number.html"
+    , "http://www.isthe.com/chongo/tech/math/prime/mersenne.html"
+    , "http://www.isthe.com/chongo/tech/math/prime/mersenne.html#largest"
+    , "http://www.lavarnd.org/cgi-bin/corpspeak.cgi"
+    , "http://www.lavarnd.org/cgi-bin/haiku.cgi"
+    , "http://www.lavarnd.org/cgi-bin/rand-none.cgi"
+    , "http://www.lavarnd.org/cgi-bin/randdist.cgi"
+    , "http://www.lavarnd.org/index.html"
+    , "http://www.lavarnd.org/what/nist-test.html"
+    , "http://www.macosxhints.com/"
+    , "http://www.mellis.com/"
+    , "http://www.nature.nps.gov/air/webcams/parks/havoso2alert/havoalert.cfm"
+    , "http://www.nature.nps.gov/air/webcams/parks/havoso2alert/timelines_24.cfm"
+    , "http://www.paulnoll.com/"
+    , "http://www.pepysdiary.com/"
+    , "http://www.sciencenews.org/index/home/activity/view"
+    , "http://www.skyandtelescope.com/"
+    , "http://www.sput.nl/~rob/sirius.html"
+    , "http://www.systemexperts.com/"
+    , "http://www.tq-international.com/phpBB3/index.php"
+    , "http://www.travelquesttours.com/index.htm"
+    , "http://www.wunderground.com/global/stations/89606.html"
+    , mconcat $ replicate 10 "21701"
+    , mconcat $ replicate 10 "M21701"
+    , mconcat $ replicate 10 "2^21701-1"
+    , mconcat $ replicate 10 "\x54\xc5"
+    , mconcat $ replicate 10 "\xc5\x54"
+    , mconcat $ replicate 10 "23209"
+    , mconcat $ replicate 10 "M23209"
+    , mconcat $ replicate 10 "2^23209-1"
+    , mconcat $ replicate 10 "\x5a\xa9"
+    , mconcat $ replicate 10 "\xa9\x5a"
+    , mconcat $ replicate 10 "391581216093"
+    , mconcat $ replicate 10 "391581*2^216093-1"
+    , mconcat $ replicate 10 "\x05\xf9\x9d\x03\x4c\x81"
+    , mconcat $ replicate 10 "FEDCBA9876543210"
+    , mconcat $ replicate 10 "\xfe\xdc\xba\x98\x76\x54\x32\x10"
+    , mconcat $ replicate 10 "EFCDAB8967452301"
+    , mconcat $ replicate 10 "\xef\xcd\xab\x89\x67\x45\x23\x01"
+    , mconcat $ replicate 10 "0123456789ABCDEF"
+    , mconcat $ replicate 10 "\x01\x23\x45\x67\x89\xab\xcd\xef"
+    , mconcat $ replicate 10 "1032547698BADCFE"
+    , mconcat $ replicate 10 "\x10\x32\x54\x76\x98\xba\xdc\xfe"
+    , mconcat $ replicate 500 "\x00"
+    , mconcat $ replicate 500 "\x07"
+    , mconcat $ replicate 500 "~"
+    , mconcat $ replicate 500 "\x7f"
+    ]
+
+{- | Non-ASCII inputs for the UTF-8 golden vectors: @é@, @café@, @naïve@,
+@U+0100@, @U+1F600@, and @U+10FFFF@.
+-}
+utf8Inputs :: [String]
+utf8Inputs =
+    [ "\xe9"
+    , "caf\xe9"
+    , "na\xefve"
+    , "\x0100"
+    , "\x1F600"
+    , "\x10FFFF"
+    ]
+
+{- | FNV-1a over the UTF-8 bytes of 'utf8Inputs'. With a 32-bit result the
+values wrap modulo @2^32@.
+-}
+fnv1aVectorsForWord32Utf8 :: [Word32]
+fnv1aVectorsForWord32Utf8 =
+    [ 0x1e9de8c1
+    , 0xa82b5049
+    , 0x999a082b
+    , 0x779015e9
+    , 0x33a29608
+    , 0x41c1bc3a
+    ]
+
+-- | FNV-1a over the UTF-8 bytes of 'utf8Inputs'.
+fnv1aVectorsForWord64Utf8 :: [Word64]
+fnv1aVectorsForWord64Utf8 =
+    [ 0x0ac21707b7181e01
+    , 0x48e8823acfa40d89
+    , 0x1e858bc68a6332ab
+    , 0x0ab1f007b70b1b89
+    , 0xfeff073875020288
+    , 0x6eb1861c2c1fff5a
+    ]
+
+fnv1VectorsForWord32 :: [Word32]
+fnv1VectorsForWord32 =
+    [ 0x811c9dc5
+    , 0x050c5d7e
+    , 0x050c5d7d
+    , 0x050c5d7c
+    , 0x050c5d7b
+    , 0x050c5d7a
+    , 0x050c5d79
+    , 0x6b772514
+    , 0x408f5e13
+    , 0xb4b1178b
+    , 0xfdc80fb0
+    , 0x31f0b262
+    , 0x050c5d1f
+    , 0x70772d5a
+    , 0x6f772bc7
+    , 0x6e772a34
+    , 0x6d7728a1
+    , 0x6c77270e
+    , 0x6b77257b
+    , 0x408f5e7c
+    , 0xb4b117e9
+    , 0xfdc80fd1
+    , 0x31f0b210
+    , 0xffe8d046
+    , 0x6e772a5c
+    , 0x4197aebb
+    , 0xfcc8100f
+    , 0xfdf147fa
+    , 0xbcd44ee1
+    , 0x23382c13
+    , 0x846d619e
+    , 0x1630abdb
+    , 0xc99e89b2
+    , 0x1692c316
+    , 0x9f091bca
+    , 0x2556be9b
+    , 0x628e0e73
+    , 0x98a0bf6c
+    , 0xb10d5725
+    , 0xdd002f35
+    , 0x4197aed4
+    , 0xfcc81061
+    , 0xfdf1479d
+    , 0xbcd44e8e
+    , 0x23382c33
+    , 0x846d61e9
+    , 0x1630abba
+    , 0xc99e89c1
+    , 0x1692c336
+    , 0x9f091ba2
+    , 0x2556befe
+    , 0x628e0e01
+    , 0x98a0bf09
+    , 0xb10d5704
+    , 0xdd002f3f
+    , 0x1c4a506f
+    , 0x6e772a41
+    , 0x26978421
+    , 0xe184ff97
+    , 0x9b5e5ac6
+    , 0x5b88e592
+    , 0xaa8164b7
+    , 0x20b18c7b
+    , 0xf28025c5
+    , 0x84bb753f
+    , 0x3219925a
+    , 0x384163c6
+    , 0x54f010d7
+    , 0x8cea820c
+    , 0xe12ab8ee
+    , 0x26978453
+    , 0xe184fff3
+    , 0x9b5e5ab5
+    , 0x5b88e5b2
+    , 0xaa8164d6
+    , 0x20b18c15
+    , 0xf28025a1
+    , 0x84bb751f
+    , 0x3219922d
+    , 0x384163ae
+    , 0x54f010b2
+    , 0x8cea8275
+    , 0xe12ab8e4
+    , 0x64411eaa
+    , 0x6977223c
+    , 0x428ae474
+    , 0xb6fa7167
+    , 0x73408525
+    , 0xb78320a1
+    , 0x0caf4135
+    , 0xb78320a2
+    , 0xcdc88e80
+    , 0xb78320a3
+    , 0x8ee1dbcb
+    , 0xb78320a4
+    , 0x4ffb2716
+    , 0x860632aa
+    , 0xcc2c5c64
+    , 0x860632a4
+    , 0x2a7ec4a6
+    , 0x860632ba
+    , 0xfefe8e14
+    , 0x0a3cffd8
+    , 0xf606c108
+    , 0x0a3cffdb
+    , 0xf906c5c1
+    , 0x0a3cffda
+    , 0xf806c42e
+    , 0xc07167d7
+    , 0xc9867775
+    , 0xbf716668
+    , 0xc78435b8
+    , 0xc6717155
+    , 0xb99568cf
+    , 0x7662e0d6
+    , 0x33a7f0e2
+    , 0xc2732f95
+    , 0xb053e78f
+    , 0x3a19c02a
+    , 0xa089821e
+    , 0x31ae8f83
+    , 0x995fa9c4
+    , 0x35983f8c
+    , 0x5036a251
+    , 0x97018583
+    , 0xb4448d60
+    , 0x025dfe59
+    , 0xc5eab3af
+    , 0x7d21ba1e
+    , 0x7704cddb
+    , 0xd0071bfe
+    , 0x0ff3774c
+    , 0xb0fea0ea
+    , 0x58177303
+    , 0x4f599cda
+    , 0x3e590a47
+    , 0x965595f8
+    , 0xc37f178d
+    , 0x9711dd26
+    , 0x23c99b7f
+    , 0x6e568b17
+    , 0x43f0245b
+    , 0xbcb7a001
+    , 0x12e6dffe
+    , 0x0792f2d6
+    , 0xb966936b
+    , 0x46439ac5
+    , 0x728d49af
+    , 0xd33745c9
+    , 0xbc382a57
+    , 0x4bda1d31
+    , 0xce35ccae
+    , 0x3b6eed94
+    , 0x445c9c58
+    , 0x3db8bf9d
+    , 0x2dee116d
+    , 0xc18738da
+    , 0x5b156176
+    , 0x2aa7d593
+    , 0xb2409658
+    , 0xe1489528
+    , 0xfe1ee07e
+    , 0xe8842315
+    , 0x3a6a63a2
+    , 0x06d2c18c
+    , 0xf8ef7225
+    , 0x843d3300
+    , 0xbb24f7ae
+    , 0x878c0ec9
+    , 0xb557810f
+    , 0x57423246
+    , 0x87f7505e
+    , 0xbb809f20
+    , 0x8932abb5
+    , 0x0a9b3aa0
+    , 0xb8682a24
+    , 0xa7ac1c56
+    , 0x11409252
+    , 0xa987f517
+    , 0xf309e7ed
+    , 0xc9e8f417
+    , 0x7f447bdd
+    , 0xb929adc5
+    , 0x57022879
+    , 0xdcfd2c49
+    , 0x6edafff5
+    , 0xf04fb1f1
+    , 0xfb7de8b9
+    , 0xc5f1d7e9
+    , 0x32c1f439
+    , 0x7fd3eb7d
+    , 0x81597da5
+    , 0x05eb7a25
+    , 0x9c0fa1b5
+    , 0x53ccb1c5
+    , 0xfabece15
+    , 0x4ad745a5
+    , 0xe5bdc495
+    , 0x23b3c0a5
+    , 0xfa823dd5
+    , 0x0c6c58b9
+    , 0xe2dbccd5
+    , 0xdb7f50f9
+    ]
+
+fnv1aVectorsForWord32 :: [Word32]
+fnv1aVectorsForWord32 =
+    [ 0x811c9dc5
+    , 0xe40c292c
+    , 0xe70c2de5
+    , 0xe60c2c52
+    , 0xe10c2473
+    , 0xe00c22e0
+    , 0xe30c2799
+    , 0x6222e842
+    , 0xa9f37ed7
+    , 0x3f5076ef
+    , 0x39aaa18a
+    , 0xbf9cf968
+    , 0x050c5d1f
+    , 0x2b24d044
+    , 0x9d2c3f7f
+    , 0x7729c516
+    , 0xb91d6109
+    , 0x931ae6a0
+    , 0x052255db
+    , 0xbef39fe6
+    , 0x6150ac75
+    , 0x9aab3a3d
+    , 0x519c4c3e
+    , 0x0c1c9eb8
+    , 0x5f299f4e
+    , 0xef8580f3
+    , 0xac297727
+    , 0x4546b9c0
+    , 0xbd564e7d
+    , 0x6bdd5c67
+    , 0xdd77ed30
+    , 0xf4ca9683
+    , 0x4aeb9bd0
+    , 0xe0e67ad0
+    , 0xc2d32fa8
+    , 0x7f743fb7
+    , 0x6900631f
+    , 0xc59c990e
+    , 0x448524fd
+    , 0xd49930d5
+    , 0x1c85c7ca
+    , 0x0229fe89
+    , 0x2c469265
+    , 0xce566940
+    , 0x8bdd8ec7
+    , 0x34787625
+    , 0xd3ca6290
+    , 0xddeaf039
+    , 0xc0e64870
+    , 0xdad35570
+    , 0x5a740578
+    , 0x5b004d15
+    , 0x6a9c09cd
+    , 0x2384f10a
+    , 0xda993a47
+    , 0x8227df4f
+    , 0x4c298165
+    , 0xfc563735
+    , 0x8cb91483
+    , 0x775bf5d0
+    , 0xd5c428d0
+    , 0x34cc0ea3
+    , 0xea3b4cb7
+    , 0x8e59f029
+    , 0x2094de2b
+    , 0xa65a0ad4
+    , 0x9bbee5f4
+    , 0xbe836343
+    , 0x22d5344e
+    , 0x19a1470c
+    , 0x4a56b1ff
+    , 0x70b8e86f
+    , 0x0a5b4a39
+    , 0xb5c3f670
+    , 0x53cc3f70
+    , 0xc03b0a99
+    , 0x7259c415
+    , 0x4095108b
+    , 0x7559bdb1
+    , 0xb3bf0bbc
+    , 0x2183ff1c
+    , 0x2bd54279
+    , 0x23a156ca
+    , 0x64e2d7e4
+    , 0x683af69a
+    , 0xaed2346e
+    , 0x4f9f2cab
+    , 0x02935131
+    , 0xc48fb86d
+    , 0x2269f369
+    , 0xc18fb3b4
+    , 0x50ef1236
+    , 0xc28fb547
+    , 0x96c3bf47
+    , 0xbf8fb08e
+    , 0xf3e4d49c
+    , 0x32179058
+    , 0x280bfee6
+    , 0x30178d32
+    , 0x21addaf8
+    , 0x4217a988
+    , 0x772633d6
+    , 0x08a3d11e
+    , 0xb7e2323a
+    , 0x07a3cf8b
+    , 0x91dfb7d1
+    , 0x06a3cdf8
+    , 0x6bdd3d68
+    , 0x1d5636a7
+    , 0xd5b808e5
+    , 0x1353e852
+    , 0xbf16b916
+    , 0xa55b89ed
+    , 0x3c1a2017
+    , 0x0588b13c
+    , 0xf22f0174
+    , 0xe83641e1
+    , 0x6e69b533
+    , 0xf1760448
+    , 0x64c8bd58
+    , 0x97b4ea23
+    , 0x9a4e92e6
+    , 0xcfb14012
+    , 0xf01b2511
+    , 0x0bbb59c3
+    , 0xce524afa
+    , 0xdd16ef45
+    , 0x60648bb3
+    , 0x7fa4bcfc
+    , 0x5053ae17
+    , 0xc9302890
+    , 0x956ded32
+    , 0x9136db84
+    , 0xdf9d3323
+    , 0x32bb6cd0
+    , 0xc8f8385b
+    , 0xeb08bfba
+    , 0x62cc8e3d
+    , 0xc3e20f5c
+    , 0x39e97f17
+    , 0x7837b203
+    , 0x319e877b
+    , 0xd3e63f89
+    , 0x29b50b38
+    , 0x5ed678b8
+    , 0xb0d5b793
+    , 0x52450be5
+    , 0xfa72d767
+    , 0x95066709
+    , 0x7f52e123
+    , 0x76966481
+    , 0x063258b0
+    , 0x2ded6e8a
+    , 0xb07d7c52
+    , 0xd0c71b71
+    , 0xf684f1bd
+    , 0x868ecfa8
+    , 0xf794f684
+    , 0xd19701c3
+    , 0x346e171e
+    , 0x91f8f676
+    , 0x0bf58848
+    , 0x6317b6d1
+    , 0xafad4c54
+    , 0x0f25681e
+    , 0x91b18d49
+    , 0x7d61c12e
+    , 0x5147d25c
+    , 0x9a8b6805
+    , 0x4cd2a447
+    , 0x1e549b14
+    , 0x2fe1b574
+    , 0xcf0cd31e
+    , 0x6c471669
+    , 0x0e5eef1e
+    , 0x2bed3602
+    , 0xb26249e0
+    , 0x2c9b86a4
+    , 0xe415e2bb
+    , 0x18a98d1d
+    , 0xb7df8b7b
+    , 0x241e9075
+    , 0x063f70dd
+    , 0x0295aed9
+    , 0x56a7f781
+    , 0x253bc645
+    , 0x46610921
+    , 0x7c1577f9
+    , 0x512b2851
+    , 0x76823999
+    , 0xc0586935
+    , 0xf3415c85
+    , 0x0ae4ff65
+    , 0x58b79725
+    , 0xdea43aa5
+    , 0x2bb3be35
+    , 0xea777a45
+    , 0x8f21c305
+    , 0x5c9d0865
+    , 0xfa823dd5
+    , 0x21a27271
+    , 0x83c5c6d5
+    , 0x813b0881
+    ]
+
+fnv1VectorsForWord64 :: [Word64]
+fnv1VectorsForWord64 =
+    [ 0xcbf29ce484222325
+    , 0xaf63bd4c8601b7be
+    , 0xaf63bd4c8601b7bd
+    , 0xaf63bd4c8601b7bc
+    , 0xaf63bd4c8601b7bb
+    , 0xaf63bd4c8601b7ba
+    , 0xaf63bd4c8601b7b9
+    , 0x08326207b4eb2f34
+    , 0xd8cbc7186ba13533
+    , 0x0378817ee2ed65cb
+    , 0xd329d59b9963f790
+    , 0x340d8765a4dda9c2
+    , 0xaf63bd4c8601b7df
+    , 0x08326707b4eb37da
+    , 0x08326607b4eb3627
+    , 0x08326507b4eb3474
+    , 0x08326407b4eb32c1
+    , 0x08326307b4eb310e
+    , 0x08326207b4eb2f5b
+    , 0xd8cbc7186ba1355c
+    , 0x0378817ee2ed65a9
+    , 0xd329d59b9963f7f1
+    , 0x340d8765a4dda9b0
+    , 0x50a6d3b724a774a6
+    , 0x08326507b4eb341c
+    , 0xd8d5c8186ba98bfb
+    , 0x1ccefc7ef118dbef
+    , 0x0c92fab3ad3db77a
+    , 0x9b77794f5fdec421
+    , 0x0ac742dfe7874433
+    , 0xd7dad5766ad8e2de
+    , 0xa1bb96378e897f5b
+    , 0x5b3f9b6733a367d2
+    , 0xb07ce25cbea969f6
+    , 0x8d9e9997f9df0d6a
+    , 0x838c673d9603cb7b
+    , 0x8b5ee8a5e872c273
+    , 0x4507c4e9fb00690c
+    , 0x4c9ca59581b27f45
+    , 0xe0aca20b624e4235
+    , 0xd8d5c8186ba98b94
+    , 0x1ccefc7ef118db81
+    , 0x0c92fab3ad3db71d
+    , 0x9b77794f5fdec44e
+    , 0x0ac742dfe7874413
+    , 0xd7dad5766ad8e2a9
+    , 0xa1bb96378e897f3a
+    , 0x5b3f9b6733a367a1
+    , 0xb07ce25cbea969d6
+    , 0x8d9e9997f9df0d02
+    , 0x838c673d9603cb1e
+    , 0x8b5ee8a5e872c201
+    , 0x4507c4e9fb006969
+    , 0x4c9ca59581b27f64
+    , 0xe0aca20b624e423f
+    , 0x13998e580afa800f
+    , 0x08326507b4eb3401
+    , 0xd8d5ad186ba95dc1
+    , 0x1c72e17ef0ca4e97
+    , 0x2183c1b327c38ae6
+    , 0xb66d096c914504f2
+    , 0x404bf57ad8476757
+    , 0x887976bd815498bb
+    , 0x3afd7f02c2bf85a5
+    , 0xfc4476b0eb70177f
+    , 0x186d2da00f77ecba
+    , 0xf97140fa48c74066
+    , 0xa2b1cf49aa926d37
+    , 0x0690712cd6cf940c
+    , 0xf7045b3102b8906e
+    , 0xd8d5ad186ba95db3
+    , 0x1c72e17ef0ca4ef3
+    , 0x2183c1b327c38a95
+    , 0xb66d096c914504d2
+    , 0x404bf57ad8476736
+    , 0x887976bd815498d5
+    , 0x3afd7f02c2bf85c1
+    , 0xfc4476b0eb70175f
+    , 0x186d2da00f77eccd
+    , 0xf97140fa48c7400e
+    , 0xa2b1cf49aa926d52
+    , 0x0690712cd6cf9475
+    , 0xf7045b3102b89064
+    , 0x74f762479f9d6aea
+    , 0x08326007b4eb2b9c
+    , 0xd8c4c9186b9b1a14
+    , 0x7b495389bdbdd4c7
+    , 0x3b6dba0d69908e25
+    , 0xd6b2b17bf4b71261
+    , 0x447bfb7f98e615b5
+    , 0xd6b2b17bf4b71262
+    , 0x3bd2807f93fe1660
+    , 0xd6b2b17bf4b71263
+    , 0x3329057f8f16170b
+    , 0xd6b2b17bf4b71264
+    , 0x2a7f8a7f8a2e19b6
+    , 0x23d3767e64b2f98a
+    , 0xff768d7e4f9d86a4
+    , 0x23d3767e64b2f984
+    , 0xccd1837e334e4aa6
+    , 0x23d3767e64b2f99a
+    , 0x7691fd7e028f6754
+    , 0x34ad3b1041204318
+    , 0xa29e749ea9d201c8
+    , 0x34ad3b104120431b
+    , 0xa29e779ea9d206e1
+    , 0x34ad3b104120431a
+    , 0xa29e769ea9d2052e
+    , 0x02a17ebca4aa3497
+    , 0x229ef18bcd375c95
+    , 0x02a17dbca4aa32c8
+    , 0x229b6f8bcd3449d8
+    , 0x02a184bca4aa3ed5
+    , 0x22b3618bcd48c3ef
+    , 0x5c2c346706186f36
+    , 0xb78c410f5b84f8c2
+    , 0xed9478212b267395
+    , 0xd9bbb55c5256662f
+    , 0x8c54f0203249438a
+    , 0xbd9790b5727dc37e
+    , 0xa64e5f36c9e2b0e3
+    , 0x8fd0680da3088a04
+    , 0x67aad32c078284cc
+    , 0xb37d55d81c57b331
+    , 0x55ac0f3829057c43
+    , 0xcb27f4b8e1b6cc20
+    , 0x26caf88bcbef2d19
+    , 0x8e6e063b97e61b8f
+    , 0xb42750f7f3b7c37e
+    , 0xf3c6ba64cf7ca99b
+    , 0xebfb69b427ea80fe
+    , 0x39b50c3ed970f46c
+    , 0x5b9b177aa3eb3e8a
+    , 0x6510063ecf4ec903
+    , 0x2b3bbd2c00797c7a
+    , 0xf1d6204ff5cb4aa7
+    , 0x4836e27ccf099f38
+    , 0x82efbb0dd073b44d
+    , 0x4a80c282ffd7d4c6
+    , 0x305d1a9c9ee43bdf
+    , 0x15c366948ffc6997
+    , 0x80153ae218916e7b
+    , 0xfa23e2bdf9e2a9e1
+    , 0xd47e8d8a2333c6de
+    , 0x7e128095f688b056
+    , 0x2f5356890efcedab
+    , 0x95c2b383014f55c5
+    , 0x4727a5339ce6070f
+    , 0xb0555ecd575108e9
+    , 0x48d785770bb4af37
+    , 0x09d4701c12af02b1
+    , 0x79f031e78f3cf62e
+    , 0x52a1ee85db1b5a94
+    , 0x6bd95b2eb37fa6b8
+    , 0x74971b7077aef85d
+    , 0xb4e4fae2ffcc1aad
+    , 0x2bd48bd898b8f63a
+    , 0xe9966ac1556257f6
+    , 0x92a3d1cd078ba293
+    , 0xf81175a482e20ab8
+    , 0x5bbb3de722e73048
+    , 0x6b4f363492b9f2be
+    , 0xc2d559df73d59875
+    , 0xf75f62284bc7a8c2
+    , 0xda8dd8e116a9f1cc
+    , 0xbdc1e6ab76057885
+    , 0xfec6a4238a1224a0
+    , 0xc03f40f3223e290e
+    , 0x1ed21673466ffda9
+    , 0xdf70f906bb0dd2af
+    , 0xf3dcda369f2af666
+    , 0x9ebb11573cdcebde
+    , 0x81c72d9077fedca0
+    , 0x0ec074a31be5fb15
+    , 0x2a8b3280b6c48f20
+    , 0xfd31777513309344
+    , 0x194534a86ad006b6
+    , 0x3be6fdf46e0cfe12
+    , 0x017cc137a07eb057
+    , 0x9428fc6e7d26b54d
+    , 0x9aaa2e3603ef8ad7
+    , 0x82c6d3f3a0ccdf7d
+    , 0xc86eeea00cf09b65
+    , 0x705f8189dbb58299
+    , 0x415a7f554391ca69
+    , 0xcfe3d49fa2bdc555
+    , 0xf0f9c56039b25191
+    , 0x7075cb6abd1d32d9
+    , 0x43c94e2c8b277509
+    , 0x3cbfd4e4ea670359
+    , 0xc05887810f4d019d
+    , 0x14468ff93ac22dc5
+    , 0xebed699589d99c05
+    , 0x6d99f6df321ca5d5
+    , 0x0cd410d08c36d625
+    , 0xef1b2a2c86831d35
+    , 0x3b349c4d69ee5f05
+    , 0x55248ce88f45f035
+    , 0xaa69ca6a18a4c885
+    , 0x1fe3fce62bd816b5
+    , 0x0289a488a8df69d9
+    , 0x15e96e1613df98b5
+    , 0xe6be57375ad89b99
+    ]
+
+fnv1aVectorsForWord64 :: [Word64]
+fnv1aVectorsForWord64 =
+    [ 0xcbf29ce484222325
+    , 0xaf63dc4c8601ec8c
+    , 0xaf63df4c8601f1a5
+    , 0xaf63de4c8601eff2
+    , 0xaf63d94c8601e773
+    , 0xaf63d84c8601e5c0
+    , 0xaf63db4c8601ead9
+    , 0x08985907b541d342
+    , 0xdcb27518fed9d577
+    , 0xdd120e790c2512af
+    , 0xcac165afa2fef40a
+    , 0x85944171f73967e8
+    , 0xaf63bd4c8601b7df
+    , 0x089be207b544f1e4
+    , 0x08a61407b54d9b5f
+    , 0x08a2ae07b54ab836
+    , 0x0891b007b53c4869
+    , 0x088e4a07b5396540
+    , 0x08987c07b5420ebb
+    , 0xdcb28a18fed9f926
+    , 0xdd1270790c25b935
+    , 0xcac146afa2febf5d
+    , 0x8593d371f738acfe
+    , 0x34531ca7168b8f38
+    , 0x08a25607b54a22ae
+    , 0xf5faf0190cf90df3
+    , 0xf27397910b3221c7
+    , 0x2c8c2b76062f22e0
+    , 0xe150688c8217b8fd
+    , 0xf35a83c10e4f1f87
+    , 0xd1edd10b507344d0
+    , 0x2a5ee739b3ddb8c3
+    , 0xdcfb970ca1c0d310
+    , 0x4054da76daa6da90
+    , 0xf70a2ff589861368
+    , 0x4c628b38aed25f17
+    , 0x9dd1f6510f78189f
+    , 0xa3de85bd491270ce
+    , 0x858e2fa32a55e61d
+    , 0x46810940eff5f915
+    , 0xf5fadd190cf8edaa
+    , 0xf273ed910b32b3e9
+    , 0x2c8c5276062f6525
+    , 0xe150b98c821842a0
+    , 0xf35aa3c10e4f55e7
+    , 0xd1ed680b50729265
+    , 0x2a5f0639b3dded70
+    , 0xdcfbaa0ca1c0f359
+    , 0x4054ba76daa6a430
+    , 0xf709c7f5898562b0
+    , 0x4c62e638aed2f9b8
+    , 0x9dd1a8510f779415
+    , 0xa3de2abd4911d62d
+    , 0x858e0ea32a55ae0a
+    , 0x46810f40eff60347
+    , 0xc33bce57bef63eaf
+    , 0x08a24307b54a0265
+    , 0xf5b9fd190cc18d15
+    , 0x4c968290ace35703
+    , 0x07174bd5c64d9350
+    , 0x5a294c3ff5d18750
+    , 0x05b3c1aeb308b843
+    , 0xb92a48da37d0f477
+    , 0x73cdddccd80ebc49
+    , 0xd58c4c13210a266b
+    , 0xe78b6081243ec194
+    , 0xb096f77096a39f34
+    , 0xb425c54ff807b6a3
+    , 0x23e520e2751bb46e
+    , 0x1a0b44ccfe1385ec
+    , 0xf5ba4b190cc2119f
+    , 0x4c962690ace2baaf
+    , 0x0716ded5c64cda19
+    , 0x5a292c3ff5d150f0
+    , 0x05b3e0aeb308ecf0
+    , 0xb92a5eda37d119d9
+    , 0x73ce41ccd80f6635
+    , 0xd58c2c132109f00b
+    , 0xe78baf81243f47d1
+    , 0xb0968f7096a2ee7c
+    , 0xb425a84ff807855c
+    , 0x23e4e9e2751b56f9
+    , 0x1a0b4eccfe1396ea
+    , 0x54abd453bb2c9004
+    , 0x08ba5f07b55ec3da
+    , 0x337354193006cb6e
+    , 0xa430d84680aabd0b
+    , 0xa9bc8acca21f39b1
+    , 0x6961196491cc682d
+    , 0xad2bb1774799dfe9
+    , 0x6961166491cc6314
+    , 0x8d1bb3904a3b1236
+    , 0x6961176491cc64c7
+    , 0xed205d87f40434c7
+    , 0x6961146491cc5fae
+    , 0xcd3baf5e44f8ad9c
+    , 0xe3b36596127cd6d8
+    , 0xf77f1072c8e8a646
+    , 0xe3b36396127cd372
+    , 0x6067dce9932ad458
+    , 0xe3b37596127cf208
+    , 0x4b7b10fa9fe83936
+    , 0xaabafe7104d914be
+    , 0xf4d3180b3cde3eda
+    , 0xaabafd7104d9130b
+    , 0xf4cfb20b3cdb5bb1
+    , 0xaabafc7104d91158
+    , 0xf4cc4c0b3cd87888
+    , 0xe729bac5d2a8d3a7
+    , 0x74bc0524f4dfa4c5
+    , 0xe72630c5d2a5b352
+    , 0x6b983224ef8fb456
+    , 0xe73042c5d2ae266d
+    , 0x8527e324fdeb4b37
+    , 0x0a83c86fee952abc
+    , 0x7318523267779d74
+    , 0x3e66d3d56b8caca1
+    , 0x956694a5c0095593
+    , 0xcac54572bb1a6fc8
+    , 0xa7a4c9f3edebf0d8
+    , 0x7829851fac17b143
+    , 0x2c8f4c9af81bcf06
+    , 0xd34e31539740c732
+    , 0x3605a2ac253d2db1
+    , 0x08c11b8346f4a3c3
+    , 0x6be396289ce8a6da
+    , 0xd9b957fb7fe794c5
+    , 0x05be33da04560a93
+    , 0x0957f1577ba9747c
+    , 0xda2cc3acc24fba57
+    , 0x74136f185b29e7f0
+    , 0xb2f2b4590edb93b2
+    , 0xb3608fce8b86ae04
+    , 0x4a3a865079359063
+    , 0x5b3a7ef496880a50
+    , 0x48fae3163854c23b
+    , 0x07aaa640476e0b9a
+    , 0x2f653656383a687d
+    , 0xa1031f8e7599d79c
+    , 0xa31908178ff92477
+    , 0x097edf3c14c3fb83
+    , 0xb51ca83feaa0971b
+    , 0xdd3c0d96d784f2e9
+    , 0x86cd26a9ea767d78
+    , 0xe6b215ff54a30c18
+    , 0xec5b06a1c5531093
+    , 0x45665a929f9ec5e5
+    , 0x8c7609b4a9f10907
+    , 0x89aac3a491f0d729
+    , 0x32ce6b26e0f4a403
+    , 0x614ab44e02b53e01
+    , 0xfa6472eb6eef3290
+    , 0x9e5d75eb1948eb6a
+    , 0xb6d12ad4a8671852
+    , 0x88826f56eba07af1
+    , 0x44535bf2645bc0fd
+    , 0x169388ffc21e3728
+    , 0xf68aac9e396d8224
+    , 0x8e87d7e7472b3883
+    , 0x295c26caa8b423de
+    , 0x322c814292e72176
+    , 0x8a06550eb8af7268
+    , 0xef86d60e661bcf71
+    , 0x9e5426c87f30ee54
+    , 0xf1ea8aa826fd047e
+    , 0x0babaf9a642cb769
+    , 0x4b3341d4068d012e
+    , 0xd15605cbc30a335c
+    , 0x5b21060aed8412e5
+    , 0x45e2cda1ce6f4227
+    , 0x50ae3745033ad7d4
+    , 0xaa4588ced46bf414
+    , 0xc1b0056c4a95467e
+    , 0x56576a71de8b4089
+    , 0xbf20965fa6dc927e
+    , 0x569f8383c2040882
+    , 0xe1e772fba08feca0
+    , 0x4ced94af97138ac4
+    , 0xc4112ffb337a82fb
+    , 0xd64a4fd41de38b7d
+    , 0x4cfc32329edebcbb
+    , 0x0803564445050395
+    , 0xaa1574ecf4642ffd
+    , 0x694bc4e54cc315f9
+    , 0xa3d7cb273b011721
+    , 0x577c2f8b6115bfa5
+    , 0xb7ec8c1a769fb4c1
+    , 0x5d5cfce63359ab19
+    , 0x33b96c3cd65b5f71
+    , 0xd845097780602bb9
+    , 0x84d47645d02da3d5
+    , 0x83544f33b58773a5
+    , 0x9175cbb2160836c5
+    , 0xc71b3bc175e72bc5
+    , 0x636806ac222ec985
+    , 0xb6ef0e6950f52ed5
+    , 0xead3d8a0f3dfdaa5
+    , 0x922908fe9a861ba5
+    , 0x6d4821de275fd5c5
+    , 0x1fe3fce62bd816b5
+    , 0xc23e9fccd6f70591
+    , 0xc1af12bdfe16b5b5
+    , 0x39e9f18f2f85e221
+    ]
diff --git a/test/Test/Data/HalfSipHash.hs b/test/Test/Data/HalfSipHash.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Data/HalfSipHash.hs
@@ -0,0 +1,182 @@
+{- | Reference HalfSipHash-2-4 vectors.
+
+The values are the reference implementation's output words, i.e. the
+little-endian reading of the byte arrays in
+<https://github.com/veorq/SipHash/blob/master/vectors.h>. The key bytes
+are @00 01 .. 07@.
+-}
+module Test.Data.HalfSipHash where
+
+import qualified Data.ByteString as BS
+import Data.Char (chr)
+import Data.String (IsString, fromString)
+import Data.Word (Word32, Word64)
+
+key :: Word64
+key = 0x0001020304050607
+
+inputs :: (IsString a, Monoid a) => [a]
+inputs = fmap (\n -> fromString $ fmap chr [0 .. n - 1]) [0 .. 64 - 1]
+
+-- | Inputs around the 256-byte length-byte wrap (254-513 bytes).
+boundaryInputs :: [BS.ByteString]
+boundaryInputs = [BS.replicate n 97 | n <- [254, 255, 256, 257, 258, 511, 512, 513]]
+
+{- | HalfSipHash-2-4-32 reference output words for the empty input and
+the prefixes of @00 01 ..@.
+-}
+halfSipHash24VectorsForWord32 :: [Word32]
+halfSipHash24VectorsForWord32 =
+    [ 0x5b9f35a9
+    , 0xb85a4727
+    , 0x03a662fa
+    , 0x04e7fe8a
+    , 0x89466e2a
+    , 0x69b6fac5
+    , 0x23fc6358
+    , 0xc563cf8b
+    , 0x8f84b8d0
+    , 0x79e706f8
+    , 0x3479b094
+    , 0x50300808
+    , 0x2f87f057
+    , 0xff63e677
+    , 0x7cf8ffd6
+    , 0x972bfe74
+    , 0x84acb5d9
+    , 0x5b6474c4
+    , 0x9b8d5b46
+    , 0x87e3ef7b
+    , 0x45104de3
+    , 0xb3623f61
+    , 0xfe67f370
+    , 0xbdb8ade6
+    , 0x630c4027
+    , 0x75787826
+    , 0x5f7b564f
+    , 0x69e6b03a
+    , 0x004064b0
+    , 0xb40f67ff
+    , 0x8b339e50
+    , 0x1a9f585d
+    , 0x1221e7fe
+    , 0x59327533
+    , 0x8c4f436a
+    , 0x29b728fe
+    , 0xecc65ce7
+    , 0x548d7e69
+    , 0x0f8b6863
+    , 0xb4620b65
+    , 0x4018bcb6
+    , 0x0545075d
+    , 0x2efd4224
+    , 0x3a86b77b
+    , 0x48d50577
+    , 0xb10852d7
+    , 0xc899d4b6
+    , 0x2e209208
+    , 0xe32ce169
+    , 0xe580b58d
+    , 0xc6649736
+    , 0x04026e01
+    , 0xd4f3853b
+    , 0xbe66dbfe
+    , 0x3a2a691e
+    , 0xc08489c6
+    , 0x40b9c5a5
+    , 0x8ce8e99b
+    , 0x4081bc7d
+    , 0xc58e077c
+    , 0x736ce7d4
+    , 0xb9cb8f42
+    , 0x7a9983bd
+    , 0x744aea59
+    ]
+
+{- | HalfSipHash-2-4-64 reference output words. Each result is the
+little-endian reading of an 8-byte @vectors_hsip64@ entry: the first
+32-bit output word is the low half and the second is the high half.
+-}
+halfSipHash24VectorsForWord64 :: [Word64]
+halfSipHash24VectorsForWord64 =
+    [ 0xc83cb8b9591f8d21
+    , 0x157338f8122455be
+    , 0x57eb507cef394f06
+    , 0x790606f7451a0fce
+    , 0xa12ee55b178ae7d5
+    , 0x80b53d2f3f7c9dcb
+    , 0x25bca28a35913ece
+    , 0x84c67bb0282720ff
+    , 0x8c85e4bc20e8feed
+    , 0x07838813cccc515b
+    , 0xeef2a6069f46b095
+    , 0x48cddd94393326ae
+    , 0x99c7f5ae9f1fc77b
+    , 0x44370c5ad752235a
+    , 0x58e6e8ea70a8b13b
+    , 0x02c9814ecb0b7d21
+    , 0xb5f37b5fd2aa3673
+    , 0x6a4f4c1c64c0ad37
+    , 0xf9423e9a2bdbb2c9
+    , 0x3c36ab2080e410f9
+    , 0xdba7ee6f0a2bf51b
+    , 0xefb3e869c21d7400
+    , 0xef76a71bfa0301e2
+    , 0x731d684be510224c
+    , 0xf1a63fae45107470
+    , 0x384071393740860c
+    , 0xf0232911d89e890d
+    , 0xb8e11eb8faf56b22
+    , 0xb516001efb5f922d
+    , 0xf110ee2cd5581936
+    , 0x9d17984886af1a29
+    , 0x7c11345c157f3c86
+    , 0x6c6211d8469d7028
+    , 0x9cf8281d68778424
+    , 0x30988f52d7e42483
+    , 0xd86bea3ae1d4eff9
+    , 0xdc7642ec407ad686
+    , 0x357ea9ccec92623f
+    , 0x0921d424e72ed9cb
+    , 0x793d408d80f68d36
+    , 0x4caec8671cc8385b
+    , 0xb3ac39d48971ab95
+    , 0x24703225c0521aa9
+    , 0xeaac2895c687005b
+    , 0x5ab1dc27adf3301e
+    , 0xd44e32909a5c7f69
+    , 0x38dc5755990f5c49
+    , 0x4df9293c2a202794
+    , 0x3e3ea94bc0a8eaa9
+    , 0x1812017d73c1a4ee
+    , 0x495af6d88f562d91
+    , 0x975cffb096959156
+    , 0xe150f598795a4402
+    , 0xb21f1de76c46ec86
+    , 0xbce389d2e7699535
+    , 0x967cbb62ca051b87
+    , 0x1d5ff142f992a4a1
+    , 0x6e5b09f67f26ec12
+    , 0x9dd831b2a15e1b5d
+    , 0x54ee923f45b4cfd8
+    , 0x60e426bf902876d6
+    , 0xf35cedb7a4633531
+    , 0x9366d472b53a0bf9
+    , 0x876032bf713ca62e
+    ]
+
+{- | Reference HalfSipHash-2-4-32 output words for 'boundaryInputs'.
+The values use the same byte order as
+'halfSipHash24VectorsForWord32'.
+-}
+boundaryVectorsForWord32 :: [Word32]
+boundaryVectorsForWord32 =
+    [ 0x8bafb24d
+    , 0x80debc4f
+    , 0xccc21763
+    , 0x74aae63c
+    , 0xc110387a
+    , 0x8d238996
+    , 0xea25986f
+    , 0x79711faa
+    ]
diff --git a/test/Test/Data/SipHash.hs b/test/Test/Data/SipHash.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Data/SipHash.hs
@@ -0,0 +1,182 @@
+{- | Reference SipHash-2-4 vectors.
+
+The values are the reference implementation's output words, i.e. the
+little-endian reading of the byte arrays in
+<https://github.com/veorq/SipHash/blob/master/vectors.h>. The key bytes
+are @00 01 .. 0f@.
+-}
+module Test.Data.SipHash where
+
+import qualified Data.ByteString as BS
+import Data.Char (chr)
+import Data.String (IsString, fromString)
+import Data.WideWord.Word128 (Word128)
+import Data.Word (Word64)
+
+key :: Word128
+key = 0x000102030405060708090a0b0c0d0e0f
+
+inputs :: (IsString a, Monoid a) => [a]
+inputs = fmap (\n -> fromString $ fmap chr [0 .. n - 1]) [0 .. 64 - 1]
+
+-- | Inputs around the 256-byte length-byte wrap (254-513 bytes).
+boundaryInputs :: [BS.ByteString]
+boundaryInputs = [BS.replicate n 97 | n <- [254, 255, 256, 257, 258, 511, 512, 513]]
+
+{- | SipHash-2-4-64 reference output words for the empty input and the
+prefixes of @00 01 ..@.
+-}
+sipHash24VectorsForWord64 :: [Word64]
+sipHash24VectorsForWord64 =
+    [ 0x726fdb47dd0e0e31
+    , 0x74f839c593dc67fd
+    , 0x0d6c8009d9a94f5a
+    , 0x85676696d7fb7e2d
+    , 0xcf2794e0277187b7
+    , 0x18765564cd99a68d
+    , 0xcbc9466e58fee3ce
+    , 0xab0200f58b01d137
+    , 0x93f5f5799a932462
+    , 0x9e0082df0ba9e4b0
+    , 0x7a5dbbc594ddb9f3
+    , 0xf4b32f46226bada7
+    , 0x751e8fbc860ee5fb
+    , 0x14ea5627c0843d90
+    , 0xf723ca908e7af2ee
+    , 0xa129ca6149be45e5
+    , 0x3f2acc7f57c29bdb
+    , 0x699ae9f52cbe4794
+    , 0x4bc1b3f0968dd39c
+    , 0xbb6dc91da77961bd
+    , 0xbed65cf21aa2ee98
+    , 0xd0f2cbb02e3b67c7
+    , 0x93536795e3a33e88
+    , 0xa80c038ccd5ccec8
+    , 0xb8ad50c6f649af94
+    , 0xbce192de8a85b8ea
+    , 0x17d835b85bbb15f3
+    , 0x2f2e6163076bcfad
+    , 0xde4daaaca71dc9a5
+    , 0xa6a2506687956571
+    , 0xad87a3535c49ef28
+    , 0x32d892fad841c342
+    , 0x7127512f72f27cce
+    , 0xa7f32346f95978e3
+    , 0x12e0b01abb051238
+    , 0x15e034d40fa197ae
+    , 0x314dffbe0815a3b4
+    , 0x027990f029623981
+    , 0xcadcd4e59ef40c4d
+    , 0x9abfd8766a33735c
+    , 0x0e3ea96b5304a7d0
+    , 0xad0c42d6fc585992
+    , 0x187306c89bc215a9
+    , 0xd4a60abcf3792b95
+    , 0xf935451de4f21df2
+    , 0xa9538f0419755787
+    , 0xdb9acddff56ca510
+    , 0xd06c98cd5c0975eb
+    , 0xe612a3cb9ecba951
+    , 0xc766e62cfcadaf96
+    , 0xee64435a9752fe72
+    , 0xa192d576b245165a
+    , 0x0a8787bf8ecb74b2
+    , 0x81b3e73d20b49b6f
+    , 0x7fa8220ba3b2ecea
+    , 0x245731c13ca42499
+    , 0xb78dbfaf3a8d83bd
+    , 0xea1ad565322a1a0b
+    , 0x60e61c23a3795013
+    , 0x6606d7e446282b93
+    , 0x6ca4ecb15c5f91e1
+    , 0x9f626da15c9625f3
+    , 0xe51b38608ef25f57
+    , 0x958a324ceb064572
+    ]
+
+{- | SipHash-2-4-128 reference output words. Each result is the
+little-endian reading of a 16-byte @vectors_sip128@ entry: the first
+64-bit output word is the low half and the second is the high half.
+-}
+sipHash24VectorsForWord128 :: [Word128]
+sipHash24VectorsForWord128 =
+    [ 0x930255c71472f66de6a825ba047f81a3
+    , 0x45fc229b1159763444af996bd8c187da
+    , 0xe4ff0af6de8ba3fcc75da4a48d227781
+    , 0x51ed8529b0b6335f4ea967520cb6709c
+    , 0x7955cd7b7c6e0f7daf8f9c2dc16481f8
+    , 0x27960e69077a5254886f778059876813
+    , 0x5ea1d78f30a05e481386208b33caee14
+    , 0x3982f01fa64ab8c053c1dbd8beebf1a1
+    , 0xb49714f364e2830f61f55862baa9623b
+    , 0xed716dbb028b7fc4abbad90a06994426
+    , 0xbafbd0f3d34754c956691478c30d1100
+    , 0x18dce5816fdcb4a277666b3868c55101
+    , 0x25c13285f64d638258f35e9066b226d6
+    , 0xf752b9c44f9329d0108bc0e947e26998
+    , 0x024949e45f48c77e9cded766aceffc31
+    , 0xd9c3cf970fec087e11a8b03399e99354
+    , 0x77052385bf1533fdbb54b067caa4e26e
+    , 0x4077e47ac466c05498b88d73e8063d47
+    , 0x23f7aefe81a44d298548bf23e4e526a4
+    , 0xb12e51528920d574b0fa65cf31770178
+    , 0xeb3938e8a544933e7390223f83fc259e
+    , 0x121d073ecd14228a215a52be5a498e56
+    , 0xae0aff8e52109c469a6bd15245b5294a
+    , 0x1c69bf9a9ae28ccfe0f5a9d5dd84d1c9
+    , 0xad32618a178a2a88d850bd78ae79b42d
+    , 0x6f8f8dcbeab951507b445e2d045fce8e
+    , 0x661f147886e0ae7ee807c3b3b4530b9c
+    , 0x94eb9e122febd3bfe4eaa669af48f2ab
+    , 0xf4ae587302f335b9884b576816da6406
+    , 0xb76a7c463cfdd40ce97d33bfc49d4baa
+    , 0x87226d68d4d71a2bde6baf1f477f5cea
+    , 0x353dc4524fde2317fcfa233218b03929
+    , 0x68eb4665559d3e363efcea5eca56397c
+    , 0xcfffa94e5f9db6b6321cf0467107c677
+    , 0xde549b30f1f02509df7e84b86c98a637
+    , 0xc88c3c922e1a2407f9a8a99de6f005a7
+    , 0x11674f90ed769e1e4648c4291f7dc43d
+    , 0x2b69d3c551473c0d1a0efce601bf620d
+    , 0xb5e7be4b085efde49e667cca8b46038c
+    , 0xd92bd2d0e5cc73449c2caf3bb95b8a52
+    , 0xd83b91c6c80cae97ad5dc9951e306adf
+    , 0xdbb6705e289135e7397f852c90891180
+    , 0x5b0ccacc34ae5036bb31c2c96a3417e6
+    , 0x89df5aecdc211840aa21b7ef3734d927
+    , 0x4273cc66b1c9b1d8785e9ced9d7d2389
+    , 0x4cb150a294fa8911657d5ebf91806d4a
+    , 0x022949cf3d0efc3f89aee75560f9330e
+    , 0x1b1563dc4bd8c88ed1190b722b431ce6
+    , 0x169b2608a6559037cf82f749f5aee5f7
+    , 0x03641a20adf237a84fa5b7d00f038d43
+    , 0x3f4286f2270d7e24e304bf4feed390a5
+    , 0x38f5f9ae7cd35cb1c493fe72a1c1e25f
+    , 0x7c013a8bd03d13b26eb306bd5c32972c
+    , 0x9ed32a009f65f09f94ca6b7a2214c892
+    , 0x871d91d64108d5fb8c32d80b1150e8dc
+    , 0xda832592b52be3481279dac78449f167
+    , 0x362a1da96f16947ee94ed572cff23819
+    , 0x8e6904163024620ffe49ed46961e4874
+    , 0x1d8a3d58d0386400d8d6a998dea5fc57
+    , 0x595357d9743676d4be1cdcef1cdeec9f
+    , 0x40e772d8cb73ca6653f128eb000c04e3
+    , 0x7a0f6793591ca9ccfe1d836a9a009776
+    , 0xbd5947f0a447d505a067f52123545358
+    , 0x7cbd3f979a063e504a83502f77d15051
+    ]
+
+{- | Reference SipHash-2-4-64 output words for 'boundaryInputs'. The
+values use the same byte order as 'sipHash24VectorsForWord64'.
+-}
+boundaryVectorsForWord64 :: [Word64]
+boundaryVectorsForWord64 =
+    [ 0x0028c1ded1b851e5
+    , 0xe987ca4ff9f8404d
+    , 0x03413138de48340e
+    , 0xa5d0d8d2d9378001
+    , 0xde306f78d586b010
+    , 0xb2b283a137aa8552
+    , 0x9cb7322fb06c14a2
+    , 0xbad61d3a1333d25b
+    ]
