diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2017, Gregor Kleen
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Gregor Kleen nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/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/changes.md b/changes.md
new file mode 100644
--- /dev/null
+++ b/changes.md
@@ -0,0 +1,3 @@
+# 0.0.0
+
+First published version
diff --git a/cryptoids.cabal b/cryptoids.cabal
new file mode 100644
--- /dev/null
+++ b/cryptoids.cabal
@@ -0,0 +1,34 @@
+name: cryptoids
+version: 0.0.0
+cabal-version: >=1.10
+build-type: Simple
+license: BSD3
+license-file: LICENSE
+maintainer: aethoago@141.li
+synopsis: Reversable and secure encoding of object ids as a bytestring
+category: cryptography
+author: Gregor Kleen
+extra-source-files:
+    changes.md
+
+source-repository head
+    type: git
+    location: https://git.rheperire.org/cryptoids
+    subdir: cryptoids
+
+library
+    exposed-modules:
+        Data.CryptoID.Poly
+    build-depends:
+        base >=4.9.1.0 && <4.11,
+        cryptoids-types ==0.0.0,
+        cryptonite >=0.23 && <0.25,
+        bytestring >=0.10.8.1 && <0.11,
+        binary >=0.8.3.0 && <0.9,
+        memory >=0.14.6 && <0.15,
+        mtl >=2.2.1 && <2.3
+    default-language: Haskell2010
+    default-extensions: RankNTypes DataKinds GeneralizedNewtypeDeriving
+                        ViewPatterns RecordWildCards FlexibleContexts
+    hs-source-dirs: src
+
diff --git a/src/Data/CryptoID/Poly.hs b/src/Data/CryptoID/Poly.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/CryptoID/Poly.hs
@@ -0,0 +1,187 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+
+{-|
+Description: Encryption of bytestrings using a type level nonce for determinism
+License: BSD3
+
+Given a value of a serializable type (like 'Int') we perform serialization and
+compute a cryptographic hash of the associated namespace (carried as a phantom
+type of kind 'Symbol').
+The serialized payload is then encrypted using the symmetric cipher in CBC mode
+using the hashed namespace as an initialization vector (IV).
+
+The probability of detecting a namespace mismatch is thus \(1 - 2^{128-l}\)
+where \(l\) is the length of the serialized payload.
+-}
+module Data.CryptoID.Poly
+  ( CryptoID(..)
+  , CryptoIDKey
+  , genKey
+  , encrypt
+  , decrypt
+  , CryptoIDError(..)
+  , CryptoCipher, CryptoHash
+  ) where
+
+import Data.CryptoID
+
+import Data.Binary
+import Data.Binary.Put
+import Data.Binary.Get
+
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as ByteString
+import qualified Data.ByteString.Char8 as ByteString.Char
+
+import qualified Data.ByteString.Lazy as Lazy (ByteString)
+
+import Data.List (sortOn)
+import Data.Ord (Down(..))
+
+import Data.ByteArray (ByteArrayAccess)
+import qualified Data.ByteArray as ByteArray
+
+import Data.Foldable (asum)
+import Control.Monad.Except
+import Control.Exception
+
+import Data.Typeable
+import GHC.TypeLits
+
+import Crypto.Cipher.Types
+import Crypto.Cipher.Blowfish (Blowfish)
+import Crypto.Hash (hash, Digest)
+import Crypto.Hash.Algorithms (SHAKE128)
+import Crypto.Error
+
+import Crypto.Random.Entropy
+
+
+-- | The symmetric cipher 'BlockCipher' this module uses 
+type CryptoCipher = Blowfish
+-- | The cryptographic 'HashAlgorithm' this module uses
+--
+-- We expect the block size of 'CryptoCipher' to be exactly the size of the
+-- 'Digest' generated by 'CryptoHash' (since a 'Digest' is used as an 'IV').
+--
+-- Violation of this expectation causes runtime errors.
+type CryptoHash   = SHAKE128 64
+  
+
+-- | This newtype ensures only keys of the correct length can be created
+--
+-- Use 'genKey' to securely generate keys.
+--
+-- Use the 'Binary' instance to save and restore values of 'CryptoIDKey' across
+-- executions.
+newtype CryptoIDKey = CryptoIDKey { keyMaterial :: ByteString }
+  deriving (Typeable, ByteArrayAccess)
+
+-- | Does not actually show any key material
+instance Show CryptoIDKey where
+  show = show . typeOf
+
+instance Binary CryptoIDKey where
+  put = putByteString . keyMaterial
+  get = CryptoIDKey <$> getKey (cipherKeySize cipher)
+    where
+      cipher :: CryptoCipher
+      cipher = undefined
+
+      -- Try key sizes from large to small ('Get' commits to the first branch
+      -- that parses)
+      getKey (KeySizeFixed n) = getByteString n
+      getKey (KeySizeEnum ns) = asum [ getKey $ KeySizeFixed n | n <- sortOn Down ns ]
+      getKey (KeySizeRange min max) = getKey $ KeySizeEnum [max .. min]
+
+
+-- | Error cases that can be encountered during 'encrypt' and 'decrypt'
+data CryptoIDError
+  = AlgorithmError CryptoError
+    -- ^ One of the underlying cryptographic algorithms
+    --   ('CryptoHash' or 'CryptoCipher') failed.
+  | NamespaceHashIsWrongLength ByteString
+    -- ^ The length of the digest produced by 'CryptoHash' does
+    --   not match the block size of 'CryptoCipher'.
+    --
+    -- The offending digest is included.
+    --
+    -- This error should not occur and is included primarily
+    -- for sake of totality.
+  | CiphertextConversionFailed
+    -- ^ The produced 'ByteString' is the wrong length for conversion into a
+    --   ciphertext.
+  | DeserializationError (Lazy.ByteString, ByteOffset, String)
+    -- ^ The plaintext obtained by decrypting a ciphertext with the given
+    --   'CryptoIDKey' in the context of the @namespace@ could not be
+    --   deserialized into a value of the expected @payload@-type.
+    --
+    -- This is expected behaviour if the @namespace@ or @payload@-type does not
+    -- match the ones used during 'encrypt'ion or if the 'ciphertext' was
+    -- tempered with.
+  | InvalidNamespaceDetected
+    -- ^ We have determined that, allthough deserializion succeded, the
+    --   ciphertext was likely modified during transit or created using a
+    --   different namespace.
+  deriving (Show, Eq)
+
+instance Exception CryptoIDError
+
+-- | Securely generate a new key using system entropy
+--
+-- When 'CryptoCipher' accepts keys of varying lengths this function generates a
+-- key of the largest accepted size.
+genKey :: MonadIO m => m CryptoIDKey
+genKey = CryptoIDKey <$> liftIO (getEntropy keySize)
+  where
+    keySize' = cipherKeySize (undefined :: CryptoCipher)
+
+    keySize
+      | KeySizeFixed n <- keySize' = n
+      | KeySizeEnum ns <- keySize' = maximum ns
+      | KeySizeRange _ max <- keySize' = max
+  
+  
+-- | @pad err size src@ appends null bytes to @src@ until it has length that is
+--   a multiple of @size@.
+pad :: ByteArrayAccess a => Int -> a -> ByteString
+pad n (ByteArray.unpack -> src) = ByteString.pack $ src ++ replicate (l `mod` n) 0
+  where
+    l = length src
+
+-- | Use 'CryptoHash' to generate a 'Digest' of the Symbol passed as proxy type
+namespace' :: forall proxy namespace m.
+              ( KnownSymbol namespace, MonadError CryptoIDError m
+              ) => proxy namespace -> m (IV CryptoCipher)
+namespace' p = case makeIV namespaceHash of
+                 Nothing -> throwError . NamespaceHashIsWrongLength $ ByteArray.convert namespaceHash
+                 Just iv -> return iv
+  where
+    namespaceHash :: Digest CryptoHash
+    namespaceHash = hash . ByteString.Char.pack $ symbolVal p
+
+-- | Wrap failure of one of the cryptographic algorithms as a 'CryptoIDError'
+cryptoFailable :: MonadError CryptoIDError m => CryptoFailable a -> m a
+cryptoFailable = either (throwError . AlgorithmError) return . eitherCryptoError
+
+-- | Encrypt an arbitrary serializable value
+encrypt :: forall m namespace.
+           ( KnownSymbol namespace
+           , MonadError CryptoIDError m
+           ) => CryptoIDKey -> ByteString -> m (CryptoID namespace ByteString)
+encrypt (keyMaterial -> key) plaintext = do
+  cipher <- cryptoFailable (cipherInit key :: CryptoFailable CryptoCipher)
+  namespace <- namespace' (Proxy :: Proxy namespace)
+  return . CryptoID . cbcEncrypt cipher namespace $ pad (blockSize cipher) plaintext
+
+
+-- | Decrypt an arbitrary serializable value
+decrypt :: forall m namespace.
+           ( KnownSymbol namespace
+           , MonadError CryptoIDError m
+           ) => CryptoIDKey -> CryptoID namespace ByteString -> m ByteString
+decrypt (keyMaterial -> key) CryptoID{..} = do
+  cipher <- cryptoFailable (cipherInit key :: CryptoFailable CryptoCipher)
+  namespace <- namespace' (Proxy :: Proxy namespace)
+  return $ cbcDecrypt cipher namespace ciphertext
+
