diff --git a/.travis.yml b/.travis.yml
new file mode 100644
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,84 @@
+# This file has been generated -- see https://github.com/hvr/multi-ghc-travis
+language: c
+sudo: false
+
+cache:
+  directories:
+    - $HOME/.cabsnap
+    - $HOME/.cabal/packages
+
+before_cache:
+  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log
+  - rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.tar
+
+matrix:
+  include:
+    - env: CABALVER=1.22 GHCVER=7.10.1
+      compiler: ": #GHC 7.10.1"
+      addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.1,alex-3.1.4,happy-1.19.5], sources: [hvr-ghc]}}
+    - env: CABALVER=1.22 GHCVER=7.10.2
+      compiler: ": #GHC 7.10.2"
+      addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.2,alex-3.1.4,happy-1.19.5], sources: [hvr-ghc]}}
+    - env: CABALVER=1.22 GHCVER=head
+      compiler: ": #GHC head"
+      addons: {apt: {packages: [cabal-install-1.22,ghc-head,alex-3.1.4,happy-1.19.5], sources: [hvr-ghc]}}
+
+  allow_failures:
+   - env: CABALVER=1.22 GHCVER=head
+
+before_install:
+  - unset CC
+  - export HAPPYVER=1.19.5
+  - export ALEXVER=3.1.4
+  - export PATH=~/.cabal/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:/opt/happy/$HAPPYVER/bin:/opt/alex/$ALEXVER/bin:$PATH
+
+install:
+  - cabal --version
+  - echo "$(ghc --version) [$(ghc --print-project-git-commit-id 2> /dev/null || echo '?')]"
+  - if [ -f $HOME/.cabal/packages/hackage.haskell.org/00-index.tar.gz ];
+    then
+      zcat $HOME/.cabal/packages/hackage.haskell.org/00-index.tar.gz >
+           $HOME/.cabal/packages/hackage.haskell.org/00-index.tar;
+    fi
+  - travis_retry cabal update
+  - "sed -i  's/^jobs:.*$/jobs: 2/' $HOME/.cabal/config"
+  - cabal install --only-dependencies --enable-tests --dry -v pipes-cacophony.cabal > installplan.txt
+  - sed -i -e '1,/^Resolving /d' installplan.txt; cat installplan.txt
+
+  # check whether current requested install-plan matches cached package-db snapshot
+  - if diff -u installplan.txt $HOME/.cabsnap/installplan.txt;
+    then
+      echo "cabal build-cache HIT";
+      rm -rfv .ghc;
+      cp -a $HOME/.cabsnap/ghc $HOME/.ghc;
+      cp -a $HOME/.cabsnap/lib $HOME/.cabsnap/share $HOME/.cabsnap/bin $HOME/.cabal/;
+    else
+      echo "cabal build-cache MISS";
+      rm -rf $HOME/.cabsnap;
+      mkdir -p $HOME/.ghc $HOME/.cabal/lib $HOME/.cabal/share $HOME/.cabal/bin;
+      cabal install --only-dependencies --enable-tests pipes-cacophony.cabal;
+      if [ "$GHCVER" = "7.10.1" ]; then cabal install Cabal-1.22.4.0; fi;
+    fi
+
+  # snapshot package-db on cache miss
+  - if [ ! -d $HOME/.cabsnap ];
+    then
+      echo "snapshotting package-db to build-cache";
+      mkdir $HOME/.cabsnap;
+      cp -a $HOME/.ghc $HOME/.cabsnap/ghc;
+      cp -a $HOME/.cabal/lib $HOME/.cabal/share $HOME/.cabal/bin installplan.txt $HOME/.cabsnap/;
+    fi
+
+  - cabal install
+
+script:
+  - cabal configure --enable-tests -v2  # -v2 provides useful information for debugging
+  - cabal build   # this builds all libraries and executables (including tests)
+  - cabal test
+  - cabal sdist   # tests that a source-distribution can be generated
+
+  # Check that the resulting source distribution can be built & installed.
+  # If there are no other `.tar.gz` files in `dist`, this can be even simpler:
+  # `cabal install --force-reinstalls dist/*-*.tar.gz`
+  - SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz &&
+   (cd dist && cabal install --force-reinstalls "$SRC_TGZ")
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org>
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,6 @@
+# pipes-cacophony
+
+[![Build Status](https://travis-ci.org/centromere/pipes-cacophony.svg?branch=master)](https://travis-ci.org/centromere/cacophony)
+[![Haskell](http://b.repl.ca/v1/language-haskell-blue.png)](http://www.haskell.org)
+
+A set of pipes to secure network connections with the [Noise](https://github.com/trevp/noise/blob/master/noise.md) protocol.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/changelog.md b/changelog.md
new file mode 100644
--- /dev/null
+++ b/changelog.md
@@ -0,0 +1,3 @@
+# 0.1.0
+
+* First version.
diff --git a/examples/echo-client/Main.hs b/examples/echo-client/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/echo-client/Main.hs
@@ -0,0 +1,65 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main where
+
+import Data.ByteString          (readFile, writeFile)
+import Data.Traversable         (forM)
+import Pipes.Network.TCP
+import Prelude                  hiding (readFile, writeFile)
+import System.Directory         (doesFileExist)
+import System.Environment       (getArgs)
+
+import Crypto.Noise.Curve
+import Crypto.Noise.Curve.Curve25519
+import Crypto.Noise.Types       (bsToSB', sbToBS')
+
+import Handshakes
+
+readKey :: FilePath -> IO (KeyPair Curve25519)
+readKey f = fmap (curveBytesToPair . bsToSB') (readFile f)
+
+genAndWriteKey :: FilePath -> IO (KeyPair Curve25519)
+genAndWriteKey f = do
+  pair@(sec, pub) <- curveGenKey
+  writeFile f $ (sbToBS' . curveSecToBytes) sec
+  writeFile (f `mappend` ".pub") $ (sbToBS' . curvePubToBytes) pub
+  return pair
+
+processKey :: FilePath -> IO (KeyPair Curve25519)
+processKey f = do
+  exists <- doesFileExist f
+  if exists then
+    readKey f
+  else
+    genAndWriteKey f
+
+main :: IO ()
+main = do
+  [host, port, htStr] <- getArgs
+  [is, rs, re] <- forM ["init_static", "resp_static", "resp_ephemeral"] processKey
+
+  let keys = HandshakeKeys is rs re
+      ht = case htStr of
+        "NN" -> NoiseNN
+        "KN" -> NoiseKN
+        "NK" -> NoiseNK
+        "KK" -> NoiseKK
+        "NE" -> NoiseNE
+        "KE" -> NoiseKE
+        "NX" -> NoiseNX
+        "KX" -> NoiseKX
+        "XN" -> NoiseXN
+        "IN" -> NoiseIN
+        "XK" -> NoiseXK
+        "IK" -> NoiseIK
+        "XE" -> NoiseXE
+        "IE" -> NoiseIE
+        "XX" -> NoiseXX
+        "IX" -> NoiseIX
+        _    -> undefined
+
+  connect host port $ \(s, _) -> do
+    let clientSender = toSocket s
+        clientReceiver = fromSocketTimeout 120000000 s 4096
+
+    processHandshake keys (clientSender, clientReceiver) ht
diff --git a/examples/echo-server/Main.hs b/examples/echo-server/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/echo-server/Main.hs
@@ -0,0 +1,80 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main where
+
+import Control.AutoUpdate       (mkAutoUpdate, defaultUpdateSettings, updateAction)
+import Control.Exception        (SomeException, displayException, handle)
+import Data.Aeson               (encode, object, (.=))
+import Data.ByteString          (ByteString, readFile, writeFile)
+import Data.ByteString.Char8    (unpack)
+import Data.ByteString.Lazy.Char8 (append)
+import Data.Traversable         (forM)
+import Data.UnixTime            (formatUnixTime, fromEpochTime)
+import Pipes.Network.TCP
+import Prelude                  hiding (readFile, writeFile)
+import System.Directory         (doesFileExist)
+import System.Environment       (getArgs)
+import System.Log.FastLogger    (toLogStr, pushLogStr, LoggerSet, newFileLoggerSet)
+import System.Posix             (epochTime)
+import System.Posix.Files       (setFileCreationMask)
+
+import Crypto.Noise.Curve
+import Crypto.Noise.Curve.Curve25519
+import Crypto.Noise.Types       (bsToSB', sbToBS')
+
+import Handshakes
+
+readKey :: FilePath -> IO (KeyPair Curve25519)
+readKey f = fmap (curveBytesToPair . bsToSB') (readFile f)
+
+genAndWriteKey :: FilePath -> IO (KeyPair Curve25519)
+genAndWriteKey f = do
+  pair@(sec, pub) <- curveGenKey
+  writeFile f $ (sbToBS' . curveSecToBytes) sec
+  writeFile (f `mappend` ".pub") $ (sbToBS' . curvePubToBytes) pub
+  return pair
+
+processKey :: FilePath -> IO (KeyPair Curve25519)
+processKey f = do
+  exists <- doesFileExist f
+  if exists then
+    readKey f
+  else
+    genAndWriteKey f
+
+main :: IO ()
+main = do
+  [port] <- getArgs
+  [is, rs, re] <- forM ["init_static", "resp_static", "resp_ephemeral"] processKey
+
+  logHandle <- openLog "debug.log"
+  au <- mkAutoUpdate defaultUpdateSettings { updateAction = getDateTime }
+  let exLogger = logException logHandle au
+      keys     = HandshakeKeys is rs re
+
+  serve HostAny port $ \(s, ip) -> do
+    let clientReceiver = fromSocketTimeout 120000000 s 4096
+        clientSender   = toSocket s
+
+    handle (exLogger ip) $ processHandshake keys (clientReceiver, clientSender)
+
+openLog :: FilePath -> IO LoggerSet
+openLog file = do
+  _ <- setFileCreationMask 0o000
+  newFileLoggerSet 1 file
+
+logException :: LoggerSet
+             -> IO ByteString
+             -> SockAddr
+             -> SomeException
+             -> IO ()
+logException ls getCachedDate ip ex = do
+  zdt <- getCachedDate
+  (pushLogStr ls . toLogStr) . (`append` "\n") . encode $
+    object [ "date"      .= unpack zdt
+           , "exception" .= displayException ex
+           , "ip"        .= show ip
+           ]
+
+getDateTime :: IO ByteString
+getDateTime = epochTime >>= formatUnixTime "%Y-%m-%d %H:%M:%S %z" . fromEpochTime
diff --git a/pipes-cacophony.cabal b/pipes-cacophony.cabal
new file mode 100644
--- /dev/null
+++ b/pipes-cacophony.cabal
@@ -0,0 +1,159 @@
+name:          pipes-cacophony
+version:       0.1.0
+synopsis:      Pipes for Noise-secured network connections.
+license:       PublicDomain
+license-file:  LICENSE
+author:        John Galt
+maintainer:    jgalt@centromere.net
+homepage:      https://github.com/centromere/pipes-cacophony
+bug-reports:   https://github.com/centromere/pipes-cacophony/issues
+category:      Pipes, Cryptography
+build-type:    Simple
+cabal-version: >=1.10
+tested-with:   GHC == 7.10.1, GHC == 7.10.2
+description:
+  A set of pipes to secure network connections with the
+  <https://github.com/trevp/noise/blob/master/noise.md Noise> protocol.
+
+extra-source-files:
+  .travis.yml
+  README.md
+  changelog.md
+  LICENSE
+
+source-repository head
+  type: git
+  location: https://github.com/centromere/pipes-cacophony.git
+
+--------------------------------------------------------------------------------
+-- FLAGS
+
+flag hlint
+
+flag build-examples
+  description: Build example executables
+  default: False
+  manual: True
+
+flag llvm
+  default: False
+  manual: True
+
+--------------------------------------------------------------------------------
+-- LIBRARY
+
+library
+  build-depends:
+    base >=4.8 && <4.9,
+    bytestring,
+    cacophony,
+    pipes
+  hs-source-dirs:   src
+  default-language: Haskell2010
+  exposed-modules:
+    Pipes.Noise
+  ghc-options:      -rtsopts -with-rtsopts=-N -Wall -fwarn-tabs
+
+  if flag(llvm)
+    ghc-options: -fllvm
+
+--------------------------------------------------------------------------------
+-- EXAMPLES
+
+executable echo-server
+  default-language: Haskell2010
+  hs-source-dirs:   examples/echo-server
+  main-is:          Main.hs
+
+  if flag(build-examples)
+    build-depends:
+      aeson,
+      async,
+      auto-update,
+      base >=4.8 && <4.9,
+      base64-bytestring,
+      bytestring,
+      cacophony,
+      directory,
+      fast-logger,
+      pipes,
+      pipes-aeson,
+      pipes-cacophony,
+      pipes-network,
+      pipes-parse,
+      text,
+      unix,
+      unix-time
+  else
+    buildable: False
+
+  ghc-options:      -threaded -rtsopts -with-rtsopts=-N -Wall -fwarn-tabs
+
+executable echo-client
+  default-language: Haskell2010
+  hs-source-dirs:   examples/echo-client
+  main-is:          Main.hs
+
+  if flag(build-examples)
+    build-depends:
+      aeson,
+      async,
+      base >=4.8 && <4.9,
+      base64-bytestring,
+      bytestring,
+      cacophony,
+      directory,
+      pipes,
+      pipes-aeson,
+      pipes-bytestring,
+      pipes-cacophony,
+      pipes-network,
+      pipes-parse,
+      text
+  else
+    buildable: False
+
+  ghc-options:      -threaded -rtsopts -with-rtsopts=-N -Wall -fwarn-tabs
+
+--------------------------------------------------------------------------------
+-- TESTS
+
+test-suite test-pipes-cacophony
+  type:             exitcode-stdio-1.0
+  main-is:          Tests.hs
+  ghc-options:      -Wall -fwarn-tabs
+  hs-source-dirs:   tests
+  default-language: Haskell2010
+
+  build-depends:
+    async,
+    base >= 4.8 && < 4.9,
+    bytestring,
+    cacophony,
+    mtl,
+    pipes,
+    pipes-cacophony,
+    QuickCheck,
+    tasty,
+    tasty-quickcheck
+
+  other-modules:
+    Handshake,
+    HandshakeStates,
+    Imports,
+    Instances,
+    Tests
+
+test-suite hlint
+  type:             exitcode-stdio-1.0
+  main-is:          hlint.hs
+  ghc-options:      -Wall -fwarn-tabs
+  hs-source-dirs:   tests
+  default-language: Haskell2010
+
+  if !flag(hlint)
+    buildable: False
+  else
+    build-depends:
+      base >= 4.8 && < 4.9,
+      hlint
diff --git a/src/Pipes/Noise.hs b/src/Pipes/Noise.hs
new file mode 100644
--- /dev/null
+++ b/src/Pipes/Noise.hs
@@ -0,0 +1,434 @@
+{-# LANGUAGE OverloadedStrings #-}
+----------------------------------------------------------------
+-- |
+-- Module      : Pipes.Noise
+-- Maintainer  : John Galt <jgalt@centromere.net>
+-- Stability   : experimental
+-- Portability : POSIX
+
+module Pipes.Noise
+  ( -- * Types
+    CipherStatePair,
+    HandshakePipe,
+    MessagePipe,
+    -- * Pipes
+    -- ** Noise_NN
+    noiseNNIPipe,
+    noiseNNRPipe,
+    -- ** Noise_KN
+    noiseKNIPipe,
+    noiseKNRPipe,
+    -- ** Noise_NK
+    noiseNKIPipe,
+    noiseNKRPipe,
+    -- ** Noise_KK
+    noiseKKIPipe,
+    noiseKKRPipe,
+    -- ** Noise_NE
+    noiseNEIPipe,
+    noiseNERPipe,
+    -- ** Noise_KE
+    noiseKEIPipe,
+    noiseKERPipe,
+    -- ** Noise_NX
+    noiseNXIPipe,
+    noiseNXRPipe,
+    -- ** Noise_KX
+    noiseKXIPipe,
+    noiseKXRPipe,
+    -- ** Noise_XN
+    noiseXNIPipe,
+    noiseXNRPipe,
+    -- ** Noise_IN
+    noiseINIPipe,
+    noiseINRPipe,
+    -- ** Noise_XK
+    noiseXKIPipe,
+    noiseXKRPipe,
+    -- ** Noise_IK
+    noiseIKIPipe,
+    noiseIKRPipe,
+    -- ** Noise_XE
+    noiseXEIPipe,
+    noiseXERPipe,
+    -- ** Noise_IE
+    noiseIEIPipe,
+    noiseIERPipe,
+    -- ** Noise_XX
+    noiseXXIPipe,
+    noiseXXRPipe,
+    -- ** Noise_IX
+    noiseIXIPipe,
+    noiseIXRPipe,
+    -- ** Noise_N
+    noiseNIPipe,
+    noiseNRPipe,
+    -- ** Noise_K
+    noiseKIPipe,
+    noiseKRPipe,
+    -- ** Noise_X
+    noiseXIPipe,
+    noiseXRPipe,
+    -- ** Message pipes
+    messageEncryptPipe,
+    messageDecryptPipe
+  ) where
+
+import Control.Concurrent.MVar (MVar, putMVar, takeMVar)
+import Control.Monad           (forever)
+import Data.ByteString         (ByteString)
+import Pipes                   (Pipe, await, yield, lift)
+
+import Crypto.Noise.Cipher     (Plaintext(..), Cipher)
+import Crypto.Noise.Curve      (Curve)
+import Crypto.Noise.Descriptors
+import Crypto.Noise.Hash       (Hash)
+import Crypto.Noise.Handshake
+import Crypto.Noise.Types      (bsToSB', sbToBS')
+
+type CipherStatePair c = (CipherState c, CipherState c)
+type HandshakePipe     = Pipe ByteString ByteString
+type MessagePipe       = Pipe ByteString ByteString
+
+emptyPT :: Plaintext
+emptyPT = Plaintext . bsToSB' $ ""
+
+noiseNNIPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseNNIPipe hs csmv = twoMessageI hs csmv noiseNNI1 noiseNNI2
+
+noiseNNRPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseNNRPipe hs csmv = twoMessageR hs csmv noiseNNR1 noiseNNR2
+
+noiseKNIPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseKNIPipe hs csmv = twoMessageI hs csmv noiseKNI1 noiseKNI2
+
+noiseKNRPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseKNRPipe hs csmv = twoMessageR hs csmv noiseKNR1 noiseKNR2
+
+noiseNKIPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseNKIPipe hs csmv = twoMessageI hs csmv noiseNKI1 noiseNKI2
+
+noiseNKRPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseNKRPipe hs csmv = twoMessageR hs csmv noiseNKR1 noiseNKR2
+
+noiseKKIPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseKKIPipe hs csmv = twoMessageI hs csmv noiseKKI1 noiseKKI2
+
+noiseKKRPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseKKRPipe hs csmv = twoMessageR hs csmv noiseKKR1 noiseKKR2
+
+noiseNEIPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseNEIPipe hs csmv = twoMessageI hs csmv noiseNEI1 noiseNEI2
+
+noiseNERPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseNERPipe hs csmv = twoMessageR hs csmv noiseNER1 noiseNER2
+
+noiseKEIPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseKEIPipe hs csmv = twoMessageI hs csmv noiseKEI1 noiseKEI2
+
+noiseKERPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseKERPipe hs csmv = twoMessageR hs csmv noiseKER1 noiseKER2
+
+noiseNXIPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseNXIPipe hs csmv = twoMessageI hs csmv noiseNXI1 noiseNXI2
+
+noiseNXRPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseNXRPipe hs csmv = twoMessageR hs csmv noiseNXR1 noiseNXR2
+
+noiseKXIPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseKXIPipe hs csmv = twoMessageI hs csmv noiseKXI1 noiseKXI2
+
+noiseKXRPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseKXRPipe hs csmv = twoMessageR hs csmv noiseKXR1 noiseKXR2
+
+noiseXNIPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseXNIPipe hs csmv = threeMessageI hs csmv noiseXNI1 noiseXNI2 noiseXNI3
+
+noiseXNRPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseXNRPipe hs csmv = threeMessageR hs csmv noiseXNR1 noiseXNR2 noiseXNR3
+
+noiseINIPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseINIPipe hs csmv = twoMessageI hs csmv noiseINI1 noiseINI2
+
+noiseINRPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseINRPipe hs csmv = twoMessageR hs csmv noiseINR1 noiseINR2
+
+noiseXKIPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseXKIPipe hs csmv = threeMessageI hs csmv noiseXKI1 noiseXKI2 noiseXKI3
+
+noiseXKRPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseXKRPipe hs csmv = threeMessageR hs csmv noiseXKR1 noiseXKR2 noiseXKR3
+
+noiseIKIPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseIKIPipe hs csmv = twoMessageI hs csmv noiseIKI1 noiseIKI2
+
+noiseIKRPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseIKRPipe hs csmv = twoMessageR hs csmv noiseIKR1 noiseIKR2
+
+noiseXEIPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseXEIPipe hs csmv = threeMessageI hs csmv noiseXEI1 noiseXEI2 noiseXEI3
+
+noiseXERPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseXERPipe hs csmv = threeMessageR hs csmv noiseXER1 noiseXER2 noiseXER3
+
+noiseIEIPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseIEIPipe hs csmv = twoMessageI hs csmv noiseIEI1 noiseIEI2
+
+noiseIERPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseIERPipe hs csmv = twoMessageR hs csmv noiseIER1 noiseIER2
+
+noiseXXIPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseXXIPipe hs csmv = threeMessageI hs csmv noiseXXI1 noiseXXI2 noiseXXI3
+
+noiseXXRPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseXXRPipe hs csmv = threeMessageR hs csmv noiseXXR1 noiseXXR2 noiseXXR3
+
+noiseIXIPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseIXIPipe hs csmv = twoMessageI hs csmv noiseIXI1 noiseIXI2
+
+noiseIXRPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseIXRPipe hs csmv = twoMessageR hs csmv noiseIXR1 noiseIXR2
+
+noiseNIPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseNIPipe hs csmv = oneMessageI hs csmv noiseNI1
+
+noiseNRPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseNRPipe hs csmv = oneMessageR hs csmv noiseNR1
+
+noiseKIPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseKIPipe hs csmv = oneMessageI hs csmv noiseKI1
+
+noiseKRPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseKRPipe hs csmv = oneMessageR hs csmv noiseKR1
+
+noiseXIPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseXIPipe hs csmv = oneMessageI hs csmv noiseXI1
+
+noiseXRPipe :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> HandshakePipe IO ()
+noiseXRPipe hs csmv = oneMessageR hs csmv noiseXR1
+
+messageEncryptPipe :: Cipher c
+            => MVar (CipherStatePair c)
+            -> MessagePipe IO ()
+messageEncryptPipe csmv = forever $ do
+  msg <- await
+
+  (encState, unused) <- lift $ takeMVar csmv
+  let pt = Plaintext . bsToSB' $ msg
+      (ct, encState') = encryptPayload pt encState
+  lift $ putMVar csmv (encState', unused)
+
+  yield ct
+
+messageDecryptPipe :: Cipher c
+            => MVar (CipherStatePair c)
+            -> MessagePipe IO ()
+messageDecryptPipe csmv = forever $ do
+  msg <- await
+
+  (unused, decState) <- lift $ takeMVar csmv
+  let (Plaintext pt, decState') = decryptPayload msg decState
+  lift $ putMVar csmv (unused, decState')
+
+  yield . sbToBS' $ pt
+
+oneMessageI :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> DescriptorIO c d h ByteString
+             -> HandshakePipe IO ()
+oneMessageI hs csmv desc1 = do
+  (msg1, cs1, _) <- lift $ writeHandshakeMsgFinal hs desc1 emptyPT
+  yield msg1
+
+  lift $ putMVar csmv (cs1, undefined)
+
+oneMessageR :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> (ByteString -> Descriptor c d h ByteString)
+             -> HandshakePipe IO ()
+oneMessageR hs csmv desc1 = do
+  msg1 <- await
+
+  let (_, cs1, _) = readHandshakeMsgFinal hs msg1 desc1
+  lift $ putMVar csmv (undefined, cs1)
+
+twoMessageI :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> DescriptorIO c d h ByteString
+             -> (ByteString -> Descriptor c d h ByteString)
+             -> HandshakePipe IO ()
+twoMessageI hs csmv desc1 desc2 = do
+  (msg1, hs') <- lift $ writeHandshakeMsg hs desc1 emptyPT
+  yield msg1
+
+  msg2 <- await
+  let (_, cs1, cs2) = readHandshakeMsgFinal hs' msg2 desc2
+  lift $ putMVar csmv (cs1, cs2)
+
+twoMessageR :: (Cipher c, Curve d, Hash h)
+             => HandshakeState c d h
+             -> MVar (CipherStatePair c)
+             -> (ByteString -> Descriptor c d h ByteString)
+             -> DescriptorIO c d h ByteString
+             -> HandshakePipe IO ()
+twoMessageR hs csmv desc1 desc2 = do
+  msg1 <- await
+
+  let (_, hs') = readHandshakeMsg hs msg1 desc1
+  (msg2, cs1, cs2) <- lift $ writeHandshakeMsgFinal hs' desc2 emptyPT
+  lift $ putMVar csmv (cs2, cs1)
+
+  yield msg2
+
+threeMessageI :: (Cipher c, Curve d, Hash h)
+              => HandshakeState c d h
+              -> MVar (CipherStatePair c)
+              -> DescriptorIO c d h ByteString
+              -> (ByteString -> Descriptor c d h ByteString)
+              -> DescriptorIO c d h ByteString
+              -> HandshakePipe IO ()
+threeMessageI hs csmv desc1 desc2 desc3 = do
+  (msg1, hs') <- lift $ writeHandshakeMsg hs desc1 emptyPT
+  yield msg1
+
+  msg2 <- await
+  let (_, hs'') = readHandshakeMsg hs' msg2 desc2
+  (msg3, cs1, cs2) <- lift $ writeHandshakeMsgFinal hs'' desc3 emptyPT
+  lift $ putMVar csmv (cs1, cs2)
+
+  yield msg3
+
+threeMessageR :: (Cipher c, Curve d, Hash h)
+              => HandshakeState c d h
+              -> MVar (CipherStatePair c)
+              -> (ByteString -> Descriptor c d h ByteString)
+              -> DescriptorIO c d h ByteString
+              -> (ByteString -> Descriptor c d h ByteString)
+              -> HandshakePipe IO ()
+threeMessageR hs csmv desc1 desc2 desc3 = do
+  msg1 <- await
+
+  let (_, hs') = readHandshakeMsg hs msg1 desc1
+  (msg2, hs'') <- lift $ writeHandshakeMsg hs' desc2 emptyPT
+  yield msg2
+
+  msg3 <- await
+  let (_, cs1, cs2) = readHandshakeMsgFinal hs'' msg3 desc3
+  lift $ putMVar csmv (cs2, cs1)
diff --git a/tests/Handshake.hs b/tests/Handshake.hs
new file mode 100644
--- /dev/null
+++ b/tests/Handshake.hs
@@ -0,0 +1,174 @@
+{-# LANGUAGE OverloadedStrings, RankNTypes #-}
+module Handshake where
+
+import Imports
+import Instances()
+import HandshakeStates
+
+import Control.Concurrent.Async (concurrently)
+import Control.Concurrent.MVar  (MVar, newEmptyMVar, takeMVar,
+                                 putMVar, newMVar)
+import Control.Monad            (forever)
+import Data.ByteString          (ByteString)
+
+import Crypto.Noise.Cipher.ChaChaPoly1305
+import Crypto.Noise.Curve
+import Crypto.Noise.Curve.Curve25519
+import Crypto.Noise.Types
+
+import Pipes hiding (Proxy)
+import Pipes.Noise
+
+is :: KeyPair Curve25519
+is = curveBytesToPair . bsToSB' $ "I\f\232\218A\210\230\147\FS\222\167\v}l\243!\168.\ESC\t\SYN\"\169\179A`\DC28\211\169tC"
+
+rs :: KeyPair Curve25519
+rs = curveBytesToPair . bsToSB' $ "\ETB\157\&7\DC2\252\NUL\148\172\148\133\218\207\&8\221y\144\209\168FX\224Ser_\178|\153.\FSg&"
+
+re :: KeyPair Curve25519
+re = curveBytesToPair . bsToSB' $ "<\231\151\151\180\217\146\DLEI}\160N\163iKc\162\210Y\168R\213\206&gm\169r\SUB[\\'"
+
+handshakeKeys :: HandshakeKeys
+handshakeKeys = HandshakeKeys is rs re
+
+data HandshakeType = NoiseNN
+                   | NoiseKN
+                   | NoiseNK
+                   | NoiseKK
+                   | NoiseNE
+                   | NoiseKE
+                   | NoiseNX
+                   | NoiseKX
+                   | NoiseXN
+                   | NoiseIN
+                   | NoiseXK
+                   | NoiseIK
+                   | NoiseXE
+                   | NoiseIE
+                   | NoiseXX
+                   | NoiseIX
+                   | NoiseN
+                   | NoiseK
+                   | NoiseX
+                   deriving (Eq)
+
+mkHandshakePipe :: HandshakeType
+                -> HandshakeKeys
+                -> MVar (CipherStatePair ChaChaPoly1305)
+                -> MVar (CipherStatePair ChaChaPoly1305)
+                -> (HandshakePipe IO (), HandshakePipe IO ())
+mkHandshakePipe ht hks csmv1 csmv2 =
+  case ht of
+    NoiseNN -> (noiseNNIPipe noiseNNIHS csmv1,
+                noiseNNRPipe noiseNNRHS csmv2)
+    NoiseKN -> (noiseKNIPipe (noiseKNIHS hks) csmv1,
+                noiseKNRPipe (noiseKNRHS hks) csmv2)
+    NoiseNK -> (noiseNKIPipe (noiseNKIHS hks) csmv1,
+                noiseNKRPipe (noiseNKRHS hks) csmv2)
+    NoiseKK -> (noiseKKIPipe (noiseKKIHS hks) csmv1,
+                noiseKKRPipe (noiseKKRHS hks) csmv2)
+    NoiseNE -> (noiseNEIPipe (noiseNEIHS hks) csmv1,
+                noiseNERPipe (noiseNERHS hks) csmv2)
+    NoiseKE -> (noiseKEIPipe (noiseKEIHS hks) csmv1,
+                noiseKERPipe (noiseKERHS hks) csmv2)
+    NoiseNX -> (noiseNXIPipe (noiseNXIHS hks) csmv1,
+                noiseNXRPipe (noiseNXRHS hks) csmv2)
+    NoiseKX -> (noiseKXIPipe (noiseKXIHS hks) csmv1,
+                noiseKXRPipe (noiseKXRHS hks) csmv2)
+    NoiseXN -> (noiseXNIPipe (noiseXNIHS hks) csmv1,
+                noiseXNRPipe (noiseXNRHS hks) csmv2)
+    NoiseIN -> (noiseINIPipe (noiseINIHS hks) csmv1,
+                noiseINRPipe (noiseINRHS hks) csmv2)
+    NoiseXK -> (noiseXKIPipe (noiseXKIHS hks) csmv1,
+                noiseXKRPipe (noiseXKRHS hks) csmv2)
+    NoiseIK -> (noiseIKIPipe (noiseIKIHS hks) csmv1,
+                noiseIKRPipe (noiseIKRHS hks) csmv2)
+    NoiseXE -> (noiseXEIPipe (noiseXEIHS hks) csmv1,
+                noiseXERPipe (noiseXERHS hks) csmv2)
+    NoiseIE -> (noiseIEIPipe (noiseIEIHS hks) csmv1,
+                noiseIERPipe (noiseIERHS hks) csmv2)
+    NoiseXX -> (noiseXXIPipe (noiseXXIHS hks) csmv1,
+                noiseXXRPipe (noiseXXRHS hks) csmv2)
+    NoiseIX -> (noiseIXIPipe (noiseIXIHS hks) csmv1,
+                noiseIXRPipe (noiseIXRHS hks) csmv2)
+    NoiseN  -> (noiseNIPipe  (noiseNIHS  hks) csmv1,
+                noiseNRPipe  (noiseNRHS  hks) csmv2)
+    NoiseK  -> (noiseKIPipe  (noiseKIHS  hks) csmv1,
+                noiseKRPipe  (noiseKRHS  hks) csmv2)
+    NoiseX  -> (noiseXIPipe  (noiseXIHS  hks) csmv1,
+                noiseXRPipe  (noiseXRHS  hks) csmv2)
+
+aggregator :: MVar [ByteString]
+           -> Consumer' ByteString IO ()
+aggregator mv = forever $ do
+  bs <- await
+  l <- lift $ takeMVar mv
+  lift $ putMVar mv (bs : l)
+
+mVarProducer :: MVar ByteString
+             -> Producer' ByteString IO ()
+mVarProducer mv = forever $ do
+  x <- lift $ takeMVar mv
+  yield x
+
+mVarConsumer :: MVar ByteString
+             -> Consumer' ByteString IO ()
+mVarConsumer mv = forever $ do
+  x <- await
+  lift $ putMVar mv x
+
+runPipe :: HandshakeType -> Property
+runPipe ht = ioProperty $ do
+  resultsmv <- newMVar []
+  msgmv1    <- newEmptyMVar
+  msgmv2    <- newEmptyMVar
+  csmv1     <- newEmptyMVar
+  csmv2     <- newEmptyMVar
+
+  let (initPipe, respPipe) = mkHandshakePipe ht handshakeKeys csmv1 csmv2
+
+  _ <- concurrently (runEffect (mVarProducer msgmv1 >-> initPipe >-> mVarConsumer msgmv2))
+                    (runEffect (mVarProducer msgmv2 >-> respPipe >-> mVarConsumer msgmv1))
+
+  testData <- generate . listOf1 $ arbitrary
+
+  if ht == NoiseN || ht == NoiseK || ht == NoiseX then
+    runEffect $
+      each testData
+      >-> messageEncryptPipe csmv1
+      >-> messageDecryptPipe csmv2
+      >-> aggregator resultsmv
+  else
+    runEffect $
+      each testData
+      >-> messageEncryptPipe csmv1
+      >-> messageDecryptPipe csmv2
+      >-> messageEncryptPipe csmv2
+      >-> messageDecryptPipe csmv1
+      >-> aggregator resultsmv
+
+  results <- takeMVar resultsmv
+  return $ reverse results === testData
+
+tests :: TestTree
+tests = testGroup "Handshakes"
+  [ testProperty "Noise_NN" . property . runPipe $ NoiseNN
+  , testProperty "Noise_KN" . property . runPipe $ NoiseKN
+  , testProperty "Noise_NK" . property . runPipe $ NoiseNK
+  , testProperty "Noise_KK" . property . runPipe $ NoiseKK
+  , testProperty "Noise_NE" . property . runPipe $ NoiseNE
+  , testProperty "Noise_KE" . property . runPipe $ NoiseKE
+  , testProperty "Noise_NX" . property . runPipe $ NoiseNX
+  , testProperty "Noise_KX" . property . runPipe $ NoiseKX
+  , testProperty "Noise_XN" . property . runPipe $ NoiseXN
+  , testProperty "Noise_IN" . property . runPipe $ NoiseIN
+  , testProperty "Noise_XK" . property . runPipe $ NoiseXK
+  , testProperty "Noise_IK" . property . runPipe $ NoiseIK
+  , testProperty "Noise_XE" . property . runPipe $ NoiseXE
+  , testProperty "Noise_IE" . property . runPipe $ NoiseIE
+  , testProperty "Noise_XX" . property . runPipe $ NoiseXX
+  , testProperty "Noise_IX" . property . runPipe $ NoiseIX
+  , testProperty "Noise_N"  . property . runPipe $ NoiseN
+  , testProperty "Noise_K"  . property . runPipe $ NoiseK
+  , testProperty "Noise_X"  . property . runPipe $ NoiseX
+  ]
diff --git a/tests/HandshakeStates.hs b/tests/HandshakeStates.hs
new file mode 100644
--- /dev/null
+++ b/tests/HandshakeStates.hs
@@ -0,0 +1,448 @@
+{-# LANGUAGE OverloadedStrings, RecordWildCards #-}
+module HandshakeStates where
+
+import Data.ByteString (ByteString)
+import Data.Proxy
+
+import Crypto.Noise.Descriptors
+import Crypto.Noise.Handshake
+import Crypto.Noise.Cipher
+import Crypto.Noise.Cipher.ChaChaPoly1305
+import Crypto.Noise.Curve
+import Crypto.Noise.Curve.Curve25519
+import Crypto.Noise.Hash
+import Crypto.Noise.Hash.SHA256
+import Crypto.Noise.Types
+
+data HandshakeKeys =
+  HandshakeKeys { initStatic    :: KeyPair Curve25519
+                , respStatic    :: KeyPair Curve25519
+                , respEphemeral :: KeyPair Curve25519
+                }
+
+makeHSN :: ByteString -> ScrubbedBytes
+makeHSN hs = concatSB [p, convert hs, u, a, u, b, u, c]
+  where
+    a = curveName  (Proxy :: Proxy Curve25519)
+    b = cipherName (Proxy :: Proxy ChaChaPoly1305)
+    c = hashName   (Proxy :: Proxy SHA256)
+    u = bsToSB' "_"
+    p = bsToSB' "Noise_"
+
+noiseNNIHS :: HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseNNIHS =
+  handshakeState
+  (makeHSN "NN")
+  Nothing
+  Nothing
+  Nothing
+  Nothing
+  Nothing
+
+noiseKNIHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseKNIHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "KN")
+  (Just initStatic)
+  Nothing
+  Nothing
+  Nothing
+  (Just noiseKNI0)
+
+noiseNKIHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseNKIHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "NK")
+  Nothing
+  Nothing
+  (Just (snd respStatic))
+  Nothing
+  (Just noiseNKI0)
+
+noiseKKIHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseKKIHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "KK")
+  (Just initStatic)
+  Nothing
+  (Just (snd respStatic))
+  Nothing
+  (Just noiseKKI0)
+
+noiseNEIHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseNEIHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "NE")
+  Nothing
+  Nothing
+  (Just (snd respStatic))
+  (Just (snd respEphemeral))
+  (Just noiseNEI0)
+
+noiseKEIHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseKEIHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "KE")
+  (Just initStatic)
+  Nothing
+  (Just (snd respStatic))
+  (Just (snd respEphemeral))
+  (Just noiseKEI0)
+
+noiseNXIHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseNXIHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "NX")
+  Nothing
+  Nothing
+  Nothing
+  Nothing
+  Nothing
+
+noiseKXIHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseKXIHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "KX")
+  (Just initStatic)
+  Nothing
+  (Just (snd respStatic))
+  Nothing
+  (Just noiseKXI0)
+
+noiseXNIHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseXNIHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "XN")
+  (Just initStatic)
+  Nothing
+  Nothing
+  Nothing
+  Nothing
+
+noiseINIHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseINIHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "IN")
+  (Just initStatic)
+  Nothing
+  Nothing
+  Nothing
+  Nothing
+
+noiseXKIHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseXKIHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "XK")
+  (Just initStatic)
+  Nothing
+  (Just (snd respStatic))
+  Nothing
+  (Just noiseXKI0)
+
+noiseIKIHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseIKIHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "IK")
+  (Just initStatic)
+  Nothing
+  (Just (snd respStatic))
+  Nothing
+  (Just noiseIKI0)
+
+noiseXEIHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseXEIHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "XE")
+  (Just initStatic)
+  Nothing
+  (Just (snd respStatic))
+  (Just (snd respEphemeral))
+  (Just noiseXEI0)
+
+noiseIEIHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseIEIHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "IE")
+  (Just initStatic)
+  Nothing
+  (Just (snd respStatic))
+  (Just (snd respEphemeral))
+  (Just noiseIEI0)
+
+noiseXXIHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseXXIHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "XX")
+  (Just initStatic)
+  Nothing
+  Nothing
+  Nothing
+  Nothing
+
+noiseIXIHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseIXIHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "IX")
+  (Just initStatic)
+  Nothing
+  Nothing
+  Nothing
+  Nothing
+
+noiseNIHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseNIHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "N")
+  Nothing
+  Nothing
+  (Just (snd respStatic))
+  Nothing
+  (Just noiseNI0)
+
+noiseKIHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseKIHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "K")
+  (Just initStatic)
+  Nothing
+  (Just (snd respStatic))
+  Nothing
+  (Just noiseKI0)
+
+noiseXIHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseXIHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "X")
+  (Just initStatic)
+  Nothing
+  (Just (snd respStatic))
+  Nothing
+  (Just noiseXI0)
+
+noiseNNRHS :: HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseNNRHS =
+  handshakeState
+  (makeHSN "NN")
+  Nothing
+  Nothing
+  Nothing
+  Nothing
+  Nothing
+
+noiseKNRHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseKNRHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "KN")
+  Nothing
+  Nothing
+  (Just (snd initStatic))
+  Nothing
+  (Just noiseKNR0)
+
+noiseNKRHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseNKRHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "NK")
+  (Just respStatic)
+  Nothing
+  Nothing
+  Nothing
+  (Just noiseNKR0)
+
+noiseKKRHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseKKRHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "KK")
+  (Just respStatic)
+  Nothing
+  (Just (snd initStatic))
+  Nothing
+  (Just noiseKKR0)
+
+noiseNERHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseNERHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "NE")
+  (Just respStatic)
+  (Just respEphemeral)
+  Nothing
+  Nothing
+  (Just noiseNER0)
+
+noiseKERHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseKERHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "KE")
+  (Just respStatic)
+  (Just respEphemeral)
+  (Just (snd initStatic))
+  Nothing
+  (Just noiseKER0)
+
+noiseNXRHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseNXRHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "NX")
+  (Just respStatic)
+  Nothing
+  Nothing
+  Nothing
+  Nothing
+
+noiseKXRHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseKXRHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "KX")
+  (Just respStatic)
+  Nothing
+  (Just (snd initStatic))
+  Nothing
+  (Just noiseKXR0)
+
+noiseXNRHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseXNRHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "XN")
+  Nothing
+  Nothing
+  Nothing
+  Nothing
+  Nothing
+
+noiseINRHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseINRHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "IN")
+  Nothing
+  Nothing
+  Nothing
+  Nothing
+  Nothing
+
+noiseXKRHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseXKRHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "XK")
+  (Just respStatic)
+  Nothing
+  Nothing
+  Nothing
+  (Just noiseXKR0)
+
+noiseIKRHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseIKRHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "IK")
+  (Just respStatic)
+  Nothing
+  Nothing
+  Nothing
+  (Just noiseIKR0)
+
+noiseXERHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseXERHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "XE")
+  (Just respStatic)
+  (Just respEphemeral)
+  Nothing
+  Nothing
+  (Just noiseXER0)
+
+noiseIERHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseIERHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "IE")
+  (Just respStatic)
+  (Just respEphemeral)
+  Nothing
+  Nothing
+  (Just noiseIER0)
+
+noiseXXRHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseXXRHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "XX")
+  (Just respStatic)
+  Nothing
+  Nothing
+  Nothing
+  Nothing
+
+noiseIXRHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseIXRHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "IX")
+  (Just respStatic)
+  Nothing
+  Nothing
+  Nothing
+  Nothing
+
+noiseNRHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseNRHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "N")
+  (Just respStatic)
+  Nothing
+  Nothing
+  Nothing
+  Nothing
+
+noiseKRHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseKRHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "K")
+  (Just respStatic)
+  Nothing
+  (Just (snd initStatic))
+  Nothing
+  (Just noiseKR0)
+
+noiseXRHS :: HandshakeKeys
+          -> HandshakeState ChaChaPoly1305 Curve25519 SHA256
+noiseXRHS HandshakeKeys{..} =
+  handshakeState
+  (makeHSN "X")
+  (Just respStatic)
+  Nothing
+  Nothing
+  Nothing
+  (Just noiseXR0)
+
+
diff --git a/tests/Imports.hs b/tests/Imports.hs
new file mode 100644
--- /dev/null
+++ b/tests/Imports.hs
@@ -0,0 +1,7 @@
+module Imports
+  (
+    module X
+  ) where
+
+import Test.Tasty as X
+import Test.Tasty.QuickCheck as X
diff --git a/tests/Instances.hs b/tests/Instances.hs
new file mode 100644
--- /dev/null
+++ b/tests/Instances.hs
@@ -0,0 +1,10 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+module Instances where
+
+import Data.ByteString (ByteString, pack)
+
+import Test.QuickCheck
+
+instance Arbitrary ByteString where
+  arbitrary = pack <$> arbitrary
diff --git a/tests/Tests.hs b/tests/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Tests.hs
@@ -0,0 +1,13 @@
+module Main where
+
+import Imports
+
+import qualified Handshake
+
+tests :: TestTree
+tests = testGroup "cacophony"
+  [ Handshake.tests
+  ]
+
+main :: IO ()
+main = defaultMain tests
diff --git a/tests/hlint.hs b/tests/hlint.hs
new file mode 100644
--- /dev/null
+++ b/tests/hlint.hs
@@ -0,0 +1,17 @@
+module Main where
+
+import Control.Monad
+import Language.Haskell.HLint
+import System.Environment
+import System.Exit
+
+main :: IO ()
+main = do
+  args <- getArgs
+  hints <- hlint $ [ "src"
+                   , "tests"
+                   , "examples"
+                   , "--hint=tests/.hlint"
+                   , "--cpp-define=HLINT"
+                   ] `mappend` args
+  unless (null hints) exitFailure
