diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,12 @@
+## 0.2.1
+
+* Add the `Crypto.Cipher.ChaChaPoly1305.Conduit` and
+  `Crypto.PubKey.ECIES.Conduit` modules
+
+## 0.2.0
+
+* Add HMAC sink
+
+## 0.1
+
+* Initial release
diff --git a/Crypto/Cipher/ChaChaPoly1305/Conduit.hs b/Crypto/Cipher/ChaChaPoly1305/Conduit.hs
new file mode 100644
--- /dev/null
+++ b/Crypto/Cipher/ChaChaPoly1305/Conduit.hs
@@ -0,0 +1,88 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+module Crypto.Cipher.ChaChaPoly1305.Conduit
+  ( encrypt
+  , decrypt
+  , ChaChaException (..)
+  ) where
+
+import           Control.Exception            (assert)
+import           Control.Monad.Catch          (Exception, MonadThrow, throwM)
+import qualified Crypto.Cipher.ChaChaPoly1305 as Cha
+import qualified Crypto.Error                 as CE
+import qualified Crypto.MAC.Poly1305          as Poly1305
+import qualified Data.ByteArray               as BA
+import           Data.ByteString              (ByteString)
+import qualified Data.ByteString              as B
+import qualified Data.ByteString.Lazy         as BL
+import           Data.Conduit                 (ConduitM, await, leftover, yield)
+import qualified Data.Conduit.Binary          as CB
+import           Data.Typeable                (Typeable)
+
+cf :: MonadThrow m
+   => (CE.CryptoError -> ChaChaException)
+   -> CE.CryptoFailable a
+   -> m a
+cf _ (CE.CryptoPassed x) = return x
+cf f (CE.CryptoFailed e) = throwM (f e)
+
+data ChaChaException
+  = EncryptNonceException !CE.CryptoError
+  | EncryptKeyException !CE.CryptoError
+  | DecryptNonceException !CE.CryptoError
+  | DecryptKeyException !CE.CryptoError
+  | MismatchedAuth
+  deriving (Show, Typeable)
+instance Exception ChaChaException
+
+encrypt
+  :: MonadThrow m
+  => ByteString -- ^ nonce (12 random bytes)
+  -> ByteString -- ^ symmetric key (32 bytes)
+  -> ConduitM ByteString ByteString m ()
+encrypt nonceBS key = do
+  nonce <- cf EncryptNonceException $ Cha.nonce12 nonceBS
+  state0 <- cf EncryptKeyException $ Cha.initialize key nonce
+  yield nonceBS
+  let loop state1 = do
+        mbs <- await
+        case mbs of
+          Nothing -> yield $ BA.convert $ Cha.finalize state1
+          Just bs -> do
+            let (bs', state2) = Cha.encrypt bs state1
+            yield bs'
+            loop state2
+  loop $ Cha.finalizeAAD state0
+
+decrypt
+  :: MonadThrow m
+  => ByteString -- ^ symmetric key (32 bytes)
+  -> ConduitM ByteString ByteString m ()
+decrypt key = do
+  nonceBS <- CB.take 12
+  nonce <- cf DecryptNonceException $ Cha.nonce12 $ BL.toStrict nonceBS
+  state0 <- cf DecryptKeyException $ Cha.initialize key nonce
+  let loop state1 = do
+        ebs <- awaitExcept16 id
+        case ebs of
+          Left final ->
+            case Poly1305.authTag final of
+              CE.CryptoPassed final' | Cha.finalize state1 == final' -> return ()
+              _ -> throwM MismatchedAuth
+          Right bs -> do
+            let (bs', state2) = Cha.decrypt bs state1
+            yield bs'
+            loop state2
+  loop $ Cha.finalizeAAD state0
+  where
+    awaitExcept16 front = do
+      mbs <- await
+      case mbs of
+        Nothing -> return $ Left $ front B.empty
+        Just bs -> do
+          let bs' = front bs
+          if B.length bs' > 16
+            then do
+              let (x, y) = B.splitAt (B.length bs' - 16) bs'
+              assert (B.length y == 16) leftover y
+              return $ Right x
+            else awaitExcept16 (B.append bs')
diff --git a/Crypto/PubKey/ECIES/Conduit.hs b/Crypto/PubKey/ECIES/Conduit.hs
new file mode 100644
--- /dev/null
+++ b/Crypto/PubKey/ECIES/Conduit.hs
@@ -0,0 +1,80 @@
+{-# LANGUAGE CPP #-}
+module Crypto.PubKey.ECIES.Conduit
+  ( encrypt
+  , decrypt
+  ) where
+
+import           Control.Monad.Catch                  (MonadThrow, throwM)
+import           Control.Monad.Trans.Class            (lift)
+import qualified Crypto.Cipher.ChaCha                 as ChaCha
+import qualified Crypto.Cipher.ChaChaPoly1305.Conduit as ChaCha
+import qualified Crypto.ECC                           as ECC
+import qualified Crypto.Error                         as CE
+import           Crypto.Hash                          (SHA512 (..), hashWith)
+import           Crypto.PubKey.ECIES                  (deriveDecrypt,
+                                                       deriveEncrypt)
+import           Crypto.Random                        (MonadRandom)
+import qualified Data.ByteArray                       as BA
+import           Data.ByteString                      (ByteString)
+import qualified Data.ByteString                      as B
+import qualified Data.ByteString.Lazy                 as BL
+import           Data.Conduit                         (ConduitM, yield)
+import qualified Data.Conduit.Binary                  as CB
+import           Data.Proxy                           (Proxy (..))
+import           System.IO.Unsafe                     (unsafePerformIO)
+
+getNonceKey :: ECC.SharedSecret -> (ByteString, ByteString)
+getNonceKey shared =
+  let state1 = ChaCha.initializeSimple $ B.take 40 $ BA.convert $ hashWith SHA512 shared
+      (nonce, state2) = ChaCha.generateSimple state1 12
+      (key, _) = ChaCha.generateSimple state2 32
+   in (nonce, key)
+
+type Curve = ECC.Curve_P256R1
+
+proxy :: Proxy Curve
+proxy = Proxy
+
+pointBinarySize :: Int
+pointBinarySize = B.length $ ECC.encodePoint proxy point
+  where
+    point = unsafePerformIO (ECC.keypairGetPublic <$> ECC.curveGenerateKeyPair proxy)
+{-# NOINLINE pointBinarySize #-}
+
+throwOnFail :: MonadThrow m => CE.CryptoFailable a -> m a
+throwOnFail (CE.CryptoPassed a) = pure a
+throwOnFail (CE.CryptoFailed e) = throwM e
+
+
+encrypt
+  :: (MonadThrow m, MonadRandom m)
+  => ECC.Point Curve
+  -> ConduitM ByteString ByteString m ()
+encrypt point = do
+  (point', shared) <- lift (deriveEncryptCompat proxy point) >>= throwOnFail
+  let (nonce, key) = getNonceKey shared
+  yield $ ECC.encodePoint proxy point'
+  ChaCha.encrypt nonce key
+  where
+#if MIN_VERSION_cryptonite(0,23,999)
+    deriveEncryptCompat prx p = deriveEncrypt prx p
+#else
+    deriveEncryptCompat prx p = CE.CryptoPassed <$> deriveEncrypt prx p
+#endif
+
+decrypt
+  :: (MonadThrow m)
+  => ECC.Scalar Curve
+  -> ConduitM ByteString ByteString m ()
+decrypt scalar = do
+  pointBS <- fmap BL.toStrict $ CB.take pointBinarySize
+  point   <- throwOnFail (ECC.decodePoint proxy pointBS)
+  shared  <- throwOnFail (deriveDecryptCompat proxy point scalar)
+  let (_nonce, key) = getNonceKey shared
+  ChaCha.decrypt key
+  where
+#if MIN_VERSION_cryptonite(0,23,999)
+    deriveDecryptCompat prx p s = deriveDecrypt prx p s
+#else
+    deriveDecryptCompat prx p s = CE.CryptoPassed (deriveDecrypt prx p s)
+#endif
diff --git a/cryptonite-conduit.cabal b/cryptonite-conduit.cabal
--- a/cryptonite-conduit.cabal
+++ b/cryptonite-conduit.cabal
@@ -1,5 +1,5 @@
 Name:                cryptonite-conduit
-Version:             0.2.0
+version:             0.2.1
 Synopsis:            cryptonite conduit
 Description:
   Conduit bridge for cryptonite
@@ -17,16 +17,19 @@
 Build-Type:          Simple
 Homepage:            https://github.com/haskell-crypto/cryptonite-conduit
 Cabal-Version:       >=1.8
-Extra-source-files:  README.md
+Extra-source-files:  README.md CHANGELOG.md
 
 Library
-  Exposed-modules:   Crypto.MAC.HMAC.Conduit
+  Exposed-modules:   Crypto.Cipher.ChaChaPoly1305.Conduit
+                     Crypto.MAC.HMAC.Conduit
                      Crypto.Hash.Conduit
+                     Crypto.PubKey.ECIES.Conduit
   Build-depends:     base >= 4 && < 5
                    , bytestring
                    , conduit
                    , conduit-extra
                    , cryptonite
+                   , exceptions
                    , memory
                    , resourcet
                    , transformers
@@ -45,6 +48,7 @@
                      , memory
                      , tasty
                      , tasty-hunit
+                     , tasty-quickcheck
   ghc-options:         -threaded -rtsopts -with-rtsopts=-N -W -Wall
 
 source-repository head
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -1,11 +1,18 @@
 {-# Language OverloadedStrings #-}
 import Conduit
+import qualified Crypto.Cipher.ChaChaPoly1305.Conduit as ChaCha
+import qualified Crypto.ECC as ECC
 import Crypto.Hash
 import Crypto.MAC.HMAC
 import Crypto.MAC.HMAC.Conduit
+import qualified Crypto.PubKey.ECIES.Conduit as PubKey
+import Crypto.Random
 import Data.ByteArray.Encoding
+import Data.Proxy (Proxy (..))
+import Data.Word (Word8)
 import Test.Tasty
 import Test.Tasty.HUnit
+import Test.Tasty.QuickCheck
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Lazy as BL
 
@@ -17,6 +24,12 @@
     [ testGroup "HMAC"
         [ testCase "File HMAC is correct" testFileHMAC
         ]
+    , testGroup "ChaChaPoly1305"
+        [ testProperty "encrypt/decrypt works" (ioProperty . propChaChaPoly1305)
+        ]
+    , testGroup "publicECC"
+        [ testProperty "encrypt/decrypt works" (ioProperty . propPublicECC)
+        ]
     ]
 
 testFileHMAC :: Assertion
@@ -25,3 +38,26 @@
     testhmac <- runConduit $ sourceLazy source $$ sinkHMAC ("foobar" :: BS.ByteString)
     let hexdump = convertToBase Base16 (testhmac :: HMAC SHA512t_256)
     assertEqual "HMAC mismatch" "ab78ef7a3a7b02b2ef50ee1a17e43ae0c134e0bece468b047780626264301831" (hexdump :: BS.ByteString)
+
+propChaChaPoly1305 :: [[Word8]] -> IO Bool
+propChaChaPoly1305 octets = do
+    let chunksIn = map BS.pack octets
+    nonce <- getRandomBytes 12
+    key <- getRandomBytes 32
+    chunksOut <- runConduit
+       $ mapM_ yield chunksIn
+      .| ChaCha.encrypt nonce key
+      .| ChaCha.decrypt key
+      .| sinkLazy
+    return $ BL.fromChunks chunksIn == chunksOut
+
+propPublicECC :: [[Word8]] -> IO Bool
+propPublicECC octets = do
+    let chunksIn = map BS.pack octets
+    ECC.KeyPair point scalar <- ECC.curveGenerateKeyPair (Proxy :: Proxy ECC.Curve_P256R1)
+    chunksOut <- runConduit
+       $ mapM_ yield chunksIn
+      .| PubKey.encrypt point
+      .| PubKey.decrypt scalar
+      .| sinkLazy
+    return $ BL.fromChunks chunksIn == chunksOut
