diff --git a/Dust.cabal b/Dust.cabal
new file mode 100644
--- /dev/null
+++ b/Dust.cabal
@@ -0,0 +1,124 @@
+Name:                Dust
+Version:             2.0
+Description:         Dust is a polymorphic protocol engine designed to circumvent Internet filtering based on protocol identification
+Synopsis:            Polymorphic protocol engine
+Category:            Network
+License:             GPL
+License-file:        LICENSE
+Author:              Brandon Wiley
+Maintainer:          brandon@ischool.utexas.edu
+Build-Type:          Simple
+Cabal-Version:       >=1.8
+
+Library
+  Build-Depends:
+    base >= 3 && < 5,
+    bytestring,
+    entropy,
+    network,
+    cereal,
+    ghc-prim,
+    binary,
+    random,
+    random-extras,
+    random-source,
+    random-fu,
+    containers,
+    directory,
+    split,
+    cipher-aes
+  Extensions:
+    ForeignFunctionInterface
+
+  Exposed-modules:
+      Dust.Network.TcpServer
+      Dust.Network.UdpServer
+      Dust.Network.TcpClient
+      Dust.Network.Util
+      Dust.Model.PacketLength
+      Dust.Model.Huffman
+      Dust.Model.Content
+      Dust.Core.Invite
+      Dust.Model.Observations
+      Dust.Model.Stats
+      Dust.Crypto.Keys
+      Dust.Crypto.ECDSA
+      Dust.Crypto.ECDH
+      Dust.Crypto.DustCipher
+
+
+--  if os(windows)
+--    build-depends: Win32
+--    C-sources: lib/curve25519-donna.c, lib/ed25519-donna/ed25519.c
+--    extra-lib-dirs=C:\OpenSSL-Win64 /OpenSSL-Win64 C:\OpenSSL-Win64\lib C:\OpenSSL-Win64\bin
+--    extra-libraries: ssleay32 eay32
+
+  if os(linux) || os(darwin)
+    C-sources: lib/curve25519-donna.c, lib/ed25519-donna/ed25519.c
+    Include-Dirs: lib
+    extra-libraries: crypto
+    Exposed-modules:
+      Dust.Crypto.Curve25519
+      Dust.Crypto.Ed25519
+      Dust.Core.DustPacket
+      Dust.Core.CryptoProtocol
+      Dust.Core.WireProtocol
+      Dust.Core.WireProtocolHandler
+      Dust.Network.DustServer
+      Dust.Model.Port
+      Dust.Model.Packet
+
+test-suite crypto
+  type: exitcode-stdio-1.0
+  main-is: TestCrypto.hs
+  ghc-options: -w -threaded -rtsopts -with-rtsopts=-N
+  hs-source-dirs: tests
+
+  build-depends:
+    base,
+    Dust,
+    HUnit >= 1.2,
+    test-framework       >= 0.6,
+    test-framework-hunit >= 0.2,
+    test-framework-quickcheck2,
+--    test-framework-th,
+    bytestring,
+    cipher-aes,
+    cereal,
+    ghc-prim,
+    QuickCheck
+
+test-suite core
+  type: exitcode-stdio-1.0
+  main-is: TestCore.hs
+  ghc-options: -w -threaded -rtsopts -with-rtsopts=-N
+  hs-source-dirs: tests
+
+  build-depends:
+    base,
+    Dust,
+    HUnit >= 1.2,
+    test-framework       >= 0.6,
+    test-framework-hunit >= 0.2,
+    test-framework-quickcheck2,
+    test-framework-th,
+    bytestring,
+    cipher-aes,
+    cereal,
+    ghc-prim,
+    QuickCheck
+
+-- test-suite model
+--   type: exitcode-stdio-1.0
+--   main-is: TestModel.hs
+--   ghc-options: -w -threaded -rtsopts -with-rtsopts=-N
+--   hs-source-dirs: tests
+
+--   build-depends:
+--     base,
+--     Dust,
+--     HUnit >= 1.2,
+--     test-framework       >= 0.6,
+--     test-framework-hunit >= 0.2,
+--     test-framework-th,
+--     base >= 3 && < 5, bytestring, cipher-aes, entropy, network, cereal, ghc-prim, binary, random, random-extras, random-source, random-fu, containers
diff --git a/Dust/Core/CryptoProtocol.hs b/Dust/Core/CryptoProtocol.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Core/CryptoProtocol.hs
@@ -0,0 +1,51 @@
+{-# LANGUAGE DeriveGeneric, DefaultSignatures #-} -- For automatic generation of cereal put and get
+
+module Dust.Core.CryptoProtocol
+(
+ Session(..),
+ Stream(..),
+ StreamHeader(..),
+ makeSession,
+ makeEncrypt,
+ makeDecrypt,
+ makeHeader,
+ makeStream,
+ makeEncoder
+) where
+
+import Dust.Core.DustPacket
+import Dust.Crypto.DustCipher
+import Dust.Crypto.ECDH
+import Dust.Crypto.Keys
+import Dust.Model.TrafficModel
+
+data Session = Session Keypair PublicKey IV deriving (Show)
+data Stream = Stream StreamHeader CipherDataPacket deriving (Show)
+data StreamHeader = StreamHeader PublicKey IV deriving (Show)
+
+makeSession :: Keypair -> PublicKey -> IV -> Session
+makeSession keypair publicKey iv = Session keypair publicKey iv
+
+makeEncrypt :: Session -> (Plaintext -> Ciphertext)
+makeEncrypt (Session (Keypair myPublic myPrivate) otherPublic iv) =
+    let key = createShared myPrivate otherPublic
+    in encrypt key iv
+
+makeDecrypt :: Session -> (Ciphertext -> Plaintext)
+makeDecrypt (Session (Keypair myPublic myPrivate) otherPublic iv) =
+    let key = createShared myPrivate otherPublic
+    in decrypt key iv
+
+makeHeader :: PublicKey -> IV -> StreamHeader
+makeHeader publicKey iv = StreamHeader publicKey iv
+
+makeStream :: StreamHeader -> CipherDataPacket -> Stream
+makeStream header cipherPacket = Stream header cipherPacket
+
+makeEncoder :: Session -> (Plaintext -> Stream)
+makeEncoder session@(Session (Keypair myPublic _) _ iv) =
+    let header = makeHeader myPublic iv
+        cipher = makeEncrypt session
+        encrypter = encryptData cipher
+        stream = makeStream header
+    in stream . encrypter . makePlainPacket
diff --git a/Dust/Core/DustPacket.hs b/Dust/Core/DustPacket.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Core/DustPacket.hs
@@ -0,0 +1,70 @@
+{-# LANGUAGE DeriveGeneric, DefaultSignatures #-} -- For automatic generation of cereal put and get
+
+module Dust.Core.DustPacket
+(
+ PlainHeader(..),
+ CipherHeader(..),
+ PlainDataPacket(..),
+ CipherDataPacket(..),
+ makePlainPacket,
+ makeCipherPacket,
+ encryptData,
+ decryptData,
+ decryptHeader,
+ length32
+) where
+
+import GHC.Generics
+import Dust.Crypto.DustCipher
+import Data.ByteString
+import qualified Data.ByteString as B
+import Data.Serialize
+import Data.Int
+
+data PlainHeader = PlainHeader {
+    payloadLength :: Int32
+} deriving (Show, Eq, Generic)
+
+data CipherHeader = CipherHeader {
+    encryptedPayloadLength :: Ciphertext
+} deriving (Show, Eq, Generic)
+
+data PlainDataPacket = PlainDataPacket PlainHeader Plaintext deriving (Show, Eq, Generic)
+data CipherDataPacket = CipherDataPacket CipherHeader Ciphertext deriving (Show, Eq, Generic)
+
+instance Serialize PlainHeader
+instance Serialize CipherHeader
+instance Serialize PlainDataPacket
+instance Serialize CipherDataPacket
+
+makePlainPacket :: Plaintext -> PlainDataPacket
+makePlainPacket (Plaintext bs) = PlainDataPacket (PlainHeader (length32 bs)) (Plaintext bs)
+
+makeCipherPacket :: Ciphertext -> Ciphertext -> CipherDataPacket
+makeCipherPacket lengthCiphertext ciphertext = CipherDataPacket (CipherHeader lengthCiphertext) ciphertext
+
+decryptHeader :: (Ciphertext -> Plaintext) -> CipherHeader -> PlainHeader
+decryptHeader cipher (CipherHeader ciphertext) =
+    let (Plaintext lengthBytes) = cipher ciphertext
+        lengthValue = decodeHeader lengthBytes
+    in PlainHeader lengthValue
+
+decodeHeader :: ByteString -> Int32
+decodeHeader bs =
+    case (decode bs)::(Either String Int32) of
+        Left _ -> 0
+        Right value -> value
+
+encryptData :: (Plaintext -> Ciphertext) -> PlainDataPacket -> CipherDataPacket
+encryptData cipher (PlainDataPacket header plaintext) =
+    let cipherheader = cipher (Plaintext (encode header))
+        ciphertext = cipher plaintext
+    in CipherDataPacket (CipherHeader cipherheader) ciphertext
+
+decryptData :: (Ciphertext -> Plaintext) -> CipherDataPacket -> PlainDataPacket
+decryptData cipher (CipherDataPacket header ciphertext) =
+    let plaintext@(Plaintext bs) = cipher ciphertext
+    in PlainDataPacket (PlainHeader (length32 bs)) plaintext
+
+length32 :: ByteString -> Int32
+length32 bs = (fromIntegral (B.length bs)) :: Int32
diff --git a/Dust/Core/Invite.hs b/Dust/Core/Invite.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Core/Invite.hs
@@ -0,0 +1,25 @@
+module Dust.Core.Invite
+(
+)
+where
+
+import Network
+import Data.ByteString
+
+import Dust.Crypto.Keys
+import Dust.Crypto.DustCipher
+import Dust.Model.TrafficModel
+
+data Invite = Invite {
+    address :: Address,
+    encryptionKey :: PublicKey,
+    signingKey :: PublicKey,
+    model :: TrafficModel,
+    nonce :: ByteString
+}
+
+data Address = Address {
+    v6 :: Bool,
+    host :: String,
+    port :: PortNumber
+}
diff --git a/Dust/Core/WireProtocol.hs b/Dust/Core/WireProtocol.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Core/WireProtocol.hs
@@ -0,0 +1,53 @@
+{-# LANGUAGE DeriveGeneric, DefaultSignatures #-} -- For automatic generation of cereal put and get
+
+module Dust.Core.WireProtocol
+(
+ getSession,
+ getPacket,
+ putSession,
+ putPacket
+) where
+
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as B
+import GHC.Int
+import Data.Serialize.Put (Put, putByteString)
+import Data.Serialize.Get (Get, getByteString)
+
+import Dust.Core.DustPacket
+import Dust.Crypto.DustCipher
+import Dust.Crypto.ECDH
+import Dust.Crypto.Keys
+import Dust.Model.TrafficModel
+import Dust.Core.CryptoProtocol
+
+getSession :: Keypair -> Get Session
+getSession keypair = do
+    public <- getByteString 32
+    iv     <- getByteString 16
+    return $ Session keypair (PublicKey public) (IV iv)
+
+putSession :: Session -> Put
+putSession (Session (Keypair (PublicKey public) _) _ (IV iv)) = do
+  putByteString public
+  putByteString iv
+
+getPacket :: Session -> Get Plaintext
+getPacket session = do
+  packetBytes <- getByteString 4
+  let cipherHeader = CipherHeader (Ciphertext packetBytes)
+  let cipher = makeDecrypt session
+  let plainPacketHeader = decryptHeader cipher cipherHeader
+  let PlainHeader packetLength = plainPacketHeader
+  let packetLen = (fromIntegral packetLength)::Int
+
+  payloadBytes <- getByteString packetLen
+  let ciphertext = Ciphertext payloadBytes
+  return $ cipher ciphertext
+
+putPacket :: Session -> Plaintext -> Put
+putPacket session plaintext = do
+  let packet = makePlainPacket plaintext
+  let (CipherDataPacket (CipherHeader (Ciphertext header)) (Ciphertext payload)) = encryptData (makeEncrypt session) packet
+  putByteString header
+  putByteString payload
diff --git a/Dust/Core/WireProtocolHandler.hs b/Dust/Core/WireProtocolHandler.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Core/WireProtocolHandler.hs
@@ -0,0 +1,46 @@
+{-# LANGUAGE DeriveGeneric, DefaultSignatures #-} -- For automatic generation of cereal put and get
+
+module Dust.Core.WireProtocolHandler
+(
+ Packets(..),
+ encodeMessage,
+ decodeMessage,
+ decodeSession,
+ decodePacket
+) where
+
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as BL
+import GHC.Int
+import Data.Serialize.Put (Put, runPut)
+import Data.Serialize.Get (runGetState)
+
+import Dust.Core.DustPacket
+import Dust.Crypto.DustCipher
+import Dust.Crypto.ECDH
+import Dust.Crypto.Keys
+import Dust.Model.TrafficModel
+import Dust.Core.CryptoProtocol
+import Dust.Core.WireProtocol
+
+data Packets = Packets [ByteString]
+
+encodeMessage :: Session -> Plaintext -> Packets
+encodeMessage session plaintext = do
+  let header = runPut (putSession session)
+  let packet = runPut (putPacket session plaintext)
+  Packets [B.append header packet]
+
+decodeMessage :: Keypair -> ByteString -> Either String (Plaintext, ByteString)
+decodeMessage keypair buffer = do
+  let eitherSession = decodeSession keypair buffer
+  case eitherSession of
+    Left error -> Left error
+    Right (session, rest) -> decodePacket session rest
+
+decodeSession :: Keypair -> ByteString -> Either String (Session, ByteString)
+decodeSession keypair buffer = runGetState (getSession keypair) buffer 0
+
+decodePacket :: Session -> ByteString -> Either String (Plaintext, ByteString)
+decodePacket session buffer = runGetState (getPacket session) buffer 0
diff --git a/Dust/Crypto/Curve25519.hs b/Dust/Crypto/Curve25519.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Crypto/Curve25519.hs
@@ -0,0 +1,39 @@
+{-# CFILES lib/curve25519-donna.c #-}
+{-# LANGUAGE ForeignFunctionInterface #-}
+
+module Dust.Crypto.Curve25519 (curve25519) where
+
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as B
+import Data.ByteString.Unsafe (unsafeUseAsCString)
+import Foreign.C.String (CString)
+
+import Foreign.Marshal.Unsafe (unsafeLocalState)
+
+curve25519 :: ByteString -> ByteString -> ByteString
+curve25519 bs1 bs2 = curve
+	where
+	Just curve = unsafeLocalState $ unsafe_curve25519 bs1 bs2
+
+unsafe_curve25519 :: ByteString -> ByteString -> IO (Maybe ByteString)
+unsafe_curve25519 secret basepoint
+    | B.length secret >= 32 && B.length basepoint >= 32 =
+        -- This ByteString will be overwritten by the C call, but so long as
+        -- the C side does not keep a reference to it afterward, it does what we
+        -- expect and saves us a copy
+        unsafeUseAsCString outBS $ \output ->
+            B.useAsCString secret $ \csecret ->
+                B.useAsCString basepoint $ \cbasepoint -> do
+                    result <- c_curve25519_donna output csecret cbasepoint
+                    case result of
+                        0 -> return $ Just outBS
+                        _ -> return Nothing
+    | otherwise = return Nothing
+    where
+    outBS = B.replicate 32 0xAB
+
+-- Should be Ptr Word8 (not CString), but this should be safe if we just
+-- use them as bytes
+
+foreign import ccall unsafe "curve25519_donna" c_curve25519_donna ::
+    CString -> CString -> CString -> IO Int
diff --git a/Dust/Crypto/DustCipher.hs b/Dust/Crypto/DustCipher.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Crypto/DustCipher.hs
@@ -0,0 +1,45 @@
+{-# LANGUAGE DeriveGeneric, DefaultSignatures #-} -- For automatic generation of cereal put and get
+
+module Dust.Crypto.DustCipher
+(
+  EncryptionKey(..),
+  IV(..),
+  Plaintext(..),
+  Ciphertext(..),
+
+  encrypt,
+  decrypt,
+  createIV
+) where
+
+import GHC.Generics
+import Data.ByteString
+import Data.Serialize
+import qualified Crypto.Cipher.AES as AES
+import System.Entropy
+
+import Dust.Crypto.Keys
+
+data EncryptionKey = EncryptionKey ByteString deriving (Show, Eq)
+newtype IV = IV ByteString deriving (Show, Eq, Generic)
+newtype Plaintext = Plaintext ByteString deriving (Show, Eq, Generic)
+newtype Ciphertext = Ciphertext ByteString deriving (Show, Eq, Generic)
+
+instance Serialize IV
+instance Serialize Plaintext
+instance Serialize Ciphertext
+
+encrypt :: EncryptionKey -> IV -> Plaintext -> Ciphertext
+encrypt (EncryptionKey keyBytes) (IV iv) (Plaintext plaintext) =
+  let aesKey = AES.initAES keyBytes
+  in Ciphertext $ AES.encryptCTR aesKey iv plaintext
+
+decrypt :: EncryptionKey -> IV -> Ciphertext -> Plaintext
+decrypt (EncryptionKey keyBytes) (IV iv) (Ciphertext ciphertext) =
+  let aesKey = AES.initAES keyBytes
+  in Plaintext $ AES.decryptCTR aesKey iv ciphertext
+
+createIV :: IO (IV)
+createIV = do
+    entropy <- getEntropy 16
+    return (IV entropy)
diff --git a/Dust/Crypto/ECDH.hs b/Dust/Crypto/ECDH.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Crypto/ECDH.hs
@@ -0,0 +1,47 @@
+module Dust.Crypto.ECDH
+(
+  splitSecret,
+  createPrivate,
+  createPublic,
+  createKeypair,
+  createShared,
+  createEphemeral
+) where
+
+import Data.ByteString as B
+import Data.Word
+import Data.Bits
+import System.Entropy
+
+import Dust.Crypto.Keys
+import Dust.Crypto.DustCipher
+import Dust.Crypto.Curve25519
+
+createEphemeral :: IO (Keypair)
+createEphemeral = do
+    entropy <- getEntropy 32
+    return (createKeypair entropy)
+
+splitSecret :: ByteString -> (Word8,ByteString,Word8)
+splitSecret bs = let firstByte = B.head bs
+                     lastByte  = B.last bs
+                     middle = B.tail (B.init bs)
+                 in (firstByte,middle,lastByte)
+
+createPrivate :: ByteString -> PrivateKey
+createPrivate bs = let (firstByte,middle,lastByte) = splitSecret bs
+                       firstByte' = firstByte .&. 248
+                       lastByte'  = (lastByte .&. 127) .|. 64
+                   in PrivateKey (firstByte' `cons` middle `snoc` lastByte')
+
+createPublic :: PrivateKey -> PublicKey
+createPublic private = let bps = pack [9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
+                       in PublicKey (curve25519 (privateBytes private) bps)
+
+createKeypair :: ByteString -> Keypair
+createKeypair entropy = let private = createPrivate entropy
+                            public = createPublic private
+                        in Keypair public private
+
+createShared :: PrivateKey -> PublicKey -> EncryptionKey
+createShared private public = EncryptionKey (curve25519 (privateBytes private) (publicBytes public))
diff --git a/Dust/Crypto/ECDSA.hs b/Dust/Crypto/ECDSA.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Crypto/ECDSA.hs
@@ -0,0 +1,42 @@
+{-# LANGUAGE DeriveGeneric, DefaultSignatures #-} -- For automatic generation of cereal put and get
+
+module Dust.Crypto.ECDSA
+(
+  Signature,
+  Signedtext(..),
+  createPrivate,
+  createPublic,
+  createSigningKeypair,
+  sign,
+  verify
+) where
+
+import GHC.Generics (Generic)
+import Data.Serialize (Serialize)
+import Data.ByteString (ByteString)
+
+import Dust.Crypto.Keys
+import Dust.Crypto.Ed25519
+
+newtype Signature = Signature { signatureBytes :: ByteString } deriving (Show, Eq, Generic)
+instance Serialize Signature
+
+data Signedtext = Signedtext PublicKey Signature ByteString deriving (Eq, Show, Generic)
+instance Serialize Signedtext
+
+createPrivate :: ByteString -> PrivateKey
+createPrivate = PrivateKey
+
+createPublic :: PrivateKey -> PublicKey
+createPublic = PublicKey . ed25519_publickey . privateBytes
+
+createSigningKeypair :: ByteString -> Keypair
+createSigningKeypair entropy = Keypair (createPublic private) private
+    where
+    private = createPrivate entropy
+
+sign :: ByteString -> Keypair -> Signedtext
+sign msg (Keypair pubkey private) = Signedtext pubkey (Signature $ ed25519_sign msg (privateBytes private) (publicBytes pubkey)) msg
+
+verify :: Signedtext -> Bool
+verify (Signedtext pubkey signature msg) = ed25519_sign_open msg (publicBytes pubkey) (signatureBytes signature)
diff --git a/Dust/Crypto/Ed25519.hs b/Dust/Crypto/Ed25519.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Crypto/Ed25519.hs
@@ -0,0 +1,85 @@
+{-# CFILES lib/ed25519-donna/ed25519.c #-}
+{-# LANGUAGE ForeignFunctionInterface #-}
+
+module Dust.Crypto.Ed25519
+(
+    ed25519_publickey,
+    ed25519_sign_open,
+    ed25519_sign
+) where
+
+import qualified Data.ByteString as B
+import Data.ByteString (ByteString)
+import Data.ByteString.Unsafe (unsafeUseAsCString)
+
+import Foreign.C
+import Foreign.Marshal.Unsafe (unsafeLocalState)
+
+ed25519_publickey :: ByteString -> ByteString
+ed25519_publickey privkey = pubkey
+    where
+    Just pubkey = fmap unsafeLocalState (unsafe_ed25519_publickey privkey)
+
+ed25519_sign_open :: ByteString -> ByteString -> ByteString -> Bool
+ed25519_sign_open msg pubkey signature = result
+	where
+	Just result = fmap unsafeLocalState $ unsafe_ed25519_sign_open msg pubkey signature
+
+ed25519_sign :: ByteString -> ByteString -> ByteString -> ByteString
+ed25519_sign msg secret pubkey = signature
+	where
+	Just signature = fmap unsafeLocalState $ unsafe_ed25519_sign msg secret pubkey
+
+unsafe_ed25519_publickey :: ByteString -> Maybe (IO ByteString)
+unsafe_ed25519_publickey input
+    | B.length input <= 32 = Just $
+        -- This ByteString will be overwritten by the C call, but so long as
+        -- the C side does not keep a reference to it afterward, it does what we
+        -- expect and saves us a copy
+        unsafeUseAsCString outBS $ \output ->
+            B.useAsCString input $ \cinput -> do
+                c_ed25519_publickey cinput output
+                return outBS
+    | otherwise = Nothing
+    where
+    outBS = B.replicate 32 0xAB
+    {-# NOINLINE outBS #-}
+
+unsafe_ed25519_sign_open :: ByteString -> ByteString -> ByteString -> Maybe (IO Bool)
+unsafe_ed25519_sign_open msg pubkey signature
+    | B.length pubkey <= 32 && B.length signature <= 64 = Just $
+        B.useAsCStringLen msg $ \(cmsg, cmsglen) ->
+            B.useAsCString pubkey $ \cpubkey ->
+                B.useAsCString signature $ \csignature ->
+                    let result = c_ed25519_sign_open cmsg (fromIntegral cmsglen) cpubkey csignature in
+                        return (result == 0)
+    | otherwise = Nothing
+
+unsafe_ed25519_sign :: ByteString -> ByteString -> ByteString -> Maybe (IO ByteString)
+unsafe_ed25519_sign msg secret pubkey
+    | B.length secret <= 32 && B.length pubkey <= 32 = Just $
+        -- This ByteString will be overwritten by the C call, but so long as
+        -- the C side does not keep a reference to it afterward, it does what we
+        -- expect and saves us a copy
+        unsafeUseAsCString outBS $ \signature ->
+            B.useAsCStringLen msg $ \(cmsg, cmsglen) ->
+                B.useAsCString secret $ \csecret ->
+                    B.useAsCString pubkey $ \cpubkey -> do
+                        c_ed25519_sign cmsg (fromIntegral cmsglen) csecret cpubkey signature
+                        return outBS
+    | otherwise = Nothing
+    where
+    outBS = B.replicate 64 0xAB
+    {-# NOINLINE outBS #-}
+
+-- Should be Ptr CUChar (not CString), but this should be safe if we just
+-- use them as bytes
+
+foreign import ccall unsafe "ed25519_publickey" c_ed25519_publickey ::
+   CString -> CString -> IO ()
+
+foreign import ccall unsafe "ed25519_sign_open" c_ed25519_sign_open ::
+    CString -> CSize -> CString -> CString -> CInt
+
+foreign import ccall unsafe "ed25519_sign" c_ed25519_sign ::
+    CString -> CSize -> CString -> CString -> CString -> IO ()
diff --git a/Dust/Crypto/Keys.hs b/Dust/Crypto/Keys.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Crypto/Keys.hs
@@ -0,0 +1,76 @@
+{-# LANGUAGE DeriveGeneric, DefaultSignatures #-} -- For automatic generation of cereal put and get
+
+module Dust.Crypto.Keys
+(
+   PublicKey(..),
+   PrivateKey(..),
+   Keypair(..),
+
+   loadKeypair,
+   saveKeypair,
+   loadPublic,
+
+   loadSigningKeypair,
+   saveSigningKeypair
+) where
+
+import GHC.Generics
+import Data.Serialize
+import Data.ByteString
+
+newtype PublicKey = PublicKey { publicBytes :: ByteString } deriving (Show, Eq, Generic)
+newtype PrivateKey = PrivateKey { privateBytes :: ByteString } deriving (Show, Eq, Generic)
+
+instance Serialize PublicKey
+
+data Keypair = Keypair {
+    public :: PublicKey,
+    private :: PrivateKey
+} deriving (Show, Eq)
+
+loadKeypair :: IO (Keypair)
+loadKeypair = do
+    public <- loadPublic "id.pub"
+    private <- loadPrivate "id.priv"
+    return (Keypair public private)
+
+loadPrivate :: FilePath -> IO (PrivateKey)
+loadPrivate path = do
+    key <- loadKey path
+    return (PrivateKey key)
+
+loadPublic :: FilePath -> IO (PublicKey)
+loadPublic path = do
+    key <- loadKey path
+    return (PublicKey key)
+
+loadKey :: FilePath -> IO (ByteString)
+loadKey path = Data.ByteString.readFile path
+
+
+saveKeypair :: Keypair -> IO ()
+saveKeypair (Keypair public private) = do
+    savePublic "id.pub" public
+    savePrivate "id.priv" private
+
+savePrivate :: FilePath -> PrivateKey -> IO ()
+savePrivate path (PrivateKey bs) = do
+    saveKey path bs
+
+savePublic :: FilePath -> PublicKey -> IO ()
+savePublic path (PublicKey bs) = do
+    saveKey path bs
+
+saveKey :: FilePath -> ByteString -> IO ()
+saveKey path bs = Data.ByteString.writeFile path bs
+
+loadSigningKeypair :: IO (Keypair)
+loadSigningKeypair = do
+    public <- loadPublic "sign.pub"
+    private <- loadPrivate "sign.priv"
+    return (Keypair public private)
+
+saveSigningKeypair :: Keypair -> IO ()
+saveSigningKeypair (Keypair public private) = do
+    savePublic "sign.pub" public
+    savePrivate "sign.priv" private
diff --git a/Dust/Model/Content.hs b/Dust/Model/Content.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Model/Content.hs
@@ -0,0 +1,55 @@
+{-# LANGUAGE DeriveGeneric, DefaultSignatures #-} -- For automatic generation of cereal put and get
+
+module Dust.Model.Content
+(
+    ContentModel(..),
+    loadContentModel,
+    makeContentModel,
+    encodeContent,
+    decodeContent
+)
+where
+
+import Data.Word (Word8)
+import System.Random
+import GHC.Generics
+import Data.Serialize
+import Data.Random.Shuffle.Weighted
+import Data.Random.RVar
+import Data.Random
+import Data.Random.Source.IO
+import Data.Random.Source.Std
+import qualified Data.Map as M
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as BL
+
+import Dust.Model.Huffman (HuffmanTree)
+import Dust.Model.Huffman as H
+
+data ContentModel = ContentModel (HuffmanTree Word8) deriving (Generic)
+instance Serialize ContentModel
+
+loadContentModel :: FilePath -> IO ContentModel
+loadContentModel path = do
+    tree <- H.fileToTree path
+    return $ ContentModel $ tree
+
+makeContentModel :: [(Word8, Int)] -> ContentModel
+makeContentModel counts =
+    let tree = H.countsToTree counts
+    in ContentModel $ tree
+
+encodeContent :: (HuffmanTree Word8) -> B.ByteString -> B.ByteString
+encodeContent tree input =
+--    let codez = H.codes tree
+--        bytes = B.unpack input
+--        bits = H.padToEight $ H.encode codez bytes
+--    in (B.concat . BL.toChunks) $ H.bitpack bits
+    input
+
+decodeContent :: (HuffmanTree Word8) -> B.ByteString -> B.ByteString
+decodeContent tree input =
+--    let bits = H.bitunpack input
+--        bytes = H.decode tree bits
+--    in B.pack bytes
+    input
diff --git a/Dust/Model/Huffman.hs b/Dust/Model/Huffman.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Model/Huffman.hs
@@ -0,0 +1,153 @@
+{-# LANGUAGE BangPatterns, DeriveGeneric, DefaultSignatures #-}
+module Dust.Model.Huffman
+(
+   HuffmanTree,
+   encode,
+   decode,
+   bitpack,
+   bitunpack,
+   padToEight,
+   codes,
+   fileToTree,
+   countsToTree
+)
+where
+
+import GHC.Generics
+import Data.Serialize hiding (encode, decode)
+
+import Data.Word
+import Data.Char (intToDigit)
+import Data.Bits ((.|.), shiftL, testBit)
+import Data.List (insertBy, foldl', sortBy, unfoldr)
+import Data.Maybe (fromJust)
+import Data.Ord (comparing)
+import qualified Data.ByteString           as S
+import qualified Data.ByteString.Lazy      as B
+import qualified Data.Map                  as M
+
+import Dust.Model.Stats (histogram)
+
+--------------------------------------------------
+
+data HuffmanTree a
+  = LeafNode a Int
+  | InternalNode Int (HuffmanTree a) (HuffmanTree a)
+  deriving (Generic)
+instance (Serialize a) => Serialize (HuffmanTree a)
+
+-- build a multiline string representation of a huffman tree
+instance Show a => Show (HuffmanTree a) where
+  show =
+      go ""
+    where
+      spaces = map (const ' ')
+      paren s = "(" ++ s ++ ")"
+      go ss (LeafNode s o) = "--" ++ paren (show o) ++ show s ++ "\n"
+      go ss (InternalNode o l r) =
+          let root  = "--" ++ paren (show o) ++ "-+"
+              ss' = ss ++ tail (spaces root)
+              lbranch = go (ss' ++ "|") l
+              rbranch = go (ss' ++ " ") r
+          in root ++ lbranch
+                  ++ ss' ++ "|\n"
+                  ++ ss' ++ "`"
+                  ++ rbranch
+
+frequency :: HuffmanTree a -> Int
+frequency (LeafNode     _ x  ) = x
+frequency (InternalNode x _ _) = x
+
+-- build a huffman tree bototm-up from a list of symbols sorted by frequency
+sortedHuffman :: [(a,Int)] -> HuffmanTree a
+sortedHuffman =
+    -- first, convert each tuple into a Leaf, then combine
+    combine . map toLeaf
+  where
+    -- repeatedly combine lowest frequency trees and reinsert the result into
+    -- the frequency ordered list
+    -- note: a priority queue could help
+    combine [t] = t
+    combine (ta:tb:ts) = combine . insertBy (comparing frequency) (merge ta tb) $ ts
+    -- make an internal node from two trees. the frequency is the sum of the
+    -- two trees frequencies
+    merge ta tb = InternalNode (frequency ta + frequency tb) ta tb
+    -- make a Leaf from a symbol,freq tuple
+    toLeaf = uncurry LeafNode
+
+-- traverse the huffman tree generating a map from the symbol to its huffman
+-- tree path (where False is left, and True is right)
+codes :: Ord a => HuffmanTree a -> M.Map a [Bool]
+codes =
+    M.fromList . go []
+  where
+    -- leaf nodes mark the end of a path to a symbol
+    go p (LeafNode s _) = [(s,reverse p)]
+    -- traverse both branches and accumulate a reverse path
+    go p (InternalNode _ l r) = go (False:p) l ++ go (True:p) r
+
+-- from a table mapping symbols to their corresponding huffman tree bit paths,
+-- replace each instance of a symbol with its bit path
+encode :: Ord a => M.Map a [Bool] -> [a] -> [Bool]
+encode tbl =
+    concatMap get
+  where
+    get x = fromJust (M.lookup x tbl)
+
+-- from a list of bits, navigate a given huffman tree and emit its decoded
+-- symbol when reaching a Leaf
+decode :: HuffmanTree a -> [Bool] -> [a]
+decode t0 xs0 =
+    go t0 xs0
+  where
+    -- reached leaf, emit symbol
+    go (LeafNode s _) bs = s : go t0 bs
+    -- choose path based on bit
+    go (InternalNode _ l r) (b:bs)
+      | not b     = go l bs
+      | otherwise = go r bs
+    go _ [] = []
+
+--------------------------------------------------
+
+swap :: (a,b) -> (b,a)
+swap ~(a,b) = (b,a)
+
+showBits :: [Bool] -> String
+showBits = map (intToDigit . fromEnum)
+
+--------------------------------------------------
+
+bitpack :: [Bool] -> B.ByteString
+bitpack = B.pack . map packByte . takeWhile (not . null) . unfoldr (Just . splitAt 8)
+    where
+    packByte = foldl' (\i b -> (i `shiftL` 1) .|. (fromIntegral $ fromEnum b)) 0
+
+bitunpack :: S.ByteString -> [Bool]
+bitunpack = concatMap (\byte -> map (testBit byte) [7,6..0]) . S.unpack
+
+--------------------------------------------------
+
+padToEight :: [Bool] -> [Bool]
+padToEight bits =
+    let len = length bits
+        rem = len `mod` 8
+        extra = 8-rem
+        padding = replicate extra False
+    in bits ++ padding
+
+fileToTree :: FilePath -> IO (HuffmanTree Word8)
+fileToTree path = do
+    contents <- B.readFile path
+    return $ bytesToTree $ B.unpack contents
+
+bytesToTree :: [Word8] -> (HuffmanTree Word8)
+bytesToTree text =
+    let frequencies = histogram text
+        sortedFrequencies = sortBy (comparing swap) frequencies
+    in sortedHuffman sortedFrequencies
+
+countsToTree :: [(Word8, Int)] -> (HuffmanTree Word8)
+countsToTree frequencies =
+    let sortedFrequencies = sortBy (comparing swap) frequencies
+    in sortedHuffman sortedFrequencies
diff --git a/Dust/Model/Observations.hs b/Dust/Model/Observations.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Model/Observations.hs
@@ -0,0 +1,185 @@
+{-# LANGUAGE DeriveGeneric, DefaultSignatures #-} -- For automatic generation of cereal put and get
+
+module Dust.Model.Observations
+(
+    Observations(..),
+    LengthObservations(..),
+    ContentObservations(..),
+    SubstringObservations(..),
+    emptyObservations,
+    loadObservations,
+    saveObservations,
+    ensureObservations,
+    observePacket,
+    observePort,
+    observeSubstrings,
+    makeModel
+)
+where
+
+import GHC.Generics
+import Data.Serialize
+import Data.ByteString (ByteString, unpack)
+import qualified Data.ByteString as B
+import Data.Int
+import Data.Word
+import System.Directory
+import Data.List (nub)
+import Data.Map (Map(..), alter, empty)
+
+import Dust.Model.PacketLength
+import qualified Dust.Model.Content as C
+import Dust.Model.Port
+import Dust.Model.TrafficModel
+
+data Observations = Observations {
+    lengths :: LengthObservations,
+    content :: ContentObservations,
+    ports   :: PortObservations,
+    strings :: SubstringObservations
+} deriving (Generic, Show)
+instance Serialize Observations
+
+data LengthObservations = LengthObservations [Int] deriving (Generic, Show)
+instance Serialize LengthObservations
+
+data ContentObservations = ContentObservations [Int] deriving (Generic, Show)
+instance Serialize ContentObservations
+
+data PortObservations = PortObservations [Int] deriving (Generic, Show)
+instance Serialize PortObservations
+
+data SubstringObservations = SubstringObservations [(Map ByteString Int)] deriving (Generic, Show)
+instance Serialize SubstringObservations
+
+observePacket :: Observations -> ByteString -> Observations
+observePacket (Observations lobs cobs pobs sobs) bs =
+  let bsl = (fromIntegral $ B.length bs)::Int
+      lobs' = observeLengths lobs (bsl, 1)
+      cobs' = observeByteString cobs bs
+  in Observations lobs' cobs' pobs sobs
+
+observePort :: Observations -> Int -> Observations
+observePort (Observations lobs cobs (PortObservations ports) sobs) port =
+  let pobs' = PortObservations $ nub $ port : ports
+  in Observations lobs cobs pobs' sobs
+
+observeSubstrings :: Observations -> ByteString -> Observations
+observeSubstrings obs bs =
+  let windows = windowed 17 bs  
+  in observeSubstringList obs 0 windows
+
+observeSubstringList :: Observations -> Int -> [ByteString] -> Observations
+observeSubstringList obs offset [] = obs
+observeSubstringList obs offset (bs:bss) = observeSubstringList (observeSubstring obs offset bs) (offset+1) bss
+
+observeSubstring :: Observations -> Int -> ByteString -> Observations
+observeSubstring (Observations lobs cobs pobs (SubstringObservations subs)) offset bs =
+  let sobs' = SubstringObservations $ updateSubstringCount subs offset bs
+  in Observations lobs cobs pobs sobs'  
+
+updateSubstringCount :: [(Map ByteString Int)] -> Int -> ByteString -> [(Map ByteString Int)]
+updateSubstringCount items index bs =
+    let (a, rest) = splitAt index items
+    in case rest of
+      [] -> items
+      (item:b) -> a ++ ((alter updateMapCount bs item):b)  
+
+updateMapCount :: Maybe Int -> Maybe Int
+updateMapCount Nothing = Just 1
+updateMapCount (Just x) = Just (x+1)
+
+windowed :: Int -> ByteString -> [ByteString]
+windowed size ls = 
+  if B.null ls
+  then []
+  else
+    if B.length ls < size
+      then [ls]
+      else (B.take size ls) : (windowed size $ B.tail ls)
+
+observeLengths :: LengthObservations -> (Int, Int) -> LengthObservations
+observeLengths (LengthObservations items) item = LengthObservations $ updateCounts items item
+
+updateCounts :: [Int] -> (Int, Int) -> [Int]
+updateCounts items (index,count) = 
+    let (a, (item:b)) = splitAt index items
+    in  a ++ ((item+count):b)
+
+observeByteString :: ContentObservations -> ByteString -> ContentObservations
+observeByteString obs bs = observeBytes obs $ unpack bs
+
+observeBytes :: ContentObservations -> [Word8] -> ContentObservations
+observeBytes obs [] = obs
+observeBytes obs (b:rest) =
+  let bint = (fromIntegral b)::Int
+      newObs = observeByte obs (bint,1)
+  in  observeBytes newObs rest
+
+observeByte :: ContentObservations -> (Int, Int) -> ContentObservations
+observeByte (ContentObservations items) item = ContentObservations $ updateCounts items item
+
+emptyObservations :: Observations
+emptyObservations = Observations emptyLengthObservations emptyContentObservations emptyPortObservations emptySubstringObservations
+
+emptyLengthObservations :: LengthObservations
+emptyLengthObservations = LengthObservations (take 1520 $ repeat 0)
+
+emptyContentObservations :: ContentObservations
+emptyContentObservations = ContentObservations (take 256 $ repeat 0)
+
+emptyPortObservations :: PortObservations
+emptyPortObservations = PortObservations []
+
+emptySubstringObservations :: SubstringObservations
+emptySubstringObservations = SubstringObservations $ take 1500 $ repeat empty
+
+loadObservations :: FilePath -> IO (Either String Observations)
+loadObservations path = do
+    s <- B.readFile path
+    return ((decode s)::(Either String Observations))
+
+saveObservations :: FilePath -> Observations -> IO()
+saveObservations path obs = do
+    putStrLn "Saving observations..."
+    let s = encode obs
+    putStrLn $ "Writing " ++ (show $ B.length s) ++ " bytes"
+    B.writeFile path s
+    putStrLn "Done."
+
+ensureObservations :: FilePath -> IO Observations
+ensureObservations path = do
+    exists <- doesFileExist path
+    case exists of
+      True -> do
+        eitherObs <- loadObservations path
+        case eitherObs of
+            Right obs -> return obs
+            Left _ -> return emptyObservations
+      False -> return emptyObservations
+
+makeModel :: Observations -> TrafficModel
+makeModel (Observations lengthObs contentObs portObs stringObs) =
+    let lengthModel = makeLengthModel lengthObs
+        contentModel = makeContentModel contentObs
+        portModel = makePortModel portObs
+    in TrafficModel lengthModel contentModel portModel
+
+makeLengthModel :: LengthObservations -> PacketLengthModel
+makeLengthModel (LengthObservations counts) =
+  let total = sum counts
+      probs = map (divideBy total) counts
+  in PacketLengthModel probs
+
+makeContentModel :: ContentObservations -> C.ContentModel
+makeContentModel (ContentObservations obs) =
+  C.makeContentModel $ zip ([0..255]::[Word8]) obs
+
+makePortModel :: PortObservations -> PortModel
+makePortModel (PortObservations ports) = PortModel ports
+
+divideBy :: Int -> Int -> Double
+divideBy d n = 
+    let fd = (fromIntegral d)::Double
+        fn = (fromIntegral n)::Double
+    in fn / fd
diff --git a/Dust/Model/Packet.hs b/Dust/Model/Packet.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Model/Packet.hs
@@ -0,0 +1,172 @@
+{-# LANGUAGE DeriveGeneric, DefaultSignatures #-}
+
+module Dust.Model.Packet
+(
+    IP(..),
+    Transport(..),
+    Packet(..),
+    Stream(..),
+    Protocol(..),
+    parsePacket
+)
+where
+
+import GHC.Generics
+import Data.Serialize (Serialize)
+import Data.Serialize.Get
+import Data.ByteString (ByteString, unpack)
+import qualified Data.ByteString as B
+import Data.Int
+import Data.Word
+import System.Directory
+import Data.List (nub)
+import Data.Map (Map(..), alter, empty)
+import Data.Bits
+
+import Dust.Model.PacketLength
+import qualified Dust.Model.Content as C
+import Dust.Model.Port
+import Dust.Model.TrafficModel
+
+data Ethernet = Ethernet {
+  ethDest :: ByteString, -- 6 bytes
+  ethSrc  :: ByteString, -- 6 bytes
+  ethtype :: Word16
+} deriving (Generic, Show)
+instance Serialize Ethernet
+
+parseEthernet :: ByteString -> Either String (Ethernet, ByteString)
+parseEthernet bs = runGetState getEthernet bs 0
+
+getEthernet :: Get Ethernet
+getEthernet = do
+  bs0 <- getByteString 6
+  bs7 <- getByteString 6
+  s13 <- getWord16be
+
+  return $ Ethernet bs0 bs7 s13
+
+data IP  = IP {
+  version :: Word8,
+  ihl     :: Word8,
+  tos     :: Word8,
+  tl      :: Word16,
+  id      :: Word16,
+  ipflags :: Word8,
+  fragment:: Word16,
+  ttl     :: Word8,
+  prot    :: Word8,
+  checksum:: Word16,
+  source  :: Word32,
+  dest    :: Word32,
+  ipopts  :: ByteString
+} deriving (Generic, Show)
+instance Serialize IP
+
+parseIP :: ByteString -> Either String (IP, ByteString)
+parseIP bs = runGetState getIP bs 0
+
+getIP :: Get IP
+getIP = do
+  b0 <- getWord8
+  b1 <- getWord8
+  s2 <- getWord16be
+  s4 <- getWord16be
+  s6 <- getWord16be
+  b8 <- getWord8
+  b9 <- getWord8
+  s10<- getWord16be
+  l12<- getWord32be
+  l14<- getWord32be
+  let v20 = B.empty
+
+  let v = shift b0 (-4)
+  let hl = shift (shift b0 4) (-4)
+  let f = (fromIntegral $ shift s6 (-13))::Word8
+  let fo = shift (shift s6 3) (-3)
+  
+  return $ IP v hl b1 s2 s4 f fo b8 b9 s10 l12 l14 v20
+
+data Transport = 
+  TCP {
+    srcport :: Word16,
+    destport:: Word16,
+    seqnum  :: Word32,
+    acknum  :: Word32,
+    offset  :: Word8,
+    reserved:: Word8,
+    tcpflags:: Word8,
+    window  :: Word16,
+    tcpchk  :: Word16,
+    urgent  :: Word16,
+    tcptops :: ByteString
+  } 
+  | UDP {
+    srcport :: Word16,
+    destport:: Word16,
+    len     :: Word16,
+    udpchk  :: Word16
+  }
+  deriving (Generic, Show)
+instance Serialize Transport
+
+parseTCP :: ByteString -> Either String (Transport, ByteString)
+parseTCP bs = runGetState getTCP bs 0
+
+getTCP :: Get Transport
+getTCP = do
+  s0 <- getWord16be
+  s2 <- getWord16be
+  l4 <- getWord32be
+  l8 <- getWord32be
+  b12<- getWord8
+  b13<- getWord8
+  s14<- getWord16be
+  s16<- getWord16be
+  s18<- getWord16be
+
+  let off = shift b12 (-4)
+  let rsv = shift (shift b12 4) (-4)
+  
+  v20 <- getByteString (fromIntegral ((off - 5) * 4)::Int)
+
+  return $ TCP s0 s2 l4 l8 off rsv b13 s14 s16 s18 v20
+
+parseUDP :: ByteString -> Either String (Transport, ByteString)
+parseUDP bs = runGetState getUDP bs 0
+
+getUDP :: Get Transport
+getUDP = do
+  s0 <- getWord16be
+  s2 <- getWord16be
+  s4 <- getWord16be
+  s6 <- getWord16be
+
+  return $ UDP s0 s2 s4 s6
+
+data Packet = Packet Ethernet IP Transport ByteString deriving (Generic, Show)
+instance Serialize Packet 
+
+data Stream = Stream Protocol Word16 [Packet] deriving (Generic, Show)
+instance Serialize Stream
+
+data Protocol = ProtocolTCP | ProtocolUDP deriving (Generic, Show)
+instance Serialize Protocol
+
+parsePacket :: ByteString -> (Either String Packet)
+parsePacket bs =
+  case parseEthernet bs of
+    Left etherError -> Left etherError
+    Right (ether, noether) ->
+      case parseIP noether of
+        Left ipError -> Left ipError
+        Right (ip, noip) -> case (prot ip) of
+          6  ->
+            case parseTCP noip of
+              Left tcpError -> Left tcpError
+              Right (tcp, notcp) -> Right $ Packet ether ip tcp notcp
+          17 ->
+            case parseUDP noip of
+              Left udpError -> Left udpError
+              Right (udp, noudp) -> Right $ Packet ether ip udp noudp
+          otherwise -> Left $ "Unknown protocol " ++ (show $ prot ip)
diff --git a/Dust/Model/PacketLength.hs b/Dust/Model/PacketLength.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Model/PacketLength.hs
@@ -0,0 +1,47 @@
+{-# LANGUAGE DeriveGeneric, DefaultSignatures #-} -- For automatic generation of cereal put and get
+
+module Dust.Model.PacketLength
+(
+    PacketLengthModel(..),
+    loadLengthModel,
+    probsToCDF,
+    nextLength
+)
+where
+
+import System.Random
+import GHC.Generics
+import Data.Serialize
+import Data.Random.Shuffle.Weighted
+import Data.Random.RVar
+import Data.Random
+import Data.Random.Source.IO
+import Data.Random.Source.Std
+import Data.Map
+import qualified Data.ByteString as B
+
+data PacketLengthModel = PacketLengthModel [Double] deriving (Eq, Show, Generic)
+
+instance Serialize PacketLengthModel
+
+loadLengthModel :: FilePath -> IO (Map Double Int)
+loadLengthModel path = do
+   probs <- loadProbs path
+   return $ probsToCDF probs
+
+loadProbs :: FilePath -> IO [Double]
+loadProbs path = do
+    s <- B.readFile path
+    let result = (decode s)::(Either String [Double])
+    case result of
+        Left error -> return ([])
+        Right arr -> return(arr)
+
+probsToCDF :: [Double] -> Map Double Int
+probsToCDF probs = cdfMapFromList $ zip probs [1..(length probs)]
+
+nextLength :: Map Double Int -> IO Int
+nextLength cdf = do
+    let dist = weightedSampleCDF 1 cdf
+    arr <- runRVar dist StdRandom :: IO [Int]
+    return (head arr)
diff --git a/Dust/Model/Port.hs b/Dust/Model/Port.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Model/Port.hs
@@ -0,0 +1,25 @@
+{-# LANGUAGE DeriveGeneric, DefaultSignatures #-} -- For automatic generation of cereal put and get
+
+module Dust.Model.Port
+(
+    PortModel(..),
+    nextPort
+)
+where
+
+import GHC.Generics
+import Data.Serialize
+import Data.Random
+import Data.Random.RVar
+import Data.Random.Extras
+import Data.Random.Source.IO
+import Data.Random.Source.Std
+
+data PortModel = PortModel [Int] deriving (Eq, Show, Generic)
+instance Serialize PortModel
+
+nextPort :: [Int] -> IO Int
+nextPort ports =
+  let dist = choice ports
+  in runRVar dist StdRandom :: IO Int
+
diff --git a/Dust/Model/Stats.hs b/Dust/Model/Stats.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Model/Stats.hs
@@ -0,0 +1,16 @@
+module Dust.Model.Stats
+(
+  histogram
+)
+where
+
+import qualified Data.Map as M
+import Data.List (foldl')
+
+-- count the number of instances each symbol occurs in a list
+histogram :: Ord a => [a] -> [(a,Int)]
+histogram xs =
+    M.toList . foldl' insert M.empty $ xs
+  where
+    insert a k = M.insertWith' (+) k 1 a
+
diff --git a/Dust/Network/DustServer.hs b/Dust/Network/DustServer.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Network/DustServer.hs
@@ -0,0 +1,69 @@
+module Dust.Network.DustServer
+(
+ dustServer
+)
+where
+
+import Data.ByteString.Lazy (ByteString)
+import Data.ByteString.Char8 (pack, unpack)
+import System.IO.Error
+import System.Entropy
+import Data.Binary.Get (runGetState)
+import Data.Binary.Put (runPut)
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as BL
+import Network.Socket
+
+import Dust.Crypto.Keys
+import Dust.Crypto.ECDH
+import Dust.Core.WireProtocolHandler
+import Dust.Network.TcpServer
+import Dust.Crypto.DustCipher
+import Dust.Model.TrafficModel
+import Dust.Network.ProtocolSocket
+import Dust.Core.CryptoProtocol
+
+dustServer :: TrafficGenerator -> (Plaintext -> IO(Plaintext)) -> IO()
+dustServer gen proxyAction = do
+    putStrLn "Loading keys..."
+    (keypair, newKeys) <- ensureKeys
+
+    if newKeys
+        then putStrLn "Generating new keys..."
+        else putStrLn "Loaded keys."
+
+    let host = "0.0.0.0"
+    let port = 6885
+
+    iv <- createIV
+
+    server host port (reencode keypair iv gen proxyAction)
+
+ensureKeys :: IO (Keypair, Bool)
+ensureKeys = do
+    result <- try loadKeypair
+    case result of
+        Left e -> do
+            entropy <- getEntropy 32
+            let keys = createKeypair entropy
+            saveKeypair keys
+            return (keys, True)
+        Right keypair -> return (keypair, False)
+
+reencode :: Keypair -> IV -> TrafficGenerator -> (Plaintext -> IO(Plaintext)) -> Socket -> IO()
+reencode keypair iv gen proxyAction sock = do
+    (session, rest) <- getSession B.empty keypair sock
+    let (Session _ otherPublic _) = session
+    (plaintext, rest') <- getPacket rest session sock
+
+    putStrLn $ "Request:" ++ (show plaintext)
+
+    result <- proxyAction plaintext
+    let Plaintext resultBytes = result
+
+    putStrLn $ "Response:" ++ (show (B.length resultBytes))
+
+    let otherSession = makeSession keypair otherPublic iv
+    encode otherSession result sock
+
+    return ()
diff --git a/Dust/Network/TcpClient.hs b/Dust/Network/TcpClient.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Network/TcpClient.hs
@@ -0,0 +1,20 @@
+module Dust.Network.TcpClient
+(
+ client
+) where
+
+import Network.Socket
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as BL
+
+import Dust.Network.Util
+import Dust.Crypto.DustCipher
+
+client :: String -> PortNumber -> (Socket -> IO(Plaintext)) -> IO(Plaintext)
+client host port handleRequest = withSocketsDo $ do
+        sock <- socket AF_INET Stream defaultProtocol
+        addr <- inet_addr host
+        connect sock (SockAddrInet port addr)
+        setSocketOption sock NoDelay 1
+
+        handleRequest sock
diff --git a/Dust/Network/TcpServer.hs b/Dust/Network/TcpServer.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Network/TcpServer.hs
@@ -0,0 +1,37 @@
+module Dust.Network.TcpServer
+(
+ server
+) where
+
+import Network (listenOn, PortID(PortNumber))
+import Network.Socket
+import qualified Network.Socket.ByteString as NSB
+import qualified Network.Socket.ByteString.Lazy as NSBL
+import Network.Socket.ByteString (sendAll)
+import Data.ByteString.Lazy (ByteString, fromChunks)
+import Control.Monad (forever)
+
+import Dust.Network.Util
+
+type Host = SockAddr
+
+server :: String -> PortNumber -> (Socket -> IO()) -> IO()
+server host port handleRequest = withSocketsDo $ do
+        sock <- initSocket host port
+        forever $ acceptAndProcess sock handleRequest
+        sClose sock
+
+initSocket :: String -> PortNumber -> IO(Socket)
+initSocket host port = listenOn $ PortNumber port
+
+acceptAndProcess :: Socket -> (Socket-> IO()) -> IO()
+acceptAndProcess sock handleRequest = do
+    (s, _) <- accept sock
+    setSocketOption s NoDelay 1
+    process handleRequest s
+
+process :: (Socket -> IO()) -> Socket -> IO()
+process handleRequest sock = do
+        handleRequest sock
+        sClose sock
+        return ()
diff --git a/Dust/Network/UdpServer.hs b/Dust/Network/UdpServer.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Network/UdpServer.hs
@@ -0,0 +1,15 @@
+module Dust.Network.UdpServer
+(
+ server
+) where
+
+import Network (PortID(PortNumber))
+import Network.Socket
+import Control.Monad (forever)
+
+server :: String -> PortNumber -> (Socket -> IO()) -> IO()
+server host port@(PortNum iport) handleRequest = withSocketsDo $ do
+        sock <- socket AF_INET Datagram defaultProtocol
+        putStrLn $ "Binding to " ++ (show iport)
+        bindSocket sock (SockAddrInet port iNADDR_ANY)
+        handleRequest sock
diff --git a/Dust/Network/Util.hs b/Dust/Network/Util.hs
new file mode 100644
--- /dev/null
+++ b/Dust/Network/Util.hs
@@ -0,0 +1,25 @@
+module Dust.Network.Util
+(
+ recvAll
+)
+where
+
+import Data.ByteString
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as BL
+import Network.Socket hiding (recv, send)
+import Network.Socket.ByteString (recv, send)
+
+recvAll :: Socket -> IO(BL.ByteString)
+recvAll sock = do
+    list <- recvList sock
+    return (BL.fromChunks list)
+
+recvList :: Socket -> IO([B.ByteString])
+recvList sock = do
+    input <- recv sock 4096
+    if B.null input
+        then return ([input])
+        else do
+            next <- recvList sock
+            return (input:next)
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,3 @@
+import Distribution.Simple
+main = defaultMain
+
diff --git a/lib/curve25519-donna.c b/lib/curve25519-donna.c
new file mode 100644
--- /dev/null
+++ b/lib/curve25519-donna.c
@@ -0,0 +1,730 @@
+/* Copyright 2008, Google Inc.
+ * 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 Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * 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.
+ *
+ * curve25519-donna: Curve25519 elliptic curve, public key function
+ *
+ * http://code.google.com/p/curve25519-donna/
+ *
+ * Adam Langley <agl@imperialviolet.org>
+ *
+ * Derived from public domain C code by Daniel J. Bernstein <djb@cr.yp.to>
+ *
+ * More information about curve25519 can be found here
+ *   http://cr.yp.to/ecdh.html
+ *
+ * djb's sample implementation of curve25519 is written in a special assembly
+ * language called qhasm and uses the floating point registers.
+ *
+ * This is, almost, a clean room reimplementation from the curve25519 paper. It
+ * uses many of the tricks described therein. Only the crecip function is taken
+ * from the sample implementation.
+ */
+
+#include <string.h>
+#include <stdint.h>
+
+typedef uint8_t u8;
+typedef int32_t s32;
+typedef int64_t limb;
+
+/* Field element representation:
+ *
+ * Field elements are written as an array of signed, 64-bit limbs, least
+ * significant first. The value of the field element is:
+ *   x[0] + 2^26·x[1] + x^51·x[2] + 2^102·x[3] + ...
+ *
+ * i.e. the limbs are 26, 25, 26, 25, ... bits wide.
+ */
+
+/* Sum two numbers: output += in */
+static void fsum(limb *output, const limb *in) {
+  unsigned i;
+  for (i = 0; i < 10; i += 2) {
+    output[0+i] = (output[0+i] + in[0+i]);
+    output[1+i] = (output[1+i] + in[1+i]);
+  }
+}
+
+/* Find the difference of two numbers: output = in - output
+ * (note the order of the arguments!)
+ */
+static void fdifference(limb *output, const limb *in) {
+  unsigned i;
+  for (i = 0; i < 10; ++i) {
+    output[i] = (in[i] - output[i]);
+  }
+}
+
+/* Multiply a number by a scalar: output = in * scalar */
+static void fscalar_product(limb *output, const limb *in, const limb scalar) {
+  unsigned i;
+  for (i = 0; i < 10; ++i) {
+    output[i] = in[i] * scalar;
+  }
+}
+
+/* Multiply two numbers: output = in2 * in
+ *
+ * output must be distinct to both inputs. The inputs are reduced coefficient
+ * form, the output is not.
+ */
+static void fproduct(limb *output, const limb *in2, const limb *in) {
+  output[0] =       ((limb) ((s32) in2[0])) * ((s32) in[0]);
+  output[1] =       ((limb) ((s32) in2[0])) * ((s32) in[1]) +
+                    ((limb) ((s32) in2[1])) * ((s32) in[0]);
+  output[2] =  2 *  ((limb) ((s32) in2[1])) * ((s32) in[1]) +
+                    ((limb) ((s32) in2[0])) * ((s32) in[2]) +
+                    ((limb) ((s32) in2[2])) * ((s32) in[0]);
+  output[3] =       ((limb) ((s32) in2[1])) * ((s32) in[2]) +
+                    ((limb) ((s32) in2[2])) * ((s32) in[1]) +
+                    ((limb) ((s32) in2[0])) * ((s32) in[3]) +
+                    ((limb) ((s32) in2[3])) * ((s32) in[0]);
+  output[4] =       ((limb) ((s32) in2[2])) * ((s32) in[2]) +
+               2 * (((limb) ((s32) in2[1])) * ((s32) in[3]) +
+                    ((limb) ((s32) in2[3])) * ((s32) in[1])) +
+                    ((limb) ((s32) in2[0])) * ((s32) in[4]) +
+                    ((limb) ((s32) in2[4])) * ((s32) in[0]);
+  output[5] =       ((limb) ((s32) in2[2])) * ((s32) in[3]) +
+                    ((limb) ((s32) in2[3])) * ((s32) in[2]) +
+                    ((limb) ((s32) in2[1])) * ((s32) in[4]) +
+                    ((limb) ((s32) in2[4])) * ((s32) in[1]) +
+                    ((limb) ((s32) in2[0])) * ((s32) in[5]) +
+                    ((limb) ((s32) in2[5])) * ((s32) in[0]);
+  output[6] =  2 * (((limb) ((s32) in2[3])) * ((s32) in[3]) +
+                    ((limb) ((s32) in2[1])) * ((s32) in[5]) +
+                    ((limb) ((s32) in2[5])) * ((s32) in[1])) +
+                    ((limb) ((s32) in2[2])) * ((s32) in[4]) +
+                    ((limb) ((s32) in2[4])) * ((s32) in[2]) +
+                    ((limb) ((s32) in2[0])) * ((s32) in[6]) +
+                    ((limb) ((s32) in2[6])) * ((s32) in[0]);
+  output[7] =       ((limb) ((s32) in2[3])) * ((s32) in[4]) +
+                    ((limb) ((s32) in2[4])) * ((s32) in[3]) +
+                    ((limb) ((s32) in2[2])) * ((s32) in[5]) +
+                    ((limb) ((s32) in2[5])) * ((s32) in[2]) +
+                    ((limb) ((s32) in2[1])) * ((s32) in[6]) +
+                    ((limb) ((s32) in2[6])) * ((s32) in[1]) +
+                    ((limb) ((s32) in2[0])) * ((s32) in[7]) +
+                    ((limb) ((s32) in2[7])) * ((s32) in[0]);
+  output[8] =       ((limb) ((s32) in2[4])) * ((s32) in[4]) +
+               2 * (((limb) ((s32) in2[3])) * ((s32) in[5]) +
+                    ((limb) ((s32) in2[5])) * ((s32) in[3]) +
+                    ((limb) ((s32) in2[1])) * ((s32) in[7]) +
+                    ((limb) ((s32) in2[7])) * ((s32) in[1])) +
+                    ((limb) ((s32) in2[2])) * ((s32) in[6]) +
+                    ((limb) ((s32) in2[6])) * ((s32) in[2]) +
+                    ((limb) ((s32) in2[0])) * ((s32) in[8]) +
+                    ((limb) ((s32) in2[8])) * ((s32) in[0]);
+  output[9] =       ((limb) ((s32) in2[4])) * ((s32) in[5]) +
+                    ((limb) ((s32) in2[5])) * ((s32) in[4]) +
+                    ((limb) ((s32) in2[3])) * ((s32) in[6]) +
+                    ((limb) ((s32) in2[6])) * ((s32) in[3]) +
+                    ((limb) ((s32) in2[2])) * ((s32) in[7]) +
+                    ((limb) ((s32) in2[7])) * ((s32) in[2]) +
+                    ((limb) ((s32) in2[1])) * ((s32) in[8]) +
+                    ((limb) ((s32) in2[8])) * ((s32) in[1]) +
+                    ((limb) ((s32) in2[0])) * ((s32) in[9]) +
+                    ((limb) ((s32) in2[9])) * ((s32) in[0]);
+  output[10] = 2 * (((limb) ((s32) in2[5])) * ((s32) in[5]) +
+                    ((limb) ((s32) in2[3])) * ((s32) in[7]) +
+                    ((limb) ((s32) in2[7])) * ((s32) in[3]) +
+                    ((limb) ((s32) in2[1])) * ((s32) in[9]) +
+                    ((limb) ((s32) in2[9])) * ((s32) in[1])) +
+                    ((limb) ((s32) in2[4])) * ((s32) in[6]) +
+                    ((limb) ((s32) in2[6])) * ((s32) in[4]) +
+                    ((limb) ((s32) in2[2])) * ((s32) in[8]) +
+                    ((limb) ((s32) in2[8])) * ((s32) in[2]);
+  output[11] =      ((limb) ((s32) in2[5])) * ((s32) in[6]) +
+                    ((limb) ((s32) in2[6])) * ((s32) in[5]) +
+                    ((limb) ((s32) in2[4])) * ((s32) in[7]) +
+                    ((limb) ((s32) in2[7])) * ((s32) in[4]) +
+                    ((limb) ((s32) in2[3])) * ((s32) in[8]) +
+                    ((limb) ((s32) in2[8])) * ((s32) in[3]) +
+                    ((limb) ((s32) in2[2])) * ((s32) in[9]) +
+                    ((limb) ((s32) in2[9])) * ((s32) in[2]);
+  output[12] =      ((limb) ((s32) in2[6])) * ((s32) in[6]) +
+               2 * (((limb) ((s32) in2[5])) * ((s32) in[7]) +
+                    ((limb) ((s32) in2[7])) * ((s32) in[5]) +
+                    ((limb) ((s32) in2[3])) * ((s32) in[9]) +
+                    ((limb) ((s32) in2[9])) * ((s32) in[3])) +
+                    ((limb) ((s32) in2[4])) * ((s32) in[8]) +
+                    ((limb) ((s32) in2[8])) * ((s32) in[4]);
+  output[13] =      ((limb) ((s32) in2[6])) * ((s32) in[7]) +
+                    ((limb) ((s32) in2[7])) * ((s32) in[6]) +
+                    ((limb) ((s32) in2[5])) * ((s32) in[8]) +
+                    ((limb) ((s32) in2[8])) * ((s32) in[5]) +
+                    ((limb) ((s32) in2[4])) * ((s32) in[9]) +
+                    ((limb) ((s32) in2[9])) * ((s32) in[4]);
+  output[14] = 2 * (((limb) ((s32) in2[7])) * ((s32) in[7]) +
+                    ((limb) ((s32) in2[5])) * ((s32) in[9]) +
+                    ((limb) ((s32) in2[9])) * ((s32) in[5])) +
+                    ((limb) ((s32) in2[6])) * ((s32) in[8]) +
+                    ((limb) ((s32) in2[8])) * ((s32) in[6]);
+  output[15] =      ((limb) ((s32) in2[7])) * ((s32) in[8]) +
+                    ((limb) ((s32) in2[8])) * ((s32) in[7]) +
+                    ((limb) ((s32) in2[6])) * ((s32) in[9]) +
+                    ((limb) ((s32) in2[9])) * ((s32) in[6]);
+  output[16] =      ((limb) ((s32) in2[8])) * ((s32) in[8]) +
+               2 * (((limb) ((s32) in2[7])) * ((s32) in[9]) +
+                    ((limb) ((s32) in2[9])) * ((s32) in[7]));
+  output[17] =      ((limb) ((s32) in2[8])) * ((s32) in[9]) +
+                    ((limb) ((s32) in2[9])) * ((s32) in[8]);
+  output[18] = 2 *  ((limb) ((s32) in2[9])) * ((s32) in[9]);
+}
+
+/* Reduce a long form to a short form by taking the input mod 2^255 - 19. */
+static void freduce_degree(limb *output) {
+  /* Each of these shifts and adds ends up multiplying the value by 19. */
+  output[8] += output[18] << 4;
+  output[8] += output[18] << 1;
+  output[8] += output[18];
+  output[7] += output[17] << 4;
+  output[7] += output[17] << 1;
+  output[7] += output[17];
+  output[6] += output[16] << 4;
+  output[6] += output[16] << 1;
+  output[6] += output[16];
+  output[5] += output[15] << 4;
+  output[5] += output[15] << 1;
+  output[5] += output[15];
+  output[4] += output[14] << 4;
+  output[4] += output[14] << 1;
+  output[4] += output[14];
+  output[3] += output[13] << 4;
+  output[3] += output[13] << 1;
+  output[3] += output[13];
+  output[2] += output[12] << 4;
+  output[2] += output[12] << 1;
+  output[2] += output[12];
+  output[1] += output[11] << 4;
+  output[1] += output[11] << 1;
+  output[1] += output[11];
+  output[0] += output[10] << 4;
+  output[0] += output[10] << 1;
+  output[0] += output[10];
+}
+
+#if (-1 & 3) != 3
+#error "This code only works on a two's complement system"
+#endif
+
+/* return v / 2^26, using only shifts and adds. */
+static inline limb
+div_by_2_26(const limb v)
+{
+  /* High word of v; no shift needed*/
+  const uint32_t highword = (uint32_t) (((uint64_t) v) >> 32);
+  /* Set to all 1s if v was negative; else set to 0s. */
+  const int32_t sign = ((int32_t) highword) >> 31;
+  /* Set to 0x3ffffff if v was negative; else set to 0. */
+  const int32_t roundoff = ((uint32_t) sign) >> 6;
+  /* Should return v / (1<<26) */
+  return (v + roundoff) >> 26;
+}
+
+/* return v / (2^25), using only shifts and adds. */
+static inline limb
+div_by_2_25(const limb v)
+{
+  /* High word of v; no shift needed*/
+  const uint32_t highword = (uint32_t) (((uint64_t) v) >> 32);
+  /* Set to all 1s if v was negative; else set to 0s. */
+  const int32_t sign = ((int32_t) highword) >> 31;
+  /* Set to 0x1ffffff if v was negative; else set to 0. */
+  const int32_t roundoff = ((uint32_t) sign) >> 7;
+  /* Should return v / (1<<25) */
+  return (v + roundoff) >> 25;
+}
+
+static inline s32
+div_s32_by_2_25(const s32 v)
+{
+   const s32 roundoff = ((uint32_t)(v >> 31)) >> 7;
+   return (v + roundoff) >> 25;
+}
+
+/* Reduce all coefficients of the short form input so that |x| < 2^26.
+ *
+ * On entry: |output[i]| < 2^62
+ */
+static void freduce_coefficients(limb *output) {
+  unsigned i;
+
+  output[10] = 0;
+
+  for (i = 0; i < 10; i += 2) {
+    limb over = div_by_2_26(output[i]);
+    output[i] -= over << 26;
+    output[i+1] += over;
+
+    over = div_by_2_25(output[i+1]);
+    output[i+1] -= over << 25;
+    output[i+2] += over;
+  }
+  /* Now |output[10]| < 2 ^ 38 and all other coefficients are reduced. */
+  output[0] += output[10] << 4;
+  output[0] += output[10] << 1;
+  output[0] += output[10];
+
+  output[10] = 0;
+
+  /* Now output[1..9] are reduced, and |output[0]| < 2^26 + 19 * 2^38
+   * So |over| will be no more than 77825  */
+  {
+    limb over = div_by_2_26(output[0]);
+    output[0] -= over << 26;
+    output[1] += over;
+  }
+
+  /* Now output[0,2..9] are reduced, and |output[1]| < 2^25 + 77825
+   * So |over| will be no more than 1. */
+  {
+    /* output[1] fits in 32 bits, so we can use div_s32_by_2_25 here. */
+    s32 over32 = div_s32_by_2_25((s32) output[1]);
+    output[1] -= over32 << 25;
+    output[2] += over32;
+  }
+
+  /* Finally, output[0,1,3..9] are reduced, and output[2] is "nearly reduced":
+   * we have |output[2]| <= 2^26.  This is good enough for all of our math,
+   * but it will require an extra freduce_coefficients before fcontract. */
+}
+
+/* A helpful wrapper around fproduct: output = in * in2.
+ *
+ * output must be distinct to both inputs. The output is reduced degree and
+ * reduced coefficient.
+ */
+static void
+fmul(limb *output, const limb *in, const limb *in2) {
+  limb t[19];
+  fproduct(t, in, in2);
+  freduce_degree(t);
+  freduce_coefficients(t);
+  memcpy(output, t, sizeof(limb) * 10);
+}
+
+static void fsquare_inner(limb *output, const limb *in) {
+  output[0] =       ((limb) ((s32) in[0])) * ((s32) in[0]);
+  output[1] =  2 *  ((limb) ((s32) in[0])) * ((s32) in[1]);
+  output[2] =  2 * (((limb) ((s32) in[1])) * ((s32) in[1]) +
+                    ((limb) ((s32) in[0])) * ((s32) in[2]));
+  output[3] =  2 * (((limb) ((s32) in[1])) * ((s32) in[2]) +
+                    ((limb) ((s32) in[0])) * ((s32) in[3]));
+  output[4] =       ((limb) ((s32) in[2])) * ((s32) in[2]) +
+               4 *  ((limb) ((s32) in[1])) * ((s32) in[3]) +
+               2 *  ((limb) ((s32) in[0])) * ((s32) in[4]);
+  output[5] =  2 * (((limb) ((s32) in[2])) * ((s32) in[3]) +
+                    ((limb) ((s32) in[1])) * ((s32) in[4]) +
+                    ((limb) ((s32) in[0])) * ((s32) in[5]));
+  output[6] =  2 * (((limb) ((s32) in[3])) * ((s32) in[3]) +
+                    ((limb) ((s32) in[2])) * ((s32) in[4]) +
+                    ((limb) ((s32) in[0])) * ((s32) in[6]) +
+               2 *  ((limb) ((s32) in[1])) * ((s32) in[5]));
+  output[7] =  2 * (((limb) ((s32) in[3])) * ((s32) in[4]) +
+                    ((limb) ((s32) in[2])) * ((s32) in[5]) +
+                    ((limb) ((s32) in[1])) * ((s32) in[6]) +
+                    ((limb) ((s32) in[0])) * ((s32) in[7]));
+  output[8] =       ((limb) ((s32) in[4])) * ((s32) in[4]) +
+               2 * (((limb) ((s32) in[2])) * ((s32) in[6]) +
+                    ((limb) ((s32) in[0])) * ((s32) in[8]) +
+               2 * (((limb) ((s32) in[1])) * ((s32) in[7]) +
+                    ((limb) ((s32) in[3])) * ((s32) in[5])));
+  output[9] =  2 * (((limb) ((s32) in[4])) * ((s32) in[5]) +
+                    ((limb) ((s32) in[3])) * ((s32) in[6]) +
+                    ((limb) ((s32) in[2])) * ((s32) in[7]) +
+                    ((limb) ((s32) in[1])) * ((s32) in[8]) +
+                    ((limb) ((s32) in[0])) * ((s32) in[9]));
+  output[10] = 2 * (((limb) ((s32) in[5])) * ((s32) in[5]) +
+                    ((limb) ((s32) in[4])) * ((s32) in[6]) +
+                    ((limb) ((s32) in[2])) * ((s32) in[8]) +
+               2 * (((limb) ((s32) in[3])) * ((s32) in[7]) +
+                    ((limb) ((s32) in[1])) * ((s32) in[9])));
+  output[11] = 2 * (((limb) ((s32) in[5])) * ((s32) in[6]) +
+                    ((limb) ((s32) in[4])) * ((s32) in[7]) +
+                    ((limb) ((s32) in[3])) * ((s32) in[8]) +
+                    ((limb) ((s32) in[2])) * ((s32) in[9]));
+  output[12] =      ((limb) ((s32) in[6])) * ((s32) in[6]) +
+               2 * (((limb) ((s32) in[4])) * ((s32) in[8]) +
+               2 * (((limb) ((s32) in[5])) * ((s32) in[7]) +
+                    ((limb) ((s32) in[3])) * ((s32) in[9])));
+  output[13] = 2 * (((limb) ((s32) in[6])) * ((s32) in[7]) +
+                    ((limb) ((s32) in[5])) * ((s32) in[8]) +
+                    ((limb) ((s32) in[4])) * ((s32) in[9]));
+  output[14] = 2 * (((limb) ((s32) in[7])) * ((s32) in[7]) +
+                    ((limb) ((s32) in[6])) * ((s32) in[8]) +
+               2 *  ((limb) ((s32) in[5])) * ((s32) in[9]));
+  output[15] = 2 * (((limb) ((s32) in[7])) * ((s32) in[8]) +
+                    ((limb) ((s32) in[6])) * ((s32) in[9]));
+  output[16] =      ((limb) ((s32) in[8])) * ((s32) in[8]) +
+               4 *  ((limb) ((s32) in[7])) * ((s32) in[9]);
+  output[17] = 2 *  ((limb) ((s32) in[8])) * ((s32) in[9]);
+  output[18] = 2 *  ((limb) ((s32) in[9])) * ((s32) in[9]);
+}
+
+static void
+fsquare(limb *output, const limb *in) {
+  limb t[19];
+  fsquare_inner(t, in);
+  freduce_degree(t);
+  freduce_coefficients(t);
+  memcpy(output, t, sizeof(limb) * 10);
+}
+
+/* Take a little-endian, 32-byte number and expand it into polynomial form */
+static void
+fexpand(limb *output, const u8 *input) {
+#define F(n,start,shift,mask) \
+  output[n] = ((((limb) input[start + 0]) | \
+                ((limb) input[start + 1]) << 8 | \
+                ((limb) input[start + 2]) << 16 | \
+                ((limb) input[start + 3]) << 24) >> shift) & mask;
+  F(0, 0, 0, 0x3ffffff);
+  F(1, 3, 2, 0x1ffffff);
+  F(2, 6, 3, 0x3ffffff);
+  F(3, 9, 5, 0x1ffffff);
+  F(4, 12, 6, 0x3ffffff);
+  F(5, 16, 0, 0x1ffffff);
+  F(6, 19, 1, 0x3ffffff);
+  F(7, 22, 3, 0x1ffffff);
+  F(8, 25, 4, 0x3ffffff);
+  F(9, 28, 6, 0x1ffffff);
+#undef F
+}
+
+#if (-32 >> 1) != -16
+#error "This code only works when >> does sign-extension on negative numbers"
+#endif
+
+/* Take a fully reduced polynomial form number and contract it into a
+ * little-endian, 32-byte array
+ */
+static void
+fcontract(u8 *output, limb *input) {
+  int i;
+  int j;
+
+  for (j = 0; j < 2; ++j) {
+    for (i = 0; i < 9; ++i) {
+      if ((i & 1) == 1) {
+        /* This calculation is a time-invariant way to make input[i] positive
+           by borrowing from the next-larger limb.
+        */
+        const s32 mask = (s32)(input[i]) >> 31;
+        const s32 carry = -(((s32)(input[i]) & mask) >> 25);
+        input[i] = (s32)(input[i]) + (carry << 25);
+        input[i+1] = (s32)(input[i+1]) - carry;
+      } else {
+        const s32 mask = (s32)(input[i]) >> 31;
+        const s32 carry = -(((s32)(input[i]) & mask) >> 26);
+        input[i] = (s32)(input[i]) + (carry << 26);
+        input[i+1] = (s32)(input[i+1]) - carry;
+      }
+    }
+    {
+      const s32 mask = (s32)(input[9]) >> 31;
+      const s32 carry = -(((s32)(input[9]) & mask) >> 25);
+      input[9] = (s32)(input[9]) + (carry << 25);
+      input[0] = (s32)(input[0]) - (carry * 19);
+    }
+  }
+
+  /* The first borrow-propagation pass above ended with every limb
+     except (possibly) input[0] non-negative.
+
+     Since each input limb except input[0] is decreased by at most 1
+     by a borrow-propagation pass, the second borrow-propagation pass
+     could only have wrapped around to decrease input[0] again if the
+     first pass left input[0] negative *and* input[1] through input[9]
+     were all zero.  In that case, input[1] is now 2^25 - 1, and this
+     last borrow-propagation step will leave input[1] non-negative.
+  */
+  {
+    const s32 mask = (s32)(input[0]) >> 31;
+    const s32 carry = -(((s32)(input[0]) & mask) >> 26);
+    input[0] = (s32)(input[0]) + (carry << 26);
+    input[1] = (s32)(input[1]) - carry;
+  }
+
+  /* Both passes through the above loop, plus the last 0-to-1 step, are
+     necessary: if input[9] is -1 and input[0] through input[8] are 0,
+     negative values will remain in the array until the end.
+   */
+
+  input[1] <<= 2;
+  input[2] <<= 3;
+  input[3] <<= 5;
+  input[4] <<= 6;
+  input[6] <<= 1;
+  input[7] <<= 3;
+  input[8] <<= 4;
+  input[9] <<= 6;
+#define F(i, s) \
+  output[s+0] |=  input[i] & 0xff; \
+  output[s+1]  = (input[i] >> 8) & 0xff; \
+  output[s+2]  = (input[i] >> 16) & 0xff; \
+  output[s+3]  = (input[i] >> 24) & 0xff;
+  output[0] = 0;
+  output[16] = 0;
+  F(0,0);
+  F(1,3);
+  F(2,6);
+  F(3,9);
+  F(4,12);
+  F(5,16);
+  F(6,19);
+  F(7,22);
+  F(8,25);
+  F(9,28);
+#undef F
+}
+
+/* Input: Q, Q', Q-Q'
+ * Output: 2Q, Q+Q'
+ *
+ *   x2 z3: long form
+ *   x3 z3: long form
+ *   x z: short form, destroyed
+ *   xprime zprime: short form, destroyed
+ *   qmqp: short form, preserved
+ */
+static void fmonty(limb *x2, limb *z2,  /* output 2Q */
+                   limb *x3, limb *z3,  /* output Q + Q' */
+                   limb *x, limb *z,    /* input Q */
+                   limb *xprime, limb *zprime,  /* input Q' */
+                   const limb *qmqp /* input Q - Q' */) {
+  limb origx[10], origxprime[10], zzz[19], xx[19], zz[19], xxprime[19],
+        zzprime[19], zzzprime[19], xxxprime[19];
+
+  memcpy(origx, x, 10 * sizeof(limb));
+  fsum(x, z);
+  fdifference(z, origx);  // does x - z
+
+  memcpy(origxprime, xprime, sizeof(limb) * 10);
+  fsum(xprime, zprime);
+  fdifference(zprime, origxprime);
+  fproduct(xxprime, xprime, z);
+  fproduct(zzprime, x, zprime);
+  freduce_degree(xxprime);
+  freduce_coefficients(xxprime);
+  freduce_degree(zzprime);
+  freduce_coefficients(zzprime);
+  memcpy(origxprime, xxprime, sizeof(limb) * 10);
+  fsum(xxprime, zzprime);
+  fdifference(zzprime, origxprime);
+  fsquare(xxxprime, xxprime);
+  fsquare(zzzprime, zzprime);
+  fproduct(zzprime, zzzprime, qmqp);
+  freduce_degree(zzprime);
+  freduce_coefficients(zzprime);
+  memcpy(x3, xxxprime, sizeof(limb) * 10);
+  memcpy(z3, zzprime, sizeof(limb) * 10);
+
+  fsquare(xx, x);
+  fsquare(zz, z);
+  fproduct(x2, xx, zz);
+  freduce_degree(x2);
+  freduce_coefficients(x2);
+  fdifference(zz, xx);  // does zz = xx - zz
+  memset(zzz + 10, 0, sizeof(limb) * 9);
+  fscalar_product(zzz, zz, 121665);
+  /* No need to call freduce_degree here:
+     fscalar_product doesn't increase the degree of its input. */
+  freduce_coefficients(zzz);
+  fsum(zzz, xx);
+  fproduct(z2, zz, zzz);
+  freduce_degree(z2);
+  freduce_coefficients(z2);
+}
+
+/* Conditionally swap two reduced-form limb arrays if 'iswap' is 1, but leave
+ * them unchanged if 'iswap' is 0.  Runs in data-invariant time to avoid
+ * side-channel attacks.
+ *
+ * NOTE that this function requires that 'iswap' be 1 or 0; other values give
+ * wrong results.  Also, the two limb arrays must be in reduced-coefficient,
+ * reduced-degree form: the values in a[10..19] or b[10..19] aren't swapped,
+ * and all all values in a[0..9],b[0..9] must have magnitude less than
+ * INT32_MAX.
+ */
+static void
+swap_conditional(limb a[19], limb b[19], limb iswap) {
+  unsigned i;
+  const s32 swap = (s32) -iswap;
+
+  for (i = 0; i < 10; ++i) {
+    const s32 x = swap & ( ((s32)a[i]) ^ ((s32)b[i]) );
+    a[i] = ((s32)a[i]) ^ x;
+    b[i] = ((s32)b[i]) ^ x;
+  }
+}
+
+/* Calculates nQ where Q is the x-coordinate of a point on the curve
+ *
+ *   resultx/resultz: the x coordinate of the resulting curve point (short form)
+ *   n: a little endian, 32-byte number
+ *   q: a point of the curve (short form)
+ */
+static void
+cmult(limb *resultx, limb *resultz, const u8 *n, const limb *q) {
+  limb a[19] = {0}, b[19] = {1}, c[19] = {1}, d[19] = {0};
+  limb *nqpqx = a, *nqpqz = b, *nqx = c, *nqz = d, *t;
+  limb e[19] = {0}, f[19] = {1}, g[19] = {0}, h[19] = {1};
+  limb *nqpqx2 = e, *nqpqz2 = f, *nqx2 = g, *nqz2 = h;
+
+  unsigned i, j;
+
+  memcpy(nqpqx, q, sizeof(limb) * 10);
+
+  for (i = 0; i < 32; ++i) {
+    u8 byte = n[31 - i];
+    for (j = 0; j < 8; ++j) {
+      const limb bit = byte >> 7;
+
+      swap_conditional(nqx, nqpqx, bit);
+      swap_conditional(nqz, nqpqz, bit);
+      fmonty(nqx2, nqz2,
+             nqpqx2, nqpqz2,
+             nqx, nqz,
+             nqpqx, nqpqz,
+             q);
+      swap_conditional(nqx2, nqpqx2, bit);
+      swap_conditional(nqz2, nqpqz2, bit);
+
+      t = nqx;
+      nqx = nqx2;
+      nqx2 = t;
+      t = nqz;
+      nqz = nqz2;
+      nqz2 = t;
+      t = nqpqx;
+      nqpqx = nqpqx2;
+      nqpqx2 = t;
+      t = nqpqz;
+      nqpqz = nqpqz2;
+      nqpqz2 = t;
+
+      byte <<= 1;
+    }
+  }
+
+  memcpy(resultx, nqx, sizeof(limb) * 10);
+  memcpy(resultz, nqz, sizeof(limb) * 10);
+}
+
+// -----------------------------------------------------------------------------
+// Shamelessly copied from djb's code
+// -----------------------------------------------------------------------------
+static void
+crecip(limb *out, const limb *z) {
+  limb z2[10];
+  limb z9[10];
+  limb z11[10];
+  limb z2_5_0[10];
+  limb z2_10_0[10];
+  limb z2_20_0[10];
+  limb z2_50_0[10];
+  limb z2_100_0[10];
+  limb t0[10];
+  limb t1[10];
+  int i;
+
+  /* 2 */ fsquare(z2,z);
+  /* 4 */ fsquare(t1,z2);
+  /* 8 */ fsquare(t0,t1);
+  /* 9 */ fmul(z9,t0,z);
+  /* 11 */ fmul(z11,z9,z2);
+  /* 22 */ fsquare(t0,z11);
+  /* 2^5 - 2^0 = 31 */ fmul(z2_5_0,t0,z9);
+
+  /* 2^6 - 2^1 */ fsquare(t0,z2_5_0);
+  /* 2^7 - 2^2 */ fsquare(t1,t0);
+  /* 2^8 - 2^3 */ fsquare(t0,t1);
+  /* 2^9 - 2^4 */ fsquare(t1,t0);
+  /* 2^10 - 2^5 */ fsquare(t0,t1);
+  /* 2^10 - 2^0 */ fmul(z2_10_0,t0,z2_5_0);
+
+  /* 2^11 - 2^1 */ fsquare(t0,z2_10_0);
+  /* 2^12 - 2^2 */ fsquare(t1,t0);
+  /* 2^20 - 2^10 */ for (i = 2;i < 10;i += 2) { fsquare(t0,t1); fsquare(t1,t0); }
+  /* 2^20 - 2^0 */ fmul(z2_20_0,t1,z2_10_0);
+
+  /* 2^21 - 2^1 */ fsquare(t0,z2_20_0);
+  /* 2^22 - 2^2 */ fsquare(t1,t0);
+  /* 2^40 - 2^20 */ for (i = 2;i < 20;i += 2) { fsquare(t0,t1); fsquare(t1,t0); }
+  /* 2^40 - 2^0 */ fmul(t0,t1,z2_20_0);
+
+  /* 2^41 - 2^1 */ fsquare(t1,t0);
+  /* 2^42 - 2^2 */ fsquare(t0,t1);
+  /* 2^50 - 2^10 */ for (i = 2;i < 10;i += 2) { fsquare(t1,t0); fsquare(t0,t1); }
+  /* 2^50 - 2^0 */ fmul(z2_50_0,t0,z2_10_0);
+
+  /* 2^51 - 2^1 */ fsquare(t0,z2_50_0);
+  /* 2^52 - 2^2 */ fsquare(t1,t0);
+  /* 2^100 - 2^50 */ for (i = 2;i < 50;i += 2) { fsquare(t0,t1); fsquare(t1,t0); }
+  /* 2^100 - 2^0 */ fmul(z2_100_0,t1,z2_50_0);
+
+  /* 2^101 - 2^1 */ fsquare(t1,z2_100_0);
+  /* 2^102 - 2^2 */ fsquare(t0,t1);
+  /* 2^200 - 2^100 */ for (i = 2;i < 100;i += 2) { fsquare(t1,t0); fsquare(t0,t1); }
+  /* 2^200 - 2^0 */ fmul(t1,t0,z2_100_0);
+
+  /* 2^201 - 2^1 */ fsquare(t0,t1);
+  /* 2^202 - 2^2 */ fsquare(t1,t0);
+  /* 2^250 - 2^50 */ for (i = 2;i < 50;i += 2) { fsquare(t0,t1); fsquare(t1,t0); }
+  /* 2^250 - 2^0 */ fmul(t0,t1,z2_50_0);
+
+  /* 2^251 - 2^1 */ fsquare(t1,t0);
+  /* 2^252 - 2^2 */ fsquare(t0,t1);
+  /* 2^253 - 2^3 */ fsquare(t1,t0);
+  /* 2^254 - 2^4 */ fsquare(t0,t1);
+  /* 2^255 - 2^5 */ fsquare(t1,t0);
+  /* 2^255 - 21 */ fmul(out,t1,z11);
+}
+
+int curve25519_donna(u8 *, const u8 *, const u8 *);
+
+int
+curve25519_donna(u8 *mypublic, const u8 *secret, const u8 *basepoint) {
+  limb bp[10], x[10], z[11], zmone[10];
+  uint8_t e[32];
+  int i;
+
+  for (i = 0; i < 32; ++i) e[i] = secret[i];
+  e[0] &= 248;
+  e[31] &= 127;
+  e[31] |= 64;
+
+  fexpand(bp, basepoint);
+  cmult(x, z, e, bp);
+  crecip(zmone, z);
+  fmul(z, x, zmone);
+  freduce_coefficients(z);
+  fcontract(mypublic, z);
+  return 0;
+}
diff --git a/lib/ed25519-donna/ed25519.c b/lib/ed25519-donna/ed25519.c
new file mode 100644
--- /dev/null
+++ b/lib/ed25519-donna/ed25519.c
@@ -0,0 +1,109 @@
+/*
+	Public domain by Andrew M. <liquidsun@gmail.com>
+
+	Ed25519 reference implementation using Ed25519-donna
+*/
+
+
+#include "ed25519-donna.h"
+#include "ed25519.h"
+#include "ed25519-randombytes.h"
+#include <openssl/sha.h>
+
+/*
+	Generates a (extsk[0..31]) and aExt (extsk[32..63])
+*/
+
+static void DONNA_INLINE
+ed25519_extsk(hash_512bits extsk, const ed25519_secret_key sk) {
+	SHA512(sk, 32, extsk);
+	extsk[0] &= 248;
+	extsk[31] &= 127;
+	extsk[31] |= 64;
+}
+
+static void
+ed25519_hram(hash_512bits hram, const ed25519_signature RS, const ed25519_public_key pk, const unsigned char *m, size_t mlen) {
+	SHA512_CTX shactx;
+	SHA512_Init(&shactx);
+	SHA512_Update(&shactx, RS, 32);
+	SHA512_Update(&shactx, pk, 32);
+	SHA512_Update(&shactx, m, mlen);
+	SHA512_Final(hram, &shactx);
+}
+
+void
+ed25519_publickey(const ed25519_secret_key sk, ed25519_public_key pk) {
+	bignum256modm a;
+	ge25519 MM16 A;
+	hash_512bits extsk;
+
+	/* A = aB */
+	ed25519_extsk(extsk, sk);
+	expand256_modm(a, extsk, 32);
+	ge25519_scalarmult_base_niels(&A, a);
+	ge25519_pack(pk, &A);
+}
+
+
+void
+ed25519_sign(const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_public_key pk, ed25519_signature RS) {
+	SHA512_CTX shactx;
+	bignum256modm r, S, a;
+	ge25519 MM16 R;
+	hash_512bits extsk, hashr, hram;
+
+	ed25519_extsk(extsk, sk);
+
+	/* r = H(aExt[32..64], m) */
+	SHA512_Init(&shactx);
+	SHA512_Update(&shactx, extsk + 32, 32);
+	SHA512_Update(&shactx, m, mlen);
+	SHA512_Final(hashr, &shactx);
+	expand256_modm(r, hashr, 64);
+
+	/* R = rB */
+	ge25519_scalarmult_base_niels(&R, r);
+	ge25519_pack(RS, &R);
+
+	/* S = H(R,A,m).. */
+	ed25519_hram(hram, RS, pk, m, mlen);
+	expand256_modm(S, hram, 64);
+
+	/* S = H(R,A,m)a */
+	expand256_modm(a, extsk, 32);
+	mul256_modm(S, S, a);
+
+	/* S = (r + H(R,A,m)a) */
+	add256_modm(S, S, r);
+
+	/* S = (r + H(R,A,m)a) mod L */	
+	contract256_modm(RS + 32, S);
+}
+
+int
+ed25519_sign_open(const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS) {
+	ge25519 MM16 R, A;
+	hash_512bits hash;
+	bignum256modm hram, S;
+	unsigned char checkR[32];
+
+	if ((RS[63] & 224) || !ge25519_unpack_negative_vartime(&A, pk))
+		return -1;
+
+	/* hram = H(R,A,m) */
+	ed25519_hram(hash, RS, pk, m, mlen);
+	expand256_modm(hram, hash, 64);
+
+	/* S */
+	expand256_modm(S, RS + 32, 32);
+
+	/* SB - H(R,A,m)A */
+	ge25519_double_scalarmult_vartime(&R, &A, hram, S);
+	ge25519_pack(checkR, &R);
+
+	/* check that R = SB - H(R,A,m)A */
+	return ed25519_verify(RS, checkR, 32) ? 0 : -1;
+}
+
+#include "ed25519-donna-batchverify.h"
diff --git a/tests/TestCore.hs b/tests/TestCore.hs
new file mode 100644
--- /dev/null
+++ b/tests/TestCore.hs
@@ -0,0 +1,35 @@
+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+module Main where
+
+import Data.Monoid
+import qualified Data.ByteString.Lazy as LZ
+
+import Test.QuickCheck
+import Test.Framework.Providers.QuickCheck2
+import Test.Framework
+
+import Dust.Model.Huffman (bitpack, bitunpack, padToEight)
+
+-- | Bits of the sort that can be bitpack'd and then bitunpack'd
+newtype Bits = Bits [Bool] deriving (Show)
+
+instance Arbitrary Bits where
+    arbitrary = fmap (Bits . padToEight) $ arbitrary `suchThat` startsWithTrue
+        where
+        startsWithTrue (True:_) = True
+        startsWithTrue _ = False
+
+prop_bitpack_unpack_loop :: Bits -> Bool
+prop_bitpack_unpack_loop (Bits bits) = bitunpack packed == bits
+    where
+    packed = mconcat $ LZ.toChunks $ bitpack bits
+    {-# NOINLINE packed #-}
+
+main :: IO ()
+main = defaultMain [
+     testGroup "Huffman" [
+         testProperty "bit pack/unpack loop" prop_bitpack_unpack_loop
+       ]
+   ]
diff --git a/tests/TestCrypto.hs b/tests/TestCrypto.hs
new file mode 100644
--- /dev/null
+++ b/tests/TestCrypto.hs
@@ -0,0 +1,104 @@
+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+module Main where
+
+import Test.Framework.Providers.HUnit
+import Test.Framework.Providers.QuickCheck2
+import Test.Framework
+import Test.HUnit hiding (test)
+import Test.QuickCheck
+import Test.QuickCheck.Gen
+
+import Data.ByteString as B (ByteString, length)
+import Data.ByteString.Char8 (pack, useAsCString, packCString)
+
+import Dust.Crypto.DustCipher
+import qualified Dust.Crypto.ECDH as ECDH
+import qualified Dust.Crypto.ECDSA as ECDSA
+import Dust.Crypto.Keys
+
+gen_privkeys :: Gen String
+gen_privkeys =
+  let e = elements ['\00'..'\FF']
+  in vectorOf 32 e
+
+prop_ECDH_pubkey_size :: Property
+prop_ECDH_pubkey_size =
+  forAll gen_privkeys $ \privBytes ->
+    let priv = PrivateKey $ pack privBytes
+        (PublicKey pub)  = ECDH.createPublic priv
+    in B.length pub == 32
+
+prop_ECDSA_pubkey_size :: Property
+prop_ECDSA_pubkey_size =
+  forAll gen_privkeys $ \privBytes ->
+    let priv = PrivateKey $ pack privBytes
+        (PublicKey pub)  = ECDSA.createPublic priv
+    in B.length pub == 32
+
+case_ECDH_keypair_save_load = do
+    let input = (pack "1234567890123457890123456789012")
+    let keypair1 = ECDH.createKeypair input
+    saveKeypair keypair1
+
+    keypair2 <- loadKeypair
+
+    keypair1 @?= keypair2
+
+case_ECDH_createShared = do
+    let entropy1 = (pack "1234567890123457890123456789012")
+    let private1 = ECDH.createPrivate entropy1
+    let public1 = ECDH.createPublic private1
+
+    let entropy2 = (pack "2109876543210987543210987654321")
+    let private2 = ECDH.createPrivate entropy2
+    let public2 = ECDH.createPublic private2
+
+    let shared1 = ECDH.createShared private1 public2
+    let shared2 = ECDH.createShared private2 public1
+
+    shared1 @?= shared2
+
+case_ECDH_createKeypair = do
+    let input = (pack "1234567890123457890123456789012")
+    let keys = ECDH.createKeypair input
+    let Keypair public private = keys
+    publicBytes public @?= pack "\164x}\156n\177\164\240\187F\163\202\232}\221\207/x\ETX\178\SOH\208\238\213q\138%\137\159\253+{"
+    privateBytes private @?= pack "023456789012345789012345678901r"
+
+case_ECDH_createPublic = do
+    let input = (pack "1234567890123457890123456789012")
+    let private = ECDH.createPrivate input
+    let public = ECDH.createPublic private
+    publicBytes public @?= pack "\164x}\156n\177\164\240\187F\163\202\232}\221\207/x\ETX\178\SOH\208\238\213q\138%\137\159\253+{"
+
+case_ECDH_createPrivate = do
+    let input = (pack "1234567890123457890123456789012")
+    let output = ECDH.createPrivate input
+    privateBytes output @?= pack "023456789012345789012345678901r"
+
+case_encrypt_decrypt = let key = EncryptionKey (pack "1234567890123456")
+                           iv = IV (pack "1234567890123456")
+
+                           plain1 = Plaintext (pack "majestic")
+                           cipher = encrypt key iv plain1
+                           plain2 = decrypt key iv cipher
+
+                       in plain1 @?= plain2
+
+main :: IO ()
+main =
+ defaultMain [
+     testGroup "ECDH25519" [ 
+         testCase "ECDH_createPrivate" case_ECDH_createPrivate,
+         testCase "ECDH_createPublic" case_ECDH_createPublic,
+         testCase "ECDH_createKeypair" case_ECDH_createKeypair,
+         testCase "ECDH_createShared" case_ECDH_createShared,
+         testCase "ECDH_keypair_save_load" case_ECDH_keypair_save_load,
+         testCase "encrypt_decrypt" case_encrypt_decrypt,
+        
+         testProperty "ECDH_pubkey_size" prop_ECDH_pubkey_size,
+         testProperty "ECDSA_pubkey_size" prop_ECDSA_pubkey_size
+       ]
+   ]
