diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 Joseph Abrahamson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+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 OR COPYRIGHT HOLDERS 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.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,3 @@
+#!/usr/bin/env runhaskell
+import Distribution.Simple
+main = defaultMain
diff --git a/saltine.cabal b/saltine.cabal
new file mode 100644
--- /dev/null
+++ b/saltine.cabal
@@ -0,0 +1,74 @@
+name:                saltine
+version:             0.0.0.1
+synopsis:            A Haskell libsodium binding
+description:
+
+  /NaCl/ (pronounced \"salt\") is a new easy-to-use high-speed software
+  library for network communica tion, encryption, decryption,
+  signatures, etc. NaCl's goal is to provide all of the core
+  operations needed to build higher-level cryptographic tools.
+  .
+  <http://nacl.cr.yp.to/>
+  .
+  /Sodium/ is a portable, cross-compilable, installable, packageable
+  crypto library based on NaCl, with a compatible API.
+  .
+  <https://github.com/jedisct1/libsodium>
+  .
+  /Saltine/ is a Haskell binding to the NaCl primitives going through
+  Sodium for build convenience and, eventually, portability.
+
+license:             MIT
+license-file:        LICENSE
+author:              Joseph Abrahamson
+maintainer:          Joseph Abrahamson <me@jspha.com>
+bug-reports:         http://github.com/tel/saltine/issues
+copyright:           Copyright (c) Joseph Abrahamson 2013
+category:            Cryptography
+build-type:          Simple
+cabal-version:       >=1.10
+tested-with:         GHC==7.6.3
+
+source-repository head
+  type: git
+  location: https://github.com/tel/saltine.git
+
+library
+  hs-source-dirs:     src
+  exposed-modules:
+    Crypto.Saltine
+    Crypto.Saltine.Internal.ByteSizes
+    Crypto.Saltine.Core.SecretBox
+    Crypto.Saltine.Core.Box
+    Crypto.Saltine.Core.Stream
+    Crypto.Saltine.Core.Auth
+    Crypto.Saltine.Core.OneTimeAuth
+    Crypto.Saltine.Core.Sign
+    Crypto.Saltine.Core.Hash
+    Crypto.Saltine.Core.ScalarMult
+    Crypto.Saltine.Class
+  other-modules:
+    Crypto.Saltine.Internal.Util
+  extra-libraries:    sodium
+  cc-options:         -Wall
+  ghc-options:        -Wall -funbox-strict-fields
+  default-language:   Haskell2010
+  build-depends:       
+    base >= 4.5 && < 4.7,
+    vector,
+    profunctors
+    
+test-suite tests
+  type:    exitcode-stdio-1.0
+  main-is: Main.hs
+  ghc-options: -Wall -threaded -O0 -rtsopts
+  hs-source-dirs: tests
+  default-language: Haskell2010
+  build-depends:
+    base >= 4.5 && < 4.7,
+    saltine,
+    bytestring,
+    vector,
+    QuickCheck,
+    test-framework-quickcheck2,
+    test-framework
diff --git a/src/Crypto/Saltine.hs b/src/Crypto/Saltine.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Saltine.hs
@@ -0,0 +1,29 @@
+{-# LANGUAGE ForeignFunctionInterface #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module Crypto.Saltine (
+  optimize,
+  module Crypto.Saltine.Core.SecretBox
+  ) where
+
+import Foreign.C
+import Crypto.Saltine.Core.SecretBox
+
+-- | Runs Sodiums's optimizer. This has no semantic effect, but both
+-- may boost the speed of Sodium after running it. It is recommended
+-- in production environments. It is, however, NOT thread-safe so no
+-- other Sodium functions should be called until it successfully
+-- returns.
+optimize :: IO ()
+optimize = do
+  err <- c_sodiumInit
+  case err of
+    0 -> -- everything went well
+      return ()
+    1 -> -- already initialized, we're good
+      return ()
+    _ -> -- some kind of failure
+      error "Crypto.Saltine.optimize"
+
+foreign import ccall "sodium_init" c_sodiumInit :: IO CInt
diff --git a/src/Crypto/Saltine/Class.hs b/src/Crypto/Saltine/Class.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Saltine/Class.hs
@@ -0,0 +1,55 @@
+-- |
+-- Module      : Crypto.Saltine.Class
+-- Copyright   : (c) Joseph Abrahamson 2013
+-- License     : MIT
+-- 
+-- Maintainer  : me@jspha.com
+-- Stability   : experimental
+-- Portability : non-portable
+-- 
+-- Saltine type classes
+module Crypto.Saltine.Class (
+  IsEncoding (..),
+  IsNonce (..)
+  ) where
+
+import Data.Profunctor
+import Data.Word
+import qualified Data.Vector.Storable as V
+import Control.Applicative
+
+-- | Class for all keys and nonces in Saltine which have a
+-- representation as 'V.Vector' of 'Word8'. 'encoded' is a 'Prism' of
+-- type @Prism' (V.Vector Word8) a@ compatible with "Control.Lens" and
+-- is automatically deduced.
+class IsEncoding a where
+  encode  :: a -> V.Vector Word8
+  decode  :: V.Vector Word8 -> Maybe a
+  encoded :: (Choice p, Applicative f)
+             => p a (f a) -> p (V.Vector Word8) (f (V.Vector Word8))
+  encoded = prism' encode decode
+  {-# INLINE encoded #-}
+
+-- | A generic class for interacting with nonces.
+class IsNonce n where
+  zero  :: n
+  -- ^ Some privileged nonce value.
+  nudge :: n -> n
+  -- ^ Some perturbation on nonces such that @n /= nudge n@ with high
+  -- probability. Since nonces are finite, repeats may happen in
+  -- particularly small cases, but no nonces in Saltine are so
+  -- small. This is not guaranteed to be difficult to predict---if a
+  -- nonce had an `Enum` instance `succ` would be a good
+  -- implementation excepting that `succ` is partial.
+
+-- Copied over from Control.Lens
+
+prism' :: (Applicative f, Choice p) =>
+          (a1 -> a) -> (a -> Maybe a2) -> p a2 (f a1) -> p a (f a)
+prism' bs sma = prism bs (\s -> maybe (Left s) Right (sma s))
+{-# INLINE prism' #-}
+
+prism :: (Applicative f, Choice p) =>
+         (a2 -> a1) -> (a -> Either a1 a3) -> p a3 (f a2) -> p a (f a1)
+prism bt seta = dimap seta (either pure (fmap bt)) . right'
+{-# INLINE prism #-}
diff --git a/src/Crypto/Saltine/Core/Auth.hs b/src/Crypto/Saltine/Core/Auth.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Saltine/Core/Auth.hs
@@ -0,0 +1,134 @@
+-- |
+-- Module      : Crypto.Saltine.Core.Auth
+-- Copyright   : (c) Joseph Abrahamson 2013
+-- License     : MIT
+-- 
+-- Maintainer  : me@jspha.com
+-- Stability   : experimental
+-- Portability : non-portable
+-- 
+-- Secret-key message authentication: 
+-- "Crypto.Saltine.Core.Auth"
+-- 
+-- The 'auth' function authenticates a message 'V.Vector' using a
+-- secret key The function returns an authenticator. The 'verify'
+-- function checks if it's passed a correct authenticator of a message
+-- under the given secret key.
+-- 
+-- The 'auth' function, viewed as a function of the message for a
+-- uniform random key, is designed to meet the standard notion of
+-- unforgeability. This means that an attacker cannot find
+-- authenticators for any messages not authenticated by the sender,
+-- even if the attacker has adaptively influenced the messages
+-- authenticated by the sender. For a formal definition see, e.g.,
+-- Section 2.4 of Bellare, Kilian, and Rogaway, \"The security of the
+-- cipher block chaining message authentication code,\" Journal of
+-- Computer and System Sciences 61 (2000), 362–399;
+-- <http://www-cse.ucsd.edu/~mihir/papers/cbc.html>.
+-- 
+-- Saltine does not make any promises regarding \"strong\"
+-- unforgeability; perhaps one valid authenticator can be converted
+-- into another valid authenticator for the same message. NaCl also
+-- does not make any promises regarding \"truncated unforgeability.\"
+-- 
+-- "Crypto.Saltine.Core.Auth" is currently an implementation of
+-- HMAC-SHA-512-256, i.e., the first 256 bits of
+-- HMAC-SHA-512. HMAC-SHA-512-256 is conjectured to meet the standard
+-- notion of unforgeability.
+-- 
+-- This is version 2010.08.30 of the auth.html web page.
+module Crypto.Saltine.Core.Auth (
+  Key, Authenticator,
+  newKey,
+  auth, verify
+  ) where
+
+import Crypto.Saltine.Class
+import Crypto.Saltine.Internal.Util
+import qualified Crypto.Saltine.Internal.ByteSizes as Bytes
+
+import Foreign.C
+import Foreign.Ptr
+import Data.Word
+import qualified Data.Vector.Storable as V
+
+import Control.Applicative
+
+-- $types
+
+-- | An opaque 'auth' cryptographic key.
+newtype Key = Key (V.Vector Word8) deriving (Eq, Ord)
+
+-- | An opaque 'auth' authenticator.
+newtype Authenticator = Au (V.Vector Word8) deriving (Eq, Ord)
+
+instance IsEncoding Key where
+  decode v = case V.length v == Bytes.authKey of
+    True -> Just (Key v)
+    False -> Nothing
+  {-# INLINE decode #-}
+  encode (Key v) = v
+  {-# INLINE encode #-}
+
+instance IsEncoding Authenticator where
+  decode v = case V.length v == Bytes.auth of
+    True -> Just (Au v)
+    False -> Nothing
+  {-# INLINE decode #-}
+  encode (Au v) = v
+  {-# INLINE encode #-}
+
+-- | Creates a random key of the correct size for 'auth' and 'verify'.
+newKey :: IO Key
+newKey = Key <$> randomVector Bytes.authKey
+
+-- | Computes an keyed authenticator 'V.Vector' from a message. It is
+-- infeasible to forge these authenticators without the key, even if
+-- an attacker observes many authenticators and messages and has the
+-- ability to influence the messages sent.
+auth :: Key 
+        -> V.Vector Word8
+        -- ^ Message
+        -> Authenticator
+auth (Key key) msg =
+  Au . snd . buildUnsafeCVector Bytes.auth $ \pa ->
+    constVectors [key, msg] $ \[pk, pm] ->
+    c_auth pa pm (fromIntegral $ V.length msg) pk
+
+-- | Checks to see if an authenticator is a correct proof that a
+-- message was signed by some key.
+verify :: Key
+          -> Authenticator
+          -> V.Vector Word8
+          -- ^ Message
+          -> Bool
+          -- ^ Is this message authentic?
+verify (Key key) (Au a) msg =
+  unsafeDidSucceed $ constVectors [key, msg, a] $ \[pk, pm, pa] ->
+  return $ c_auth_verify pa pm (fromIntegral $ V.length msg) pk
+
+foreign import ccall "crypto_auth"
+  c_auth :: Ptr Word8
+            -- ^ Authenticator output buffer
+            -> Ptr Word8
+            -- ^ Constant message buffer
+            -> CULLong
+            -- ^ Length of message buffer
+            -> Ptr Word8
+            -- ^ Constant key buffer
+            -> IO CInt
+            -- ^ Always 0
+
+-- | We don't even include this in the IO monad since all of the
+-- buffers are constant.
+foreign import ccall "crypto_auth_verify"
+  c_auth_verify :: Ptr Word8
+                   -- ^ Constant authenticator buffer
+                   -> Ptr Word8
+                   -- ^ Constant message buffer
+                   -> CULLong
+                   -- ^ Length of message buffer
+                   -> Ptr Word8
+                   -- ^ Constant key buffer
+                   -> CInt
+                   -- ^ Success if 0, failure if -1
diff --git a/src/Crypto/Saltine/Core/Box.hs b/src/Crypto/Saltine/Core/Box.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Saltine/Core/Box.hs
@@ -0,0 +1,306 @@
+-- |
+-- Module      : Crypto.Saltine.Core.Box
+-- Copyright   : (c) Joseph Abrahamson 2013
+-- License     : MIT
+-- 
+-- Maintainer  : me@jspha.com
+-- Stability   : experimental
+-- Portability : non-portable
+-- 
+-- Public-key authenticated encryption:
+-- "Crypto.Saltine.Core.Box"
+-- 
+-- The 'box' function encrypts and authenticates a message 'V.Vector'
+-- using the sender's secret key, the receiver's public key, and a
+-- nonce. The 'boxOpen' function verifies and decrypts a ciphertext
+-- 'V.Vector' using the receiver's secret key, the sender's public
+-- key, and a nonce. If the ciphertext fails verification, 'boxOpen'
+-- returns 'Nothing'.
+-- 
+-- The "Crypto.Saltine.Core.Box" module is designed to meet the
+-- standard notions of privacy and third-party unforgeability for a
+-- public-key authenticated-encryption scheme using nonces. For formal
+-- definitions see, e.g., Jee Hea An, "Authenticated encryption in the
+-- public-key setting: security notions and analyses,"
+-- <http://eprint.iacr.org/2001/079>.
+-- 
+-- Distinct messages between the same @{sender, receiver}@ set are
+-- required to have distinct nonces. For example, the
+-- lexicographically smaller public key can use nonce 1 for its first
+-- message to the other key, nonce 3 for its second message, nonce 5
+-- for its third message, etc., while the lexicographically larger
+-- public key uses nonce 2 for its first message to the other key,
+-- nonce 4 for its second message, nonce 6 for its third message,
+-- etc. Nonces are long enough that randomly generated nonces have
+-- negligible risk of collision.
+-- 
+-- There is no harm in having the same nonce for different messages if
+-- the @{sender, receiver}@ sets are different. This is true even if
+-- the sets overlap. For example, a sender can use the same nonce for
+-- two different messages if the messages are sent to two different
+-- public keys.
+-- 
+-- The "Crypto.Saltine.Core.Box" module is not meant to provide
+-- non-repudiation. On the contrary: the crypto_box function
+-- guarantees repudiability. A receiver can freely modify a boxed
+-- message, and therefore cannot convince third parties that this
+-- particular message came from the sender. The sender and receiver
+-- are nevertheless protected against forgeries by other parties. In
+-- the terminology of
+-- <http://groups.google.com/group/sci.crypt/msg/ec5c18b23b11d82c>,
+-- crypto_box uses "public-key authenticators" rather than "public-key
+-- signatures."
+-- 
+-- Users who want public verifiability (or receiver-assisted public
+-- verifiability) should instead use signatures (or
+-- signcryption). Signatures are documented in the
+-- "Crypto.Saltine.Core.Sign" module.
+-- 
+-- "Crypto.Saltine.Core.Box" is @curve25519xsalsa20poly1305@, a
+-- particular combination of Curve25519, Salsa20, and Poly1305
+-- specified in "Cryptography in NaCl"
+-- (<http://nacl.cr.yp.to/valid.html>). This function is conjectured
+-- to meet the standard notions of privacy and third-party
+-- unforgeability.
+-- 
+-- This is version 2010.08.30 of the box.html web page.
+module Crypto.Saltine.Core.Box (
+  SecretKey, PublicKey, Keypair, CombinedKey, Nonce,
+  newKeypair, beforeNM, newNonce,
+  box, boxOpen,
+  boxAfterNM, boxOpenAfterNM  
+  ) where
+
+import Crypto.Saltine.Class
+import Crypto.Saltine.Internal.Util
+import qualified Crypto.Saltine.Internal.ByteSizes as Bytes
+
+import Foreign.C
+import Foreign.Ptr
+import Data.Word
+import qualified Data.Vector.Storable as V
+
+import Control.Applicative
+
+-- $types
+
+-- | An opaque 'box' cryptographic secret key.
+newtype SecretKey = SK (V.Vector Word8) deriving (Eq, Ord)
+
+instance IsEncoding SecretKey where
+  decode v = case V.length v == Bytes.boxSK of
+    True -> Just (SK v)
+    False -> Nothing
+  {-# INLINE decode #-}
+  encode (SK v) = v
+  {-# INLINE encode #-}
+
+-- | An opaque 'box' cryptographic public key.
+newtype PublicKey = PK (V.Vector Word8) deriving (Eq, Ord)
+
+instance IsEncoding PublicKey where
+  decode v = case V.length v == Bytes.boxPK of
+    True -> Just (PK v)
+    False -> Nothing
+  {-# INLINE decode #-}
+  encode (PK v) = v
+  {-# INLINE encode #-}
+
+-- | A convenience type for keypairs
+type Keypair = (SecretKey, PublicKey)
+
+-- | An opaque 'boxAfterNM' cryptographic combined key.
+newtype CombinedKey = CK (V.Vector Word8) deriving (Eq, Ord)
+
+instance IsEncoding CombinedKey where
+  decode v = case V.length v == Bytes.boxBeforeNM of
+    True -> Just (CK v)
+    False -> Nothing
+  {-# INLINE decode #-}
+  encode (CK v) = v
+  {-# INLINE encode #-}
+
+-- | An opaque 'box' nonce.
+newtype Nonce = Nonce (V.Vector Word8) deriving (Eq, Ord)
+
+instance IsEncoding Nonce where
+  decode v = case V.length v == Bytes.boxNonce of
+    True -> Just (Nonce v)
+    False -> Nothing
+  {-# INLINE decode #-}
+  encode (Nonce v) = v
+  {-# INLINE encode #-}
+
+instance IsNonce Nonce where
+  zero = Nonce (V.replicate Bytes.boxNonce 0)
+  nudge (Nonce n) = Nonce (nudgeVector n)
+
+-- | Randomly generates a secret key and a corresponding public key.
+newKeypair :: IO Keypair
+newKeypair = do
+  -- This is a little bizarre and a likely source of errors.
+  -- _err ought to always be 0.
+  ((_err, sk), pk) <- buildUnsafeCVector' Bytes.boxPK $ \pkbuf ->
+    buildUnsafeCVector' Bytes.boxSK $ \skbuf ->
+    c_box_keypair pkbuf skbuf
+  return (SK sk, PK pk)
+
+-- | Randomly generates a nonce for usage with 'box' and 'boxOpen'.
+newNonce :: IO Nonce
+newNonce = Nonce <$> randomVector Bytes.boxNonce
+
+-- | Build a 'CombinedKey' for sending from 'SecretKey' to
+-- 'PublicKey'. This is a precomputation step which can accelerate
+-- later encryption calls.
+beforeNM :: SecretKey -> PublicKey -> CombinedKey
+beforeNM (SK sk) (PK pk) = CK $ snd $ buildUnsafeCVector Bytes.boxBeforeNM $ \ckbuf ->
+  constVectors [pk, sk] $ \[ppk, psk] ->
+  c_box_beforenm ckbuf ppk psk
+
+-- | Encrypts a message for sending to the owner of the public
+-- key. They must have your public key in order to decrypt the
+-- message. It is infeasible for an attacker to decrypt the message so
+-- long as the 'Nonce' is not repeated.
+box :: PublicKey -> SecretKey -> Nonce
+       -> V.Vector Word8
+       -- ^ Message
+       -> V.Vector Word8
+       -- ^ Ciphertext
+box (PK pk) (SK sk) (Nonce nonce) msg =
+  unpad' . snd . buildUnsafeCVector len $ \pc ->
+    constVectors [pk, sk, pad' msg, nonce] $ \[ppk, psk, pm, pn] ->
+    c_box pc pm (fromIntegral len) pn ppk psk
+  where len    = V.length msg + Bytes.boxZero
+        pad'   = pad Bytes.boxZero
+        unpad' = unpad Bytes.boxBoxZero
+
+-- | Decrypts a message sent from the owner of the public key. They
+-- must have encrypted it using your secret key. Returns 'Nothing' if
+-- the keys and message do not match.
+boxOpen :: PublicKey -> SecretKey -> Nonce
+           -> V.Vector Word8
+           -- ^ Ciphertext
+           -> Maybe (V.Vector Word8)
+           -- ^ Message
+boxOpen (PK pk) (SK sk) (Nonce nonce) cipher =
+  let (err, vec) = buildUnsafeCVector len $ \pm ->
+        constVectors [pk, sk, pad' cipher, nonce] $ \[ppk, psk, pc, pn] ->
+        c_box_open pm pc (fromIntegral len) pn ppk psk
+  in hush . handleErrno err $ unpad' vec
+  where len    = V.length cipher + Bytes.boxBoxZero
+        pad'   = pad Bytes.boxBoxZero
+        unpad' = unpad Bytes.boxZero
+
+-- | 'box' using a 'CombinedKey' and is thus faster.
+boxAfterNM :: CombinedKey -> Nonce
+              -> V.Vector Word8
+              -- ^ Message
+              -> V.Vector Word8
+              -- ^ Ciphertext
+boxAfterNM (CK ck) (Nonce nonce) msg =
+  unpad' . snd . buildUnsafeCVector len $ \pc ->
+    constVectors [ck, pad' msg, nonce] $ \[pck, pm, pn] ->
+    c_box_afternm pc pm (fromIntegral len) pn pck
+  where len    = V.length msg + Bytes.boxZero
+        pad'   = pad Bytes.boxZero
+        unpad' = unpad Bytes.boxBoxZero
+
+-- | 'boxOpen' using a 'CombinedKey' and is thus faster.
+boxOpenAfterNM :: CombinedKey -> Nonce
+           -> V.Vector Word8
+           -- ^ Ciphertext
+           -> Maybe (V.Vector Word8)
+           -- ^ Message
+boxOpenAfterNM (CK ck) (Nonce nonce) cipher =
+  let (err, vec) = buildUnsafeCVector len $ \pm ->
+        constVectors [ck, pad' cipher, nonce] $ \[pck, pc, pn] ->
+        c_box_open_afternm pm pc (fromIntegral len) pn pck
+  in hush . handleErrno err $ unpad' vec
+  where len    = V.length cipher + Bytes.boxBoxZero
+        pad'   = pad Bytes.boxBoxZero
+        unpad' = unpad Bytes.boxZero
+
+
+-- | Should always return a 0.
+foreign import ccall "crypto_box_keypair"
+  c_box_keypair :: Ptr Word8
+                   -- ^ Public key
+                   -> Ptr Word8
+                   -- ^ Secret key
+                   -> IO CInt
+                   -- ^ Always 0
+
+-- | The secretbox C API uses 0-padded C strings.
+foreign import ccall "crypto_box"
+  c_box :: Ptr Word8
+           -- ^ Cipher 0-padded output buffer
+           -> Ptr Word8
+           -- ^ Constant 0-padded message input buffer
+           -> CULLong
+           -- ^ Length of message input buffer (incl. 0s)
+           -> Ptr Word8
+           -- ^ Constant nonce buffer
+           -> Ptr Word8
+           -- ^ Constant public key buffer
+           -> Ptr Word8
+           -- ^ Constant secret key buffer
+           -> IO CInt
+           -- ^ Always 0
+
+-- | The secretbox C API uses 0-padded C strings.
+foreign import ccall "crypto_box_open"
+  c_box_open :: Ptr Word8
+                -- ^ Message 0-padded output buffer
+                -> Ptr Word8
+                -- ^ Constant 0-padded ciphertext input buffer
+                -> CULLong
+                -- ^ Length of message input buffer (incl. 0s)
+                -> Ptr Word8
+                -- ^ Constant nonce buffer
+                -> Ptr Word8
+                -- ^ Constant public key buffer
+                -> Ptr Word8
+                -- ^ Constant secret key buffer
+                -> IO CInt
+                -- ^ 0 for success, -1 for failure to verify
+
+-- | Single target key precompilation.
+foreign import ccall "crypto_box_beforenm"
+  c_box_beforenm :: Ptr Word8
+                    -- ^ Combined key output buffer
+                    -> Ptr Word8
+                    -- ^ Constant public key buffer
+                    -> Ptr Word8
+                    -- ^ Constant secret key buffer
+                    -> IO CInt
+                    -- ^ Always 0
+
+-- | Precompiled key crypto box. Uses 0-padded C strings.
+foreign import ccall "crypto_box_afternm"
+  c_box_afternm :: Ptr Word8
+                   -- ^ Cipher 0-padded output buffer
+                   -> Ptr Word8
+                   -- ^ Constant 0-padded message input buffer
+                   -> CULLong
+                   -- ^ Length of message input buffer (incl. 0s)
+                   -> Ptr Word8
+                   -- ^ Constant nonce buffer
+                   -> Ptr Word8
+                   -- ^ Constant combined key buffer
+                   -> IO CInt
+                   -- ^ Always 0
+
+-- | The secretbox C API uses 0-padded C strings.
+foreign import ccall "crypto_box_open_afternm"
+  c_box_open_afternm :: Ptr Word8
+                        -- ^ Message 0-padded output buffer
+                        -> Ptr Word8
+                        -- ^ Constant 0-padded ciphertext input buffer
+                        -> CULLong
+                        -- ^ Length of message input buffer (incl. 0s)
+                        -> Ptr Word8
+                        -- ^ Constant nonce buffer
+                        -> Ptr Word8
+                        -- ^ Constant combined key buffer
+                        -> IO CInt
+                        -- ^ 0 for success, -1 for failure to verify
diff --git a/src/Crypto/Saltine/Core/Hash.hs b/src/Crypto/Saltine/Core/Hash.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Saltine/Core/Hash.hs
@@ -0,0 +1,115 @@
+-- |
+-- Module      : Crypto.Saltine.Core.Hash
+-- Copyright   : (c) Joseph Abrahamson 2013
+-- License     : MIT
+-- 
+-- Maintainer  : me@jspha.com
+-- Stability   : experimental
+-- Portability : non-portable
+-- 
+-- Hashing: "Crypto.Saltine.Core.Hash"
+-- 
+-- The 'hash' function hashes a message 'V.Vector' and returns a
+-- hash. Hashes are always of length 'Bytes.hash'. The 'shorthash'
+-- function hashes a message 'V.Vector' with respect to a secret key
+-- and returns a very short hash. Short hashes are always of length
+-- 'Bytes.shorthash'.
+-- 
+-- The 'hash' function is designed to be usable as a strong component
+-- of DSA, RSA-PSS, key derivation, hash-based message-authentication
+-- codes, hash-based ciphers, and various other common
+-- applications. "Strong" means that the security of these
+-- applications, when instantiated with 'hash', is the same as the
+-- security of the applications against generic attacks. In
+-- particular, the 'hash' function is designed to make finding
+-- collisions difficult.
+-- 
+-- 'hash' is currently an implementation of SHA-512. 'shorthash' is
+-- currently an implementation of SipHash-2-4
+-- (<https://131002.net/siphash/>).
+-- 
+-- There has been considerable degradation of public confidence in the
+-- security conjectures for many hash functions, including
+-- SHA-512. However, for the moment, there do not appear to be
+-- alternatives that inspire satisfactory levels of confidence. One
+-- can hope that NIST's SHA-3 competition will improve the situation.
+-- 
+-- Sodium includes an implementation of the Blake2 hash
+-- (<https://blake2.net/>) but since this is not standard NaCl nor was
+-- Blake2 selected to be SHA-3 the library doesn't bind it.
+-- 
+-- This is version 2010.08.30 of the hash.html web page. Information
+-- about SipHash has been added.
+module Crypto.Saltine.Core.Hash (
+  ShorthashKey,
+  hash,
+  shorthash, newShorthashKey
+  ) where
+
+import Crypto.Saltine.Class
+import Crypto.Saltine.Internal.Util
+import qualified Crypto.Saltine.Internal.ByteSizes as Bytes
+
+import Foreign.C
+import Foreign.Ptr
+import Data.Word
+import qualified Data.Vector.Storable as V
+
+import Control.Applicative
+
+-- | Computes a cryptographically collision-resistant hash making
+-- @hash m == hash m' ==> m == m'@ highly likely even when under
+-- attack.
+hash :: V.Vector Word8
+        -- ^ Message
+        -> V.Vector Word8
+        -- ^ Hash
+hash m = snd . buildUnsafeCVector Bytes.hash $ \ph ->
+  constVectors [m] $ \[pm] -> c_hash ph pm (fromIntegral $ V.length m)
+
+-- | An opaque 'shorthash' cryptographic secret key.
+newtype ShorthashKey = ShK (V.Vector Word8) deriving (Eq, Ord)
+
+instance IsEncoding ShorthashKey where
+  decode v = case V.length v == Bytes.shorthashKey of
+    True -> Just (ShK v)
+    False -> Nothing
+  {-# INLINE decode #-}
+  encode (ShK v) = v
+  {-# INLINE encode #-}
+
+-- | Randomly generates a new key for 'shorthash'.
+newShorthashKey :: IO ShorthashKey
+newShorthashKey = ShK <$> randomVector Bytes.shorthashKey
+
+-- | Computes a very short, fast keyed hash.
+shorthash :: ShorthashKey
+             -> V.Vector Word8
+             -- ^ Message
+             -> V.Vector Word8
+             -- ^ Hash
+shorthash (ShK k) m = snd . buildUnsafeCVector Bytes.shorthash $ \ph ->
+  constVectors [k, m] $ \[pk, pm] ->
+  c_shorthash ph pm (fromIntegral $ V.length m) pk
+             
+foreign import ccall "crypto_hash"
+  c_hash :: Ptr Word8
+            -- ^ Output hash buffer
+            -> Ptr Word8
+            -- ^ Constant message buffer
+            -> CULLong
+            -- ^ Constant message buffer length
+            -> IO CInt
+            -- ^ Always 0
+
+foreign import ccall "crypto_shorthash"
+  c_shorthash :: Ptr Word8
+                 -- ^ Output hash buffer
+                 -> Ptr Word8
+                 -- ^ Constant message buffer
+                 -> CULLong
+                 -- ^ Message buffer length
+                 -> Ptr Word8
+                 -- ^ Constant Key buffer
+                 -> IO CInt
+                 -- ^ Always 0
diff --git a/src/Crypto/Saltine/Core/OneTimeAuth.hs b/src/Crypto/Saltine/Core/OneTimeAuth.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Saltine/Core/OneTimeAuth.hs
@@ -0,0 +1,128 @@
+-- |
+-- Module      : Crypto.Saltine.Core.OneTimeAuth
+-- Copyright   : (c) Joseph Abrahamson 2013
+-- License     : MIT
+-- 
+-- Maintainer  : me@jspha.com
+-- Stability   : experimental
+-- Portability : non-portable
+-- 
+-- Secret-key single-message authentication:
+-- "Crypto.Saltine.Core.OneTimeAuth"
+-- 
+-- The 'auth' function authenticates a message 'V.Vector' using a
+-- secret key The function returns an authenticator. The 'verify'
+-- function checks if it's passed a correct authenticator of a message
+-- under the given secret key.
+-- 
+-- The 'auth' function, viewed as a function of the message for a
+-- uniform random key, is designed to meet the standard notion of
+-- unforgeability after a single message. After the sender
+-- authenticates one message, an attacker cannot find authenticators
+-- for any other messages.
+-- 
+-- The sender must not use 'auth' to authenticate more than one
+-- message under the same key. Authenticators for two messages under
+-- the same key should be expected to reveal enough information to
+-- allow forgeries of authenticators on other messages.
+-- 
+-- "Crypto.Saltine.Core.OneTimeAuth" is
+-- @crypto_onetimeauth_poly1305@, an authenticator specified in
+-- "Cryptography in NaCl" (<http://nacl.cr.yp.to/valid.html>), Section
+-- 9. This authenticator is proven to meet the standard notion of
+-- unforgeability after a single message.
+-- 
+-- This is version 2010.08.30 of the onetimeauth.html web page.
+module Crypto.Saltine.Core.OneTimeAuth (
+  Key, Authenticator,
+  newKey,
+  auth, verify
+  ) where
+
+import Crypto.Saltine.Class
+import Crypto.Saltine.Internal.Util
+import qualified Crypto.Saltine.Internal.ByteSizes as Bytes
+
+import Foreign.C
+import Foreign.Ptr
+import Data.Word
+import qualified Data.Vector.Storable as V
+
+import Control.Applicative
+
+-- $types
+
+-- | An opaque 'auth' cryptographic key.
+newtype Key = Key (V.Vector Word8) deriving (Eq, Ord)
+
+-- | An opaque 'auth' authenticator.
+newtype Authenticator = Au (V.Vector Word8) deriving (Eq, Ord)
+
+instance IsEncoding Key where
+  decode v = case V.length v == Bytes.onetimeKey of
+    True -> Just (Key v)
+    False -> Nothing
+  {-# INLINE decode #-}
+  encode (Key v) = v
+  {-# INLINE encode #-}
+
+instance IsEncoding Authenticator where
+  decode v = case V.length v == Bytes.onetime of
+    True -> Just (Au v)
+    False -> Nothing
+  {-# INLINE decode #-}
+  encode (Au v) = v
+  {-# INLINE encode #-}
+
+-- | Creates a random key of the correct size for 'auth' and 'verify'.
+newKey :: IO Key
+newKey = Key <$> randomVector Bytes.onetimeKey
+
+-- | Builds a keyed 'Authenticator' for a message. This
+-- 'Authenticator' is /impossible/ to forge so long as the 'Key' is
+-- never used twice.
+auth :: Key
+        -> V.Vector Word8
+        -- ^ Message
+        -> Authenticator
+auth (Key key) msg =
+  Au . snd . buildUnsafeCVector Bytes.onetime $ \pa ->
+    constVectors [key, msg] $ \[pk, pm] ->
+    c_onetimeauth pa pm (fromIntegral $ V.length msg) pk
+
+-- | Verifies that an 'Authenticator' matches a given message and key.
+verify :: Key
+          -> Authenticator
+          -> V.Vector Word8
+          -- ^ Message
+          -> Bool
+          -- ^ Is this message authentic?
+verify (Key key) (Au a) msg =
+  unsafeDidSucceed $ constVectors [key, msg, a] $ \[pk, pm, pa] ->
+  return $ c_onetimeauth_verify pa pm (fromIntegral $ V.length msg) pk
+
+foreign import ccall "crypto_onetimeauth"
+  c_onetimeauth :: Ptr Word8
+                   -- ^ Authenticator output buffer
+                   -> Ptr Word8
+                   -- ^ Constant message buffer
+                   -> CULLong
+                   -- ^ Length of message buffer
+                   -> Ptr Word8
+                   -- ^ Constant key buffer
+                   -> IO CInt
+                   -- ^ Always 0
+
+-- | We don't even include this in the IO monad since all of the
+-- buffers are constant.
+foreign import ccall "crypto_onetimeauth_verify"
+  c_onetimeauth_verify :: Ptr Word8
+                          -- ^ Constant authenticator buffer
+                          -> Ptr Word8
+                          -- ^ Constant message buffer
+                          -> CULLong
+                          -- ^ Length of message buffer
+                          -> Ptr Word8
+                          -- ^ Constant key buffer
+                          -> CInt
+                          -- ^ Success if 0, failure if -1
diff --git a/src/Crypto/Saltine/Core/ScalarMult.hs b/src/Crypto/Saltine/Core/ScalarMult.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Saltine/Core/ScalarMult.hs
@@ -0,0 +1,117 @@
+-- |
+-- Module      : Crypto.Saltine.Core.ScalarMult
+-- Copyright   : (c) Joseph Abrahamson 2013
+-- License     : MIT
+-- 
+-- Maintainer  : me@jspha.com
+-- Stability   : experimental
+-- Portability : non-portable
+-- 
+-- Scalar multiplication: "Crypto.Saltine.Core.ScalarMult"
+-- 
+-- The 'mult' function multiplies a group element by an integer of
+-- length 'Bytes.multScalar'. It returns the resulting group element
+-- of length 'Bytes.mult'. The 'multBase' function multiplies a
+-- standard group element by an integer of length
+-- 'Bytes.multScalar'. It returns the resulting group element of
+-- length 'Bytes.mult'.
+-- 
+-- The correspondence between strings and group elements depends on
+-- the primitive implemented by 'mult'. The correspondence is not
+-- necessarily injective in either direction, but it is compatible
+-- with scalar multiplication in the group. The correspondence does
+-- not necessarily include all group elements, but it does include all
+-- strings; i.e., every string represents at least one group element.
+-- 
+-- The correspondence between strings and integers also depends on the
+-- primitive implemented by 'mult'. Every string represents at least
+-- one integer.
+-- 
+-- 'mult' is designed to be strong as a component of various
+-- well-known \"hashed Diffie–Hellman\" applications. In particular,
+-- it is designed to make the \"computational Diffie–Hellman\" problem
+-- (CDH) difficult with respect to the standard base. 'mult' is also
+-- designed to make CDH difficult with respect to other nontrivial
+-- bases. In particular, if a represented group element has small
+-- order, then it is annihilated by all represented scalars. This
+-- feature allows protocols to avoid validating membership in the
+-- subgroup generated by the standard base.
+-- 
+-- NaCl does not make any promises regarding the \"decisional
+-- Diffie–Hellman\" problem (DDH), the \"static Diffie–Hellman\"
+-- problem (SDH), etc. Users are responsible for hashing group
+-- elements.
+-- 
+-- 'mult' is the function @crypto_scalarmult_curve25519@ specified in
+-- \"Cryptography in NaCl\", Sections 2, 3, and 4
+-- (<http://nacl.cr.yp.to/valid.html>). This function is conjectured
+-- to be strong. For background see Bernstein, \"Curve25519: new
+-- Diffie-Hellman speed records,\" Lecture Notes in Computer Science
+-- 3958 (2006), 207–228, <http://cr.yp.to/papers.html#curve25519>.
+-- 
+-- This is version 2010.08.30 of the scalarmult.html web page.
+module Crypto.Saltine.Core.ScalarMult (
+  Scalar, GroupElement,
+  mult, multBase
+  ) where
+
+import Crypto.Saltine.Class
+import Crypto.Saltine.Internal.Util
+import qualified Crypto.Saltine.Internal.ByteSizes as Bytes
+
+import Foreign.C
+import Foreign.Ptr
+import Data.Word
+import qualified Data.Vector.Storable as V
+
+-- $types
+
+-- | A group element.
+newtype GroupElement = GE (V.Vector Word8) deriving (Eq)
+
+-- | A scalar integer.
+newtype Scalar = Sc (V.Vector Word8) deriving (Eq)
+
+instance IsEncoding GroupElement where
+  decode v = case V.length v == Bytes.mult of
+    True -> Just (GE v)
+    False -> Nothing
+  {-# INLINE decode #-}
+  encode (GE v) = v
+  {-# INLINE encode #-}
+
+instance IsEncoding Scalar where
+  decode v = case V.length v == Bytes.multScalar of
+    True -> Just (Sc v)
+    False -> Nothing
+  {-# INLINE decode #-}
+  encode (Sc v) = v
+  {-# INLINE encode #-}
+
+mult :: Scalar -> GroupElement -> GroupElement
+mult (Sc n) (GE p) = GE . snd . buildUnsafeCVector Bytes.mult $ \pq ->
+  constVectors [n, p] $ \[pn, pp] ->
+  c_scalarmult pq pn pp
+
+multBase :: Scalar -> GroupElement
+multBase (Sc n) = GE . snd . buildUnsafeCVector Bytes.mult $ \pq ->
+  constVectors [n] $ \[pn] ->
+  c_scalarmult_base pq pn
+
+foreign import ccall "crypto_scalarmult"
+  c_scalarmult :: Ptr Word8
+                  -- ^ Output group element buffer
+                  -> Ptr Word8
+                  -- ^ Input integer buffer
+                  -> Ptr Word8
+                  -- ^ Input group element buffer
+                  -> IO CInt
+                  -- ^ Always 0
+
+foreign import ccall "crypto_scalarmult"
+  c_scalarmult_base :: Ptr Word8
+                       -- ^ Output group element buffer
+                       -> Ptr Word8
+                       -- ^ Input integer buffer
+                       -> IO CInt
+                       -- ^ Always 0
diff --git a/src/Crypto/Saltine/Core/SecretBox.hs b/src/Crypto/Saltine/Core/SecretBox.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Saltine/Core/SecretBox.hs
@@ -0,0 +1,151 @@
+-- |
+-- Module      : Crypto.Saltine.Core.SecretBox
+-- Copyright   : (c) Joseph Abrahamson 2013
+-- License     : MIT
+-- 
+-- Maintainer  : me@jspha.com
+-- Stability   : experimental
+-- Portability : non-portable
+-- 
+-- Secret-key authenticated encryption:
+-- "Crypto.Saltine.Core.SecretBox"
+-- 
+-- The 'secretbox' function encrypts and authenticates a message
+-- 'V.Vector' using a secret key and a nonce. The 'secretboxOpen'
+-- function verifies and decrypts a ciphertext 'V.Vector' using a secret
+-- key and a nonce. If the ciphertext fails validation,
+-- 'secretboxOpen' returns 'Nothing'.
+-- 
+-- The "Crypto.Saltine.Core.SecretBox" module is designed to meet
+-- the standard notions of privacy and authenticity for a secret-key
+-- authenticated-encryption scheme using nonces. For formal
+-- definitions see, e.g., Bellare and Namprempre, "Authenticated
+-- encryption: relations among notions and analysis of the generic
+-- composition paradigm," Lecture Notes in Computer Science 1976
+-- (2000), 531–545, <http://www-cse.ucsd.edu/~mihir/papers/oem.html>.
+-- 
+-- Note that the length is not hidden. Note also that it is the
+-- caller's responsibility to ensure the uniqueness of nonces—for
+-- example, by using nonce 1 for the first message, nonce 2 for the
+-- second message, etc. Nonces are long enough that randomly generated
+-- nonces have negligible risk of collision.
+-- 
+-- "Crypto.Saltine.Core.SecretBox" is
+-- @crypto_secretbox_xsalsa20poly1305@, a particular combination of
+-- Salsa20 and Poly1305 specified in \"Cryptography in NaCl\"
+-- (<http://nacl.cr.yp.to/valid.html>). This function is conjectured
+-- to meet the standard notions of privacy and authenticity.
+-- 
+-- This is version 2010.08.30 of the secretbox.html web page.
+module Crypto.Saltine.Core.SecretBox (
+  Key, Nonce,
+  secretbox, secretboxOpen,
+  newKey, newNonce
+  ) where
+
+import Crypto.Saltine.Class
+import Crypto.Saltine.Internal.Util
+import qualified Crypto.Saltine.Internal.ByteSizes as Bytes
+
+import Foreign.C
+import Foreign.Ptr
+import Data.Word
+import qualified Data.Vector.Storable as V
+
+import Control.Applicative
+
+-- $types
+
+-- | An opaque 'secretbox' cryptographic key.
+newtype Key = Key (V.Vector Word8) deriving (Eq, Ord)
+
+instance IsEncoding Key where
+  decode v = case V.length v == Bytes.secretBoxKey of
+    True -> Just (Key v)
+    False -> Nothing
+  {-# INLINE decode #-}
+  encode (Key v) = v
+  {-# INLINE encode #-}
+
+-- | An opaque 'secretbox' nonce.
+newtype Nonce = Nonce (V.Vector Word8) deriving (Eq, Ord)
+
+instance IsEncoding Nonce where
+  decode v = case V.length v == Bytes.secretBoxNonce of
+    True -> Just (Nonce v)
+    False -> Nothing
+  {-# INLINE decode #-}
+  encode (Nonce v) = v
+  {-# INLINE encode #-}
+
+instance IsNonce Nonce where
+  zero = Nonce (V.replicate Bytes.secretBoxNonce 0)
+  nudge (Nonce n) = Nonce (nudgeVector n)
+
+-- | Creates a random key of the correct size for 'secretbox'.
+newKey :: IO Key
+newKey = Key <$> randomVector Bytes.secretBoxKey
+
+-- | Creates a random nonce of the correct size for 'secretbox'.
+newNonce :: IO Nonce
+newNonce = Nonce <$> randomVector Bytes.secretBoxNonce
+
+-- | Encrypts a message. It is infeasible for an attacker to decrypt
+-- the message so long as the 'Nonce' is never repeated.
+secretbox :: Key -> Nonce
+             -> V.Vector Word8
+             -- ^ Message
+             -> V.Vector Word8
+             -- ^ Ciphertext
+secretbox (Key key) (Nonce nonce) msg =
+  unpad' . snd . buildUnsafeCVector len $ \pc ->
+    constVectors [key, pad' msg, nonce] $ \[pk, pm, pn] ->
+    c_secretbox pc pm (fromIntegral len) pn pk
+  where len    = V.length msg + Bytes.secretBoxZero
+        pad'   = pad Bytes.secretBoxZero
+        unpad' = unpad Bytes.secretBoxBoxZero
+
+-- | Decrypts a message. Returns 'Nothing' if the keys and message do
+-- not match.
+secretboxOpen :: Key -> Nonce 
+                 -> V.Vector Word8
+                 -- ^ Ciphertext
+                 -> Maybe (V.Vector Word8)
+                 -- ^ Message
+secretboxOpen (Key key) (Nonce nonce) cipher =
+  let (err, vec) = buildUnsafeCVector len $ \pm ->
+        constVectors [key, pad' cipher, nonce] $ \[pk, pc, pn] ->
+        c_secretbox_open pm pc (fromIntegral len) pn pk
+  in hush . handleErrno err $ unpad' vec
+  where len    = V.length cipher + Bytes.secretBoxBoxZero
+        pad'   = pad Bytes.secretBoxBoxZero
+        unpad' = unpad Bytes.secretBoxZero
+
+-- | The secretbox C API uses 0-padded C strings. Always returns 0.
+foreign import ccall "crypto_secretbox"
+  c_secretbox :: Ptr Word8
+                 -- ^ Cipher 0-padded output buffer
+                 -> Ptr Word8
+                 -- ^ Constant 0-padded message input buffer
+                 -> CULLong
+                 -- ^ Length of message input buffer (incl. 0s)
+                 -> Ptr Word8
+                 -- ^ Constant nonce buffer
+                 -> Ptr Word8
+                 -- ^ Constant key buffer
+                 -> IO CInt
+
+-- | The secretbox C API uses 0-padded C strings. Returns 0 if
+-- successful or -1 if verification failed.
+foreign import ccall "crypto_secretbox_open"
+  c_secretbox_open :: Ptr Word8
+                      -- ^ Message 0-padded output buffer
+                      -> Ptr Word8
+                      -- ^ Constant 0-padded message input buffer
+                      -> CULLong
+                      -- ^ Length of message input buffer (incl. 0s)
+                      -> Ptr Word8
+                      -- ^ Constant nonce buffer
+                      -> Ptr Word8
+                      -- ^ Constant key buffer
+                      -> IO CInt
diff --git a/src/Crypto/Saltine/Core/Sign.hs b/src/Crypto/Saltine/Core/Sign.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Saltine/Core/Sign.hs
@@ -0,0 +1,154 @@
+-- |
+-- Module      : Crypto.Saltine.Core.Sign
+-- Copyright   : (c) Joseph Abrahamson 2013
+-- License     : MIT
+-- 
+-- Maintainer  : me@jspha.com
+-- Stability   : experimental
+-- Portability : non-portable
+-- 
+-- Signatures: "Crypto.Saltine.Core.Sign"
+-- 
+-- The 'newKeypair' function randomly generates a secret key and a
+-- corresponding public key. The 'sign' function signs a message
+-- 'V.Vector' using the signer's secret key and returns the resulting
+-- signed message. The 'signOpen' function verifies the signature in a
+-- signed message using the signer's public key then returns the
+-- message without its signature.
+-- 
+-- "Crypto.Saltine.Core.Sign" is an EdDSA signature using
+-- elliptic-curve Curve25519 (see: <http://ed25519.cr.yp.to/>). See
+-- also, \"Daniel J. Bernstein, Niels Duif, Tanja Lange, Peter
+-- Schwabe, Bo-Yin Yang. High-speed high-security signatures. Journal
+-- of Cryptographic Engineering 2 (2012), 77–89.\"
+-- <http://ed25519.cr.yp.to/ed25519-20110926.pdf>.
+--
+-- This is current information as of 2013 June 6.
+
+module Crypto.Saltine.Core.Sign (
+  SecretKey, PublicKey, Keypair,
+  newKeypair,
+  sign, signOpen
+  ) where
+
+import Crypto.Saltine.Class
+import Crypto.Saltine.Internal.Util
+import qualified Crypto.Saltine.Internal.ByteSizes as Bytes
+
+import Foreign.C
+import Foreign.Ptr
+import Foreign.Marshal.Alloc
+import Foreign.Storable
+import System.IO.Unsafe
+import Data.Word
+import qualified Data.Vector.Storable as V
+
+-- $types
+
+-- | An opaque 'box' cryptographic secret key.
+newtype SecretKey = SK (V.Vector Word8) deriving (Eq, Ord)
+
+instance IsEncoding SecretKey where
+  decode v = case V.length v == Bytes.signSK of
+    True -> Just (SK v)
+    False -> Nothing
+  {-# INLINE decode #-}
+  encode (SK v) = v
+  {-# INLINE encode #-}
+
+-- | An opaque 'box' cryptographic public key.
+newtype PublicKey = PK (V.Vector Word8) deriving (Eq, Ord)
+
+instance IsEncoding PublicKey where
+  decode v = case V.length v == Bytes.signPK of
+    True -> Just (PK v)
+    False -> Nothing
+  {-# INLINE decode #-}
+  encode (PK v) = v
+  {-# INLINE encode #-}
+
+-- | A convenience type for keypairs
+type Keypair = (SecretKey, PublicKey)
+
+-- | Creates a random key of the correct size for 'sign' and
+-- 'signOpen' of form @(secretKey, publicKey)@.
+newKeypair :: IO Keypair
+newKeypair = do
+  -- This is a little bizarre and a likely source of errors.
+  -- _err ought to always be 0.
+  ((_err, sk), pk) <- buildUnsafeCVector' Bytes.signPK $ \pkbuf ->
+    buildUnsafeCVector' Bytes.signSK $ \skbuf ->
+    c_sign_keypair pkbuf skbuf
+  return (SK sk, PK pk)
+
+-- | Augments a message with a signature forming a \"signed
+-- message\".
+sign :: SecretKey
+        -> V.Vector Word8
+        -- ^ Message
+        -> V.Vector Word8
+        -- ^ Signed message
+sign (SK k) m = unsafePerformIO $ 
+  alloca $ \psmlen -> do
+    (_err, sm) <- buildUnsafeCVector' (len + Bytes.sign) $ \psmbuf ->
+      constVectors [k, m] $ \[pk, pm] ->
+      c_sign psmbuf psmlen pm (fromIntegral len) pk
+    smlen <- peek psmlen
+    return $ V.take (fromIntegral smlen) sm
+  where len = V.length m
+
+-- | Checks a \"signed message\" returning 'Just' the original message
+-- iff the signature was generated using the 'SecretKey' corresponding
+-- to the given 'PublicKey'. Returns 'Nothing' otherwise.
+signOpen :: PublicKey
+            -> V.Vector Word8
+            -- ^ Signed message
+            -> Maybe (V.Vector Word8)
+            -- ^ Maybe the restored message
+signOpen (PK k) sm = unsafePerformIO $
+  alloca $ \pmlen -> do
+    (err, m) <- buildUnsafeCVector' smlen $ \pmbuf ->
+      constVectors [k, sm] $ \[pk, psm] ->
+      c_sign_open pmbuf pmlen psm (fromIntegral smlen) pk
+    mlen <- peek pmlen
+    case err of
+      0 -> return $ Just $ V.take (fromIntegral mlen) m
+      _ -> return $ Nothing
+  where smlen = V.length sm
+
+
+foreign import ccall "crypto_sign_keypair"
+  c_sign_keypair :: Ptr Word8
+                    -- ^ Public key output buffer
+                    -> Ptr Word8
+                    -- ^ Secret key output buffer
+                    -> IO CInt
+                    -- ^ Always 0
+
+foreign import ccall "crypto_sign"
+  c_sign :: Ptr Word8
+            -- ^ Signed message output buffer
+            -> Ptr CULLong
+            -- ^ Length of signed message
+            -> Ptr Word8
+            -- ^ Constant message buffer
+            -> CULLong
+            -- ^ Length of message input buffer
+            -> Ptr Word8
+            -- ^ Constant secret key buffer
+            -> IO CInt
+            -- ^ Always 0
+
+foreign import ccall "crypto_sign_open"
+  c_sign_open :: Ptr Word8
+                 -- ^ Message output buffer
+                 -> Ptr CULLong
+                 -- ^ Length of message
+                 -> Ptr Word8
+                 -- ^ Constant signed message buffer
+                 -> CULLong
+                 -- ^ Length of signed message buffer
+                 -> Ptr Word8
+                 -- ^ Public key buffer
+                 -> IO CInt
+                 -- ^ 0 if signature is verifiable, -1 otherwise
diff --git a/src/Crypto/Saltine/Core/Stream.hs b/src/Crypto/Saltine/Core/Stream.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Saltine/Core/Stream.hs
@@ -0,0 +1,157 @@
+-- |
+-- Module      : Crypto.Saltine.Core.Stream
+-- Copyright   : (c) Joseph Abrahamson 2013
+-- License     : MIT
+-- 
+-- Maintainer  : me@jspha.com
+-- Stability   : experimental
+-- Portability : non-portable
+-- 
+-- Secret-key encryption:
+-- "Crypto.Saltine.Core.Stream"
+-- 
+-- The 'stream' function produces a sized stream 'V.Vector' as a
+-- function of a secret key and a nonce. The 'xor' function encrypts a
+-- message 'V.Vector' using a secret key and a nonce.  The 'xor'
+-- function guarantees that the ciphertext has the same length as the
+-- plaintext, and is the @plaintext `xor` stream k n@. Consequently
+-- 'xor' can also be used to decrypt.
+-- 
+-- The 'stream' function, viewed as a function of the nonce for a
+-- uniform random key, is designed to meet the standard notion of
+-- unpredictability (\"PRF\"). For a formal definition see, e.g.,
+-- Section 2.3 of Bellare, Kilian, and Rogaway, \"The security of the
+-- cipher block chaining message authentication code,\" Journal of
+-- Computer and System Sciences 61 (2000), 362–399;
+-- <http://www-cse.ucsd.edu/~mihir/papers/cbc.html>. This means that
+-- an attacker cannot distinguish this function from a uniform random
+-- function. Consequently, if a series of messages is encrypted by
+-- 'xor' with /a different nonce for each message/, the ciphertexts
+-- are indistinguishable from uniform random strings of the same
+-- length.
+-- 
+-- Note that the length is not hidden. Note also that it is the
+-- caller's responsibility to ensure the uniqueness of nonces—for
+-- example, by using nonce 1 for the first message, nonce 2 for the
+-- second message, etc. Nonces are long enough that randomly generated
+-- nonces have negligible risk of collision.
+-- 
+-- Saltine does not make any promises regarding the resistance of
+-- crypto_stream to \"related-key attacks.\" It is the caller's
+-- responsibility to use proper key-derivation functions.
+-- 
+-- "Crypto.Saltine.Core.Stream" is @crypto_stream_xsalsa20@, a
+-- particular cipher specified in \"Cryptography in NaCl\"
+-- (<http://nacl.cr.yp.to/valid.html>), Section 7. This cipher is
+-- conjectured to meet the standard notion of unpredictability.
+-- 
+-- This is version 2010.08.30 of the stream.html web page.
+
+module Crypto.Saltine.Core.Stream (
+  Key, Nonce,
+  newKey, newNonce,
+  stream, xor
+  ) where
+
+import Crypto.Saltine.Class
+import Crypto.Saltine.Internal.Util
+import qualified Crypto.Saltine.Internal.ByteSizes as Bytes
+
+import Foreign.C
+import Foreign.Ptr
+import Data.Word
+import qualified Data.Vector.Storable as V
+
+import Control.Applicative
+
+-- $types
+
+-- | An opaque 'stream' cryptographic key.
+newtype Key = Key (V.Vector Word8) deriving (Eq, Ord)
+
+instance IsEncoding Key where
+  decode v = case V.length v == Bytes.streamKey of
+    True -> Just (Key v)
+    False -> Nothing
+  {-# INLINE decode #-}
+  encode (Key v) = v
+  {-# INLINE encode #-}
+
+-- | An opaque 'stream' nonce.
+newtype Nonce = Nonce (V.Vector Word8) deriving (Eq, Ord)
+
+instance IsNonce Nonce where
+  zero = Nonce (V.replicate Bytes.streamNonce 0)
+  nudge (Nonce n) = Nonce (nudgeVector n)
+
+instance IsEncoding Nonce where
+  decode v = case V.length v == Bytes.streamNonce of
+    True -> Just (Nonce v)
+    False -> Nothing
+  {-# INLINE decode #-}
+  encode (Nonce v) = v
+  {-# INLINE encode #-}
+
+-- | Creates a random key of the correct size for 'stream' and 'xor'.
+newKey :: IO Key
+newKey = Key <$> randomVector Bytes.streamKey
+
+-- | Creates a random nonce of the correct size for 'stream' and
+-- 'xor'.
+newNonce :: IO Nonce
+newNonce = Nonce <$> randomVector Bytes.streamNonce
+
+-- | Generates a cryptographic random stream indexed by the 'Key' and
+-- 'Nonce'. These streams are indistinguishable from random noise so
+-- long as the 'Nonce' is not used more than once.
+stream :: Key -> Nonce -> Int
+          -> V.Vector Word8
+          -- ^ Cryptographic stream
+stream (Key key) (Nonce nonce) n =
+  snd . buildUnsafeCVector n $ \ps ->
+    constVectors [key, nonce] $ \[pk, pn] ->
+    c_stream ps (fromIntegral n) pn pk
+
+-- | Computes the exclusive-or between a message and a cryptographic
+-- random stream indexed by the 'Key' and the 'Nonce'. This renders
+-- the output indistinguishable from random noise so long as the
+-- 'Nonce' is not used more than once. /Note:/ while this can be used
+-- for encryption and decryption, it is /possible for an attacker to/
+-- /manipulate the message in transit without detection/. USE AT YOUR
+-- OWN RISK.
+xor :: Key -> Nonce 
+       -> V.Vector Word8
+       -- ^ Message
+       -> V.Vector Word8
+       -- ^ Ciphertext
+xor (Key key) (Nonce nonce) msg =
+  snd . buildUnsafeCVector len $ \pc ->
+    constVectors [key, nonce, msg] $ \[pk, pn, pm] ->
+    c_stream_xor pc pm (fromIntegral len) pn pk
+  where len = V.length msg
+
+foreign import ccall "crypto_stream"
+  c_stream :: Ptr Word8
+              -- ^ Stream output buffer
+              -> CULLong
+              -- ^ Length of stream to generate
+              -> Ptr Word8
+              -- ^ Constant nonce buffer
+              -> Ptr Word8
+              -- ^ Constant key buffer
+              -> IO CInt
+              -- ^ Always 0
+
+foreign import ccall "crypto_stream_xor"
+  c_stream_xor :: Ptr Word8
+                  -- ^ Ciphertext output buffer
+                  -> Ptr Word8
+                  -- ^ Constant message buffer
+                  -> CULLong
+                  -- ^ Length of message buffer
+                  -> Ptr Word8
+                  -- ^ Constant nonce buffer
+                  -> Ptr Word8
+                  -- ^ Constant key buffer
+                  -> IO CInt
+                  -- ^ Always 0
diff --git a/src/Crypto/Saltine/Internal/ByteSizes.hs b/src/Crypto/Saltine/Internal/ByteSizes.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Saltine/Internal/ByteSizes.hs
@@ -0,0 +1,414 @@
+{-# LANGUAGE ForeignFunctionInterface #-}
+-- |
+-- Module      : Crypto.Saltine.Core.ScalarMult
+-- Copyright   : (c) Joseph Abrahamson 2013
+-- License     : MIT
+-- 
+-- Maintainer  : me@jspha.com
+-- Stability   : experimental
+-- Portability : non-portable
+-- 
+-- Various sizes
+-- 
+-- While technically these sizes are hidden behind opaque newtype
+-- wrappers, they can be useful for computation and sizing and are
+-- thus exposed.
+-- 
+-- As of @libsodium-4.1@ some of these sizes are not exported and thus
+-- are hardcoded here. This limitation should be removed in later
+-- versions of @libsodium@.
+module Crypto.Saltine.Internal.ByteSizes (
+
+  auth,
+  authKey,
+  boxPK,
+  boxSK,
+  boxNonce,
+  boxZero,
+  boxBoxZero,
+  boxMac,
+  boxBeforeNM,
+  onetime,
+  onetimeKey,
+  mult,
+  multScalar,
+  secretBoxKey,
+  secretBoxNonce,
+  secretBoxZero,
+  secretBoxBoxZero,
+  sign,
+  signPK,
+  signSK,
+  streamKey,
+  streamNonce,
+  hash,
+  shorthash,
+  shorthashKey
+  
+  ) where
+
+import Foreign.C
+
+-- Constants for
+
+auth, authKey :: Int
+boxPK, boxSK, boxNonce, boxZero, boxBoxZero, boxMac, boxBeforeNM :: Int
+onetime, onetimeKey :: Int
+mult, multScalar :: Int
+secretBoxKey, secretBoxNonce, secretBoxZero, secretBoxBoxZero :: Int
+sign, signPK, signSK :: Int
+streamKey, streamNonce :: Int
+hash, shorthash, shorthashKey :: Int
+
+
+-- Authentication
+-- | Size of a @crypto_auth@ authenticator.
+auth    = fromIntegral c_crypto_auth_bytes
+-- | Size of a @crypto_auth@ authenticator key. 
+authKey = fromIntegral c_crypto_auth_keybytes
+
+-- Box
+-- | Size of a @crypto_box@ public key
+boxPK       = fromIntegral c_crypto_box_publickeybytes
+-- | Size of a @crypto_box@ secret key
+boxSK       = fromIntegral c_crypto_box_secretkeybytes
+-- | Size of a @crypto_box@ nonce
+boxNonce    = fromIntegral c_crypto_box_noncebytes
+-- | Size of 0-padding prepended to messages before using @crypto_box@
+-- or after using @crypto_box_open@
+boxZero     = fromIntegral c_crypto_box_zerobytes
+-- | Size of 0-padding prepended to ciphertext before using
+-- @crypto_box_open@ or after using @crypto_box@.
+boxBoxZero  = fromIntegral c_crypto_box_boxzerobytes
+boxMac      = fromIntegral c_crypto_box_macbytes
+-- | Size of a @crypto_box_beforenm@-generated combined key
+boxBeforeNM =
+  fromIntegral c_crypto_box_beforenmbytes
+
+-- OneTimeAuth
+-- | Size of a @crypto_onetimeauth@ authenticator.
+onetime    = fromIntegral c_crypto_onetimeauth_bytes
+-- | Size of a @crypto_onetimeauth@ authenticator key.
+onetimeKey = fromIntegral c_crypto_onetimeauth_keybytes
+
+-- ScalarMult
+-- | Size of a group element string representation for
+-- @crypto_scalarmult@.
+mult = fromIntegral c_crypto_scalarmult_bytes
+-- | Size of a integer string representation for @crypto_scalarmult@.
+multScalar = fromIntegral c_crypto_scalarmult_scalarbytes
+
+-- SecretBox
+-- | Size of a @crypto_secretbox@ secret key
+secretBoxKey     = fromIntegral c_crypto_secretbox_keybytes
+-- | Size of a @crypto_secretbox@ nonce
+secretBoxNonce   = fromIntegral c_crypto_secretbox_noncebytes
+-- | Size of 0-padding prepended to messages before using
+-- @crypto_secretbox@ or after using @crypto_secretbox_open@
+secretBoxZero    = fromIntegral c_crypto_secretbox_zerobytes
+-- | Size of 0-padding prepended to ciphertext before using
+-- @crypto_secretbox_open@ or after using @crypto_secretbox@
+secretBoxBoxZero = fromIntegral c_crypto_secretbox_boxzerobytes
+
+-- Signatures
+-- | The maximum size of a signature prepended to a message to form a
+-- signed message.
+sign   = fromIntegral c_crypto_sign_bytes
+-- | The size of a public key for signing verification
+signPK = fromIntegral c_crypto_sign_publickeybytes
+-- | The size of a secret key for signing
+signSK = fromIntegral c_crypto_sign_secretkeybytes
+
+-- Streams
+-- | The size of a key for the cryptographic stream generation
+streamKey   = fromIntegral c_crypto_stream_keybytes
+-- | The size of a nonce for the cryptographic stream generation
+streamNonce = fromIntegral c_crypto_stream_noncebytes
+
+-- Hashes
+-- | The size of a hash resulting from
+-- 'Crypto.Saltine.Internal.Hash.hash'.
+hash         = fromIntegral c_crypto_hash_bytes
+-- | The size of a keyed hash resulting from
+-- 'Crypto.Saltine.Internal.Hash.shorthash'.
+shorthash    = fromIntegral c_crypto_shorthash_bytes
+-- | The size of a hashing key for the keyed hash function
+-- 'Crypto.Saltine.Internal.Hash.shorthash'.
+shorthashKey = fromIntegral c_crypto_shorthash_keybytes
+
+-- src/libsodium/crypto_auth/crypto_auth.c
+foreign import ccall "crypto_auth_bytes"
+  c_crypto_auth_bytes :: CSize
+foreign import ccall "crypto_auth_keybytes"
+  c_crypto_auth_keybytes :: CSize
+
+-- src/libsodium/crypto_box/crypto_box.c
+foreign import ccall "crypto_box_publickeybytes"
+  c_crypto_box_publickeybytes :: CSize
+foreign import ccall "crypto_box_secretkeybytes"
+  c_crypto_box_secretkeybytes :: CSize
+foreign import ccall "crypto_box_beforenmbytes"
+  c_crypto_box_beforenmbytes :: CSize
+foreign import ccall "crypto_box_noncebytes"
+  c_crypto_box_noncebytes :: CSize
+foreign import ccall "crypto_box_zerobytes"
+  c_crypto_box_zerobytes :: CSize
+foreign import ccall "crypto_box_boxzerobytes"
+  c_crypto_box_boxzerobytes :: CSize
+foreign import ccall "crypto_box_macbytes"
+  c_crypto_box_macbytes :: CSize
+                           
+-- src/libsodium/crypto_onetimeauth/crypto_onetimeauth.c
+foreign import ccall "crypto_onetimeauth_bytes"
+  c_crypto_onetimeauth_bytes :: CSize
+foreign import ccall "crypto_onetimeauth_keybytes"
+  c_crypto_onetimeauth_keybytes :: CSize
+
+-- src/libsodium/crypto_scalarmult/crypto_scalarmult.c
+foreign import ccall "crypto_scalarmult_bytes"
+  c_crypto_scalarmult_bytes :: CSize
+foreign import ccall "crypto_scalarmult_scalarbytes"
+  c_crypto_scalarmult_scalarbytes :: CSize
+
+-- src/libsodium/crypto_secretbox/crypto_secretbox.c
+foreign import ccall "crypto_secretbox_keybytes"
+  c_crypto_secretbox_keybytes :: CSize
+foreign import ccall "crypto_secretbox_noncebytes"
+  c_crypto_secretbox_noncebytes :: CSize
+foreign import ccall "crypto_secretbox_zerobytes"
+  c_crypto_secretbox_zerobytes :: CSize
+foreign import ccall "crypto_secretbox_boxzerobytes"
+  c_crypto_secretbox_boxzerobytes :: CSize
+
+-- src/libsodium/crypto_sign/crypto_sign.c
+foreign import ccall "crypto_sign_bytes"
+  c_crypto_sign_bytes :: CSize
+foreign import ccall "crypto_sign_publickeybytes"
+  c_crypto_sign_publickeybytes :: CSize
+foreign import ccall "crypto_sign_secretkeybytes"
+  c_crypto_sign_secretkeybytes :: CSize
+
+-- HARDCODED
+-- ---------
+
+-- | The size of a @crypto_stream@ or @crypto_stream_xor@
+-- key. HARDCODED to be @crypto_stream_xsalsa20@ for now until Sodium
+-- exports the C constant.
+c_crypto_stream_keybytes :: CSize
+c_crypto_stream_keybytes = 32
+
+-- | The size of a @crypto_stream@ or @crypto_stream_xor@
+-- nonce. HARDCODED to be @crypto_stream_xsalsa20@ for now until
+-- Sodium exports the C constant.
+c_crypto_stream_noncebytes :: CSize
+c_crypto_stream_noncebytes = 24
+
+-- | The size of a @crypto_hash@ output hash. HARDCODED to be
+-- @crypto_hash_sha512@ for now until Sodium exports the C constant.
+c_crypto_hash_bytes :: CSize
+c_crypto_hash_bytes = 64
+
+-- | The size of a @crypto_shorthash@ output hash. HARDCODED to be
+-- @crypto_shorthash_siphash24@ for now until Sodium exports the C
+-- constant.
+c_crypto_shorthash_bytes :: CSize
+c_crypto_shorthash_bytes = 8
+
+-- | The size of a @crypto_shorthash@ key. HARDCODED to be
+-- @crypto_shorthash_siphash24@ for now until Sodium exports the C
+-- constant.
+c_crypto_shorthash_keybytes :: CSize
+c_crypto_shorthash_keybytes = 16
+
+
+-- src/libsodium/crypto_stream/crypto_stream.c
+-- foreign import ccall "crypto_stream_keybytes"
+--   c_crypto_stream_keybytes :: CSize
+-- foreign import ccall "crypto_stream_noncebytes"
+--   c_crypto_stream_noncebytes :: CSize
+
+-- src/libsodium/crypto_shorthash/crypto_shorthash.c
+-- foreign import ccall "crypto_shorthash_bytes"
+--   c_crypto_shorthash_bytes :: CSize
+-- foreign import ccall "crypto_shorthash_keybytes"
+--   c_crypto_shorthash_keybytes :: CSize
+
+-- Others
+-- ------
+
+-- src/libsodium/crypto_auth/hmacsha256/auth_hmacsha256_api.c
+-- foreign import ccall "crypto_auth_hmacsha256_bytes"
+--   c_crypto_auth_hmacsha256_bytes :: CSize
+-- foreign import ccall "crypto_auth_hmacsha256_keybytes"
+--   c_crypto_auth_hmacsha256_keybytes :: CSize
+
+-- src/libsodium/crypto_auth/hmacsha512256/auth_hmacsha512256_api.c
+-- foreign import ccall "crypto_auth_hmacsha512256_bytes"
+--   c_crypto_auth_hmacsha512256_bytes :: CSize
+-- foreign import ccall "crypto_auth_hmacsha512256_keybytes"
+--   c_crypto_auth_hmacsha512256_keybytes :: CSize
+
+-- src/libsodium/crypto_box/curve25519xsalsa20poly1305/box_curve25519xsalsa20poly1305_api.c
+-- foreign import ccall "crypto_box_curve25519xsalsa20poly1305_publickeybytes"
+--   c_crypto_box_curve25519xsalsa20poly1305_publickeybytes :: CSize
+-- foreign import ccall "crypto_box_curve25519xsalsa20poly1305_secretkeybytes"
+--   c_crypto_box_curve25519xsalsa20poly1305_secretkeybytes :: CSize
+-- foreign import ccall "crypto_box_curve25519xsalsa20poly1305_beforenmbytes"
+--   c_crypto_box_curve25519xsalsa20poly1305_beforenmbytes :: CSize
+-- foreign import ccall "crypto_box_curve25519xsalsa20poly1305_noncebytes"
+--   c_crypto_box_curve25519xsalsa20poly1305_noncebytes :: CSize
+-- foreign import ccall "crypto_box_curve25519xsalsa20poly1305_zerobytes"
+--   c_crypto_box_curve25519xsalsa20poly1305_zerobytes :: CSize
+-- foreign import ccall "crypto_box_curve25519xsalsa20poly1305_boxzerobytes"
+--   c_crypto_box_curve25519xsalsa20poly1305_boxzerobytes :: CSize
+-- foreign import ccall "crypto_box_curve25519xsalsa20poly1305_macbytes"
+--   c_crypto_box_curve25519xsalsa20poly1305_macbytes :: CSize
+
+-- src/libsodium/crypto_core/hsalsa20/core_hsalsa20_api.c
+-- foreign import ccall "crypto_core_hsalsa20_outputbytes"
+--   c_crypto_core_hsalsa20_outputbytes :: CSize
+-- foreign import ccall "crypto_core_hsalsa20_inputbytes"
+--   c_crypto_core_hsalsa20_inputbytes :: CSize
+-- foreign import ccall "crypto_core_hsalsa20_keybytes"
+--   c_crypto_core_hsalsa20_keybytes :: CSize
+-- foreign import ccall "crypto_core_hsalsa20_constbytes"
+--   c_crypto_core_hsalsa20_constbytes :: CSize
+
+-- src/libsodium/crypto_core/salsa20/core_salsa20_api.c
+-- foreign import ccall "crypto_core_salsa20_outputbytes"
+--   c_crypto_core_salsa20_outputbytes :: CSize
+-- foreign import ccall "crypto_core_salsa20_inputbytes"
+--   c_crypto_core_salsa20_inputbytes :: CSize
+-- foreign import ccall "crypto_core_salsa20_keybytes"
+--   c_crypto_core_salsa20_keybytes :: CSize
+-- foreign import ccall "crypto_core_salsa20_constbytes"
+--   c_crypto_core_salsa20_constbytes :: CSize
+
+-- src/libsodium/crypto_core/salsa2012/core_salsa2012_api.c
+-- foreign import ccall "crypto_core_salsa2012_outputbytes"
+--   c_crypto_core_salsa2012_outputbytes :: CSize
+-- foreign import ccall "crypto_core_salsa2012_inputbytes"
+--   c_crypto_core_salsa2012_inputbytes :: CSize
+-- foreign import ccall "crypto_core_salsa2012_keybytes"
+--   c_crypto_core_salsa2012_keybytes :: CSize
+-- foreign import ccall "crypto_core_salsa2012_constbytes"
+--   c_crypto_core_salsa2012_constbytes :: CSize
+
+-- src/libsodium/crypto_core/salsa208/core_salsa208_api.c
+-- foreign import ccall "crypto_core_salsa208_outputbytes"
+--   c_crypto_core_salsa208_outputbytes :: CSize
+-- foreign import ccall "crypto_core_salsa208_inputbytes"
+--   c_crypto_core_salsa208_inputbytes :: CSize
+-- foreign import ccall "crypto_core_salsa208_keybytes"
+--   c_crypto_core_salsa208_keybytes :: CSize
+-- foreign import ccall "crypto_core_salsa208_constbytes"
+--   c_crypto_core_salsa208_constbytes :: CSize
+
+-- src/libsodium/crypto_generichash/blake2/generichash_blake2_api.c
+-- foreign import ccall "crypto_generichash_blake2b_blockbytes"
+--   c_crypto_generichash_blake2b_blockbytes :: CSize
+
+-- src/libsodium/crypto_generichash/crypto_generichash.c
+-- foreign import ccall "crypto_generichash_bytes"
+--   c_crypto_generichash_bytes :: CSize
+-- foreign import ccall "crypto_generichash_keybytes"
+--   c_crypto_generichash_keybytes :: CSize
+-- foreign import ccall "crypto_generichash_blockbytes"
+--   c_crypto_generichash_blockbytes :: CSize
+
+-- src/libsodium/crypto_hash/sha256/hash_sha256_api.c
+-- foreign import ccall "crypto_hash_sha256_bytes"
+--   c_crypto_hash_sha256_bytes :: CSize
+
+-- src/libsodium/crypto_hash/sha512/hash_sha512_api.c
+-- foreign import ccall "crypto_hash_sha512_bytes"
+--   c_crypto_hash_sha512_bytes :: CSize
+
+-- src/libsodium/crypto_hashblocks/sha256/hashblocks_sha256_api.c
+-- foreign import ccall "crypto_hashblocks_sha256_statebytes"
+--   c_crypto_hashblocks_sha256_statebytes :: CSize
+-- foreign import ccall "crypto_hashblocks_sha256_blockbytes"
+--   c_crypto_hashblocks_sha256_blockbytes :: CSize
+
+-- src/libsodium/crypto_hashblocks/sha512/hashblocks_sha512_api.c
+-- foreign import ccall "crypto_hashblocks_sha512_statebytes"
+--   c_crypto_hashblocks_sha512_statebytes :: CSize
+-- foreign import ccall "crypto_hashblocks_sha512_blockbytes"
+--   c_crypto_hashblocks_sha512_blockbytes :: CSize
+
+-- src/libsodium/crypto_onetimeauth/poly1305/onetimeauth_poly1305_api.c
+-- foreign import ccall "crypto_onetimeauth_poly1305_bytes"
+--   c_crypto_onetimeauth_poly1305_bytes :: CSize
+-- foreign import ccall "crypto_onetimeauth_poly1305_keybytes"
+--   c_crypto_onetimeauth_poly1305_keybytes :: CSize
+
+-- src/libsodium/crypto_secretbox/xsalsa20poly1305/secretbox_xsalsa20poly1305_api.c
+-- foreign import ccall "crypto_secretbox_xsalsa20poly1305_keybytes"
+--   c_crypto_secretbox_xsalsa20poly1305_keybytes :: CSize
+-- foreign import ccall "crypto_secretbox_xsalsa20poly1305_noncebytes"
+--   c_crypto_secretbox_xsalsa20poly1305_noncebytes :: CSize
+-- foreign import ccall "crypto_secretbox_xsalsa20poly1305_zerobytes"
+--   c_crypto_secretbox_xsalsa20poly1305_zerobytes :: CSize
+-- foreign import ccall "crypto_secretbox_xsalsa20poly1305_boxzerobytes"
+--   c_crypto_secretbox_xsalsa20poly1305_boxzerobytes :: CSize
+
+-- foreign import ccall "crypto_shorthash_siphash24_bytes"
+--   c_crypto_shorthash_siphash24_bytes :: CSize
+
+-- src/libsodium/crypto_sign/ed25519/sign_ed25519_api.c
+-- foreign import ccall "crypto_sign_ed25519_bytes"
+--   c_crypto_sign_ed25519_bytes :: CSize
+-- foreign import ccall "crypto_sign_ed25519_publickeybytes"
+--   c_crypto_sign_ed25519_publickeybytes :: CSize
+-- foreign import ccall "crypto_sign_ed25519_secretkeybytes"
+--   c_crypto_sign_ed25519_secretkeybytes :: CSize
+
+-- src/libsodium/crypto_sign/edwards25519sha512batch/sign_edwards25519sha512batch_api.c
+-- foreign import ccall "crypto_sign_edwards25519sha512batch_bytes"
+--   c_crypto_sign_edwards25519sha512batch_bytes :: CSize
+-- foreign import ccall "crypto_sign_edwards25519sha512batch_publickeybytes"
+--   c_crypto_sign_edwards25519sha512batch_publickeybytes :: CSize
+-- foreign import ccall "crypto_sign_edwards25519sha512batch_secretkeybytes"
+--   c_crypto_sign_edwards25519sha512batch_secretkeybytes :: CSize
+
+-- src/libsodium/crypto_stream/aes128ctr/stream_aes128ctr_api.c
+-- foreign import ccall "crypto_stream_aes128ctr_keybytes"
+--   c_crypto_stream_aes128ctr_keybytes :: CSize
+-- foreign import ccall "crypto_stream_aes128ctr_noncebytes"
+--   c_crypto_stream_aes128ctr_noncebytes :: CSize
+-- foreign import ccall "crypto_stream_aes128ctr_beforenmbytes"
+--   c_crypto_stream_aes128ctr_beforenmbytes :: CSize
+
+-- src/libsodium/crypto_stream/aes256estream/stream_aes256estream_api.c
+-- foreign import ccall "crypto_stream_aes256estream_keybytes"
+--   c_crypto_stream_aes256estream_keybytes :: CSize
+-- foreign import ccall "crypto_stream_aes256estream_noncebytes"
+--   c_crypto_stream_aes256estream_noncebytes :: CSize
+-- foreign import ccall "crypto_stream_aes256estream_beforenmbytes"
+--   c_crypto_stream_aes256estream_beforenmbytes :: CSize
+
+-- src/libsodium/crypto_stream/salsa2012/stream_salsa2012_api.c
+-- foreign import ccall "crypto_stream_salsa2012_keybytes"
+--   c_crypto_stream_salsa2012_keybytes :: CSize
+-- foreign import ccall "crypto_stream_salsa2012_noncebytes"
+--   c_crypto_stream_salsa2012_noncebytes :: CSize
+
+-- src/libsodium/crypto_stream/salsa208/stream_salsa208_api.c
+-- foreign import ccall "crypto_stream_salsa208_keybytes"
+--   c_crypto_stream_salsa208_keybytes :: CSize
+-- foreign import ccall "crypto_stream_salsa208_noncebytes"
+--   c_crypto_stream_salsa208_noncebytes :: CSize
+
+-- src/libsodium/crypto_stream/xsalsa20/stream_xsalsa20_api.c
+-- foreign import ccall "crypto_stream_xsalsa20_keybytes"
+--   c_crypto_stream_xsalsa20_keybytes :: CSize
+-- foreign import ccall "crypto_stream_xsalsa20_noncebytes"
+--   c_crypto_stream_xsalsa20_noncebytes :: CSize
+
+-- src/libsodium/crypto_verify/16/verify_16_api.c
+-- foreign import ccall "crypto_verify_16_bytes"
+--   c_crypto_verify_16_bytes :: CSize
+
+-- src/libsodium/crypto_verify/32/verify_32_api.c
+-- foreign import ccall "crypto_verify_32_bytes"
+--   c_crypto_verify_32_bytes :: CSize
diff --git a/src/Crypto/Saltine/Internal/Util.hs b/src/Crypto/Saltine/Internal/Util.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Saltine/Internal/Util.hs
@@ -0,0 +1,85 @@
+module Crypto.Saltine.Internal.Util where
+
+import Foreign.C
+import Foreign.Ptr
+import Foreign.ForeignPtr
+import System.IO.Unsafe
+import Data.Word
+import Data.Monoid
+import qualified Data.Vector.Storable as V
+import qualified Data.Vector.Storable.Mutable as VM
+
+import Data.STRef
+
+foreign import ccall "randombytes_buf"
+  c_randombytes_buf :: Ptr Word8 -> CInt -> IO ()
+
+-- | Increments a 'V.Vector' with 0 as the least-significant index.
+nudgeVector :: V.Vector Word8 -> V.Vector Word8
+nudgeVector v = V.modify go v
+  where go mv = do
+          iref <- newSTRef 0
+          loop iref mv
+        loop iref mv = do
+          i <- readSTRef iref
+          if i < len
+            then do val <- VM.read mv i
+                    if val == maxBound
+                       then do VM.write mv i minBound
+                               modifySTRef iref succ
+                               loop iref mv
+                      else VM.write mv i (succ val)
+            else return ()
+        len = V.length v
+
+-- | 0-pad a vector
+pad :: (VM.Storable a, Num a) => Int -> V.Vector a -> V.Vector a
+pad n = mappend (V.replicate n 0)
+
+-- | Remove a 0-padding from a vector
+unpad :: VM.Storable a => Int -> V.Vector a -> V.Vector a
+unpad = V.drop
+
+-- | Converts a C-convention errno to an Either
+handleErrno :: CInt -> (a -> Either String a)
+handleErrno err a = case err of
+  0  -> Right a
+  -1 -> Left "failed"
+  n  -> Left ("unexpected error code: " ++ show n)
+
+unsafeDidSucceed :: IO CInt -> Bool
+unsafeDidSucceed = go . unsafePerformIO
+  where go 0 = True
+        go _ = False
+
+-- | Convenience function for accessing constant C vectors
+constVectors :: VM.Storable a => [V.Vector a] -> ([Ptr a] -> IO b) -> IO b
+-- Manual unfold of: @constVectors = runContT . mapM (ContT . V.unsafeWith)@
+constVectors = foldr (\v kk -> \k -> (V.unsafeWith v) (\a -> kk (\as -> k (a:as)))) ($ [])
+
+-- | Slightly safer cousin to 'buildUnsafeCVector' that remains in the
+-- 'IO' monad.
+buildUnsafeCVector' :: VM.Storable a => Int -> (Ptr a -> IO b) -> IO (b, V.Vector a)
+buildUnsafeCVector' n k = do
+  buf <- mallocForeignPtrArray n
+  b <- withForeignPtr buf k
+  vec <- V.unsafeFreeze (VM.unsafeFromForeignPtr buf 0 n)
+  return (b, vec)
+
+-- | Extremely unsafe function, use with utmost care! Builds a new
+-- Vector using a ccall which is given access to the raw underlying
+-- pointer. Overwrites are UNCHECKED and 'unsafePerformIO' is used so
+-- it's difficult to predict the timing of the 'Vector' creation.
+buildUnsafeCVector :: VM.Storable a => Int -> (Ptr a -> IO b) -> (b, V.Vector a)
+buildUnsafeCVector n = unsafePerformIO . buildUnsafeCVector' n
+
+-- | Build a sized random 'V.Vector' using Sodium's bindings to
+-- @/dev/urandom@.
+randomVector :: Int -> IO (V.Vector Word8)
+randomVector n = do
+  (_, vec) <- buildUnsafeCVector' n (`c_randombytes_buf` fromIntegral n)
+  return vec
+
+-- | To prevent a dependency on package 'errors'
+hush :: Either s a -> Maybe a
+hush = either (const Nothing) Just
diff --git a/tests/Main.hs b/tests/Main.hs
new file mode 100644
--- /dev/null
+++ b/tests/Main.hs
@@ -0,0 +1,22 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main where
+
+import SecretBoxProperties (testSecretBox)
+import BoxProperties (testBox)
+import StreamProperties (testStream)
+import AuthProperties (testAuth)
+import OneTimeAuthProperties (testOneTimeAuth)
+import SignProperties (testSign)
+
+import Test.Framework
+
+main :: IO ()
+main = defaultMain [
+  testBox,
+  testSecretBox,
+  testStream,
+  testAuth,
+  testOneTimeAuth,
+  testSign
+  ]
