diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,9 @@
 # Changelog
 
+- 0.3.2 (2026-11-14)
+  * Adds Crypto.Cipher.ChaCha, with ChaCha keystreams of 8, 12, or 20
+    rounds.
+
 - 0.3.1 (2026-10-31)
   * Uses SSE2 on x86_64, along with AVX2 on CPUs that support it,
     detected at runtime. With AVX2, 'cipher' is roughly 3.5x faster on
diff --git a/bench/Main.hs b/bench/Main.hs
--- a/bench/Main.hs
+++ b/bench/Main.hs
@@ -8,6 +8,7 @@
 
 import Control.DeepSeq
 import Criterion.Main
+import qualified Crypto.Cipher.ChaCha as ChaCha
 import qualified Crypto.Cipher.ChaCha20 as ChaCha20
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Base16 as B16
@@ -21,6 +22,7 @@
 main :: IO ()
 main = defaultMain [
     suite
+  , keystream
   ]
 
 plain :: BS.ByteString
@@ -47,5 +49,13 @@
     bench "cipher" $ nf (ChaCha20.cipher key 1 non) plain
   , bench "cipher (1024B)" $ nf (ChaCha20.cipher key 1 non) plain_1k
   , bench "cipher (4096B)" $ nf (ChaCha20.cipher key 1 non) plain_4k
+  ]
+
+keystream :: Benchmark
+keystream =
+  bgroup "ppad-chacha (keystream, 1024B)" [
+    bench "ChaCha8" $ nf (ChaCha.keystream ChaCha.ChaCha8 key 1 non) 1024
+  , bench "ChaCha12" $ nf (ChaCha.keystream ChaCha.ChaCha12 key 1 non) 1024
+  , bench "ChaCha20" $ nf (ChaCha.keystream ChaCha.ChaCha20 key 1 non) 1024
   ]
 
diff --git a/bench/Weight.hs b/bench/Weight.hs
--- a/bench/Weight.hs
+++ b/bench/Weight.hs
@@ -7,6 +7,7 @@
 module Main where
 
 import Control.DeepSeq
+import qualified Crypto.Cipher.ChaCha as ChaCha
 import qualified Crypto.Cipher.ChaCha20 as ChaCha20
 import qualified Data.ByteString as BS
 import GHC.Generics
@@ -21,6 +22,7 @@
 main = mainWith $ do
   block
   cipher
+  keystream
 
 block :: Weigh ()
 block =
@@ -46,3 +48,13 @@
           (ChaCha20.cipher key 1 non) bs2
         func "cipher (4096B input)"
           (ChaCha20.cipher key 1 non) bs3
+
+keystream :: Weigh ()
+keystream =
+  let !key = BS.replicate 32 0x88
+      !non = BS.replicate 12 0x00
+  in  wgroup "keystream" $ do
+        func "keystream (ChaCha8, 1024B)"
+          (ChaCha.keystream ChaCha.ChaCha8 key 1 non) 1024
+        func "keystream (ChaCha20, 1024B)"
+          (ChaCha.keystream ChaCha.ChaCha20 key 1 non) 1024
diff --git a/cbits/chacha20_native.c b/cbits/chacha20_native.c
--- a/cbits/chacha20_native.c
+++ b/cbits/chacha20_native.c
@@ -65,13 +65,15 @@
         v1 = veorq_u32(v1, v2); v1 = ROTL32_7(v1);        \
     } while (0)
 
-/* 20-round ChaCha20 core: 10 iterations of (column + diagonal). */
+/* ChaCha core: 'drounds' iterations of (column + diagonal), e.g.
+ * 10 for ChaCha20. */
 static inline void chacha20_core(uint32x4_t *v0, uint32x4_t *v1,
                                   uint32x4_t *v2, uint32x4_t *v3,
                                   uint32x4_t s0, uint32x4_t s1,
-                                  uint32x4_t s2, uint32x4_t s3) {
+                                  uint32x4_t s2, uint32x4_t s3,
+                                  int drounds) {
     uint32x4_t a = s0, b = s1, c = s2, d = s3;
-    for (int i = 0; i < 10; i++) {
+    for (int i = 0; i < drounds; i++) {
         QUARTER(a, b, c, d);
         /* shift rows: row 1 left 1, row 2 left 2, row 3 left 3.    */
         b = vextq_u32(b, b, 1);
@@ -106,10 +108,12 @@
 }
 
 /*
- * Generate one 64-byte ChaCha20 keystream block at 'out'.
+ * Generate one 64-byte keystream block at 'out', using 'drounds'
+ * double rounds (10 for ChaCha20).
  */
 void chacha20_block_native(const uint8_t key[32], uint32_t counter,
-                           const uint8_t nonce[12], uint8_t out[64]) {
+                           const uint8_t nonce[12], uint8_t out[64],
+                           int drounds) {
     uint32x4_t s0, s1, s2;
     uint32_t n0, n1, n2;
     chacha20_setup(key, nonce, &s0, &s1, &s2, &n0, &n1, &n2);
@@ -117,7 +121,7 @@
     uint32_t s3_in[4] = { counter, n0, n1, n2 };
     uint32x4_t s3 = vld1q_u32(s3_in);
     uint32x4_t v0, v1, v2, v3;
-    chacha20_core(&v0, &v1, &v2, &v3, s0, s1, s2, s3);
+    chacha20_core(&v0, &v1, &v2, &v3, s0, s1, s2, s3, drounds);
 
     vst1q_u8(out + 0,  vreinterpretq_u8_u32(v0));
     vst1q_u8(out + 16, vreinterpretq_u8_u32(v1));
@@ -126,14 +130,15 @@
 }
 
 /*
- * Encrypt/decrypt 'inlen' bytes at 'in' into 'out' using ChaCha20
- * with the given key, starting counter, and nonce.  Stream cipher,
- * so the same routine decrypts.
+ * Encrypt/decrypt 'inlen' bytes at 'in' into 'out' using ChaCha with
+ * 'drounds' double rounds (10 for ChaCha20) and the given key,
+ * starting counter, and nonce.  Stream cipher, so the same routine
+ * decrypts.  'in' and 'out' may be the same buffer.
  */
 void chacha20_cipher_native(const uint8_t key[32], uint32_t counter,
                             const uint8_t nonce[12],
                             const uint8_t *in, uint8_t *out,
-                            size_t inlen) {
+                            size_t inlen, int drounds) {
     uint32x4_t s0, s1, s2;
     uint32_t n0, n1, n2;
     chacha20_setup(key, nonce, &s0, &s1, &s2, &n0, &n1, &n2);
@@ -143,7 +148,7 @@
         uint32_t s3_in[4] = { counter, n0, n1, n2 };
         uint32x4_t s3 = vld1q_u32(s3_in);
         uint32x4_t v0, v1, v2, v3;
-        chacha20_core(&v0, &v1, &v2, &v3, s0, s1, s2, s3);
+        chacha20_core(&v0, &v1, &v2, &v3, s0, s1, s2, s3, drounds);
 
         uint8x16_t i0 = vld1q_u8(in + pos +  0);
         uint8x16_t i1 = vld1q_u8(in + pos + 16);
@@ -168,7 +173,7 @@
         uint32_t s3_in[4] = { counter, n0, n1, n2 };
         uint32x4_t s3 = vld1q_u32(s3_in);
         uint32x4_t v0, v1, v2, v3;
-        chacha20_core(&v0, &v1, &v2, &v3, s0, s1, s2, s3);
+        chacha20_core(&v0, &v1, &v2, &v3, s0, s1, s2, s3, drounds);
 
         uint8_t block[64];
         vst1q_u8(block +  0, vreinterpretq_u8_u32(v0));
@@ -219,13 +224,15 @@
         v1 = _mm_xor_si128(v1, v2); v1 = ROTL32_N(v1, 7);        \
     } while (0)
 
-/* 20-round ChaCha20 core: 10 iterations of (column + diagonal). */
+/* ChaCha core: 'drounds' iterations of (column + diagonal), e.g.
+ * 10 for ChaCha20. */
 static inline void chacha20_core(__m128i *v0, __m128i *v1,
                                  __m128i *v2, __m128i *v3,
                                  __m128i s0, __m128i s1,
-                                 __m128i s2, __m128i s3) {
+                                 __m128i s2, __m128i s3,
+                                 int drounds) {
     __m128i a = s0, b = s1, c = s2, d = s3;
-    for (int i = 0; i < 10; i++) {
+    for (int i = 0; i < drounds; i++) {
         QUARTER(a, b, c, d);
         /* shift rows: row 1 left 1, row 2 left 2, row 3 left 3.    */
         b = _mm_shuffle_epi32(b, _MM_SHUFFLE(0, 3, 2, 1));
@@ -347,14 +354,15 @@
         v1 = _mm256_xor_si256(v1, v2); v1 = ROTL32_N_AVX2(v1, 7);   \
     } while (0)
 
-/* 20-round ChaCha20 core over two blocks. */
+/* ChaCha core over two blocks. */
 __attribute__((target("avx2")))
 static inline void chacha20_core_avx2(__m256i *v0, __m256i *v1,
                                       __m256i *v2, __m256i *v3,
                                       __m256i s0, __m256i s1,
-                                      __m256i s2, __m256i s3) {
+                                      __m256i s2, __m256i s3,
+                                      int drounds) {
     __m256i a = s0, b = s1, c = s2, d = s3;
-    for (int i = 0; i < 10; i++) {
+    for (int i = 0; i < drounds; i++) {
         QUARTER_AVX2(a, b, c, d);
         b = _mm256_shuffle_epi32(b, _MM_SHUFFLE(0, 3, 2, 1));
         c = _mm256_shuffle_epi32(c, _MM_SHUFFLE(1, 0, 3, 2));
@@ -370,13 +378,14 @@
     *v3 = _mm256_add_epi32(d, s3);
 }
 
-/* 20-round ChaCha20 core over four blocks, as two interleaved
+/* ChaCha core over four blocks, as two interleaved
  * pairs so that their independent dependency chains overlap.        */
 __attribute__((target("avx2")))
-static inline void chacha20_core4_avx2(__m256i v[8], const __m256i s[8]) {
+static inline void chacha20_core4_avx2(__m256i v[8], const __m256i s[8],
+                                       int drounds) {
     __m256i a = s[0], b = s[1], c = s[2], d = s[3];
     __m256i e = s[4], f = s[5], g = s[6], h = s[7];
-    for (int i = 0; i < 10; i++) {
+    for (int i = 0; i < drounds; i++) {
         QUARTER_AVX2(a, b, c, d);
         QUARTER_AVX2(e, f, g, h);
         b = _mm256_shuffle_epi32(b, _MM_SHUFFLE(0, 3, 2, 1));
@@ -438,7 +447,7 @@
                                    uint32_t counter,
                                    const uint8_t nonce[12],
                                    const uint8_t *in, uint8_t *out,
-                                   size_t inlen) {
+                                   size_t inlen, int drounds) {
     __m256i s0 = _mm256_broadcastsi128_si256(
         _mm_loadu_si128((const __m128i *)chacha_constants));
     __m256i s1 = _mm256_broadcastsi128_si256(
@@ -462,7 +471,7 @@
                               (int)c3, (int)n0, (int)n1, (int)n2)
         };
         __m256i v[8];
-        chacha20_core4_avx2(v, s);
+        chacha20_core4_avx2(v, s, drounds);
         chacha20_xor2_avx2(in + pos, out + pos, v[0], v[1], v[2], v[3]);
         chacha20_xor2_avx2(in + pos + 128, out + pos + 128,
                            v[4], v[5], v[6], v[7]);
@@ -475,7 +484,7 @@
             (int)counter, (int)n0, (int)n1, (int)n2,
             (int)c1,      (int)n0, (int)n1, (int)n2);
         __m256i v0, v1, v2, v3;
-        chacha20_core_avx2(&v0, &v1, &v2, &v3, s0, s1, s2, s3);
+        chacha20_core_avx2(&v0, &v1, &v2, &v3, s0, s1, s2, s3, drounds);
         chacha20_xor2_avx2(in + pos, out + pos, v0, v1, v2, v3);
         pos += 128;
         counter += 2;
@@ -484,17 +493,19 @@
 }
 
 /*
- * Generate one 64-byte ChaCha20 keystream block at 'out'.
+ * Generate one 64-byte keystream block at 'out', using 'drounds'
+ * double rounds (10 for ChaCha20).
  */
 void chacha20_block_native(const uint8_t key[32], uint32_t counter,
-                           const uint8_t nonce[12], uint8_t out[64]) {
+                           const uint8_t nonce[12], uint8_t out[64],
+                           int drounds) {
     __m128i s0, s1, s2;
     uint32_t n0, n1, n2;
     chacha20_setup(key, nonce, &s0, &s1, &s2, &n0, &n1, &n2);
 
     __m128i s3 = chacha20_row3(counter, n0, n1, n2);
     __m128i v0, v1, v2, v3;
-    chacha20_core(&v0, &v1, &v2, &v3, s0, s1, s2, s3);
+    chacha20_core(&v0, &v1, &v2, &v3, s0, s1, s2, s3, drounds);
 
     _mm_storeu_si128((__m128i *)(out +  0), v0);
     _mm_storeu_si128((__m128i *)(out + 16), v1);
@@ -503,14 +514,15 @@
 }
 
 /*
- * Encrypt/decrypt 'inlen' bytes at 'in' into 'out' using ChaCha20
- * with the given key, starting counter, and nonce.  Stream cipher,
- * so the same routine decrypts.
+ * Encrypt/decrypt 'inlen' bytes at 'in' into 'out' using ChaCha with
+ * 'drounds' double rounds (10 for ChaCha20) and the given key,
+ * starting counter, and nonce.  Stream cipher, so the same routine
+ * decrypts.  'in' and 'out' may be the same buffer.
  */
 void chacha20_cipher_native(const uint8_t key[32], uint32_t counter,
                             const uint8_t nonce[12],
                             const uint8_t *in, uint8_t *out,
-                            size_t inlen) {
+                            size_t inlen, int drounds) {
     __m128i s0, s1, s2;
     uint32_t n0, n1, n2;
     chacha20_setup(key, nonce, &s0, &s1, &s2, &n0, &n1, &n2);
@@ -518,14 +530,15 @@
     /* block pairs via AVX2 where available, the rest via SSE2 */
     size_t pos = 0;
     if (inlen >= 128 && have_avx2()) {
-        pos = chacha20_cipher_avx2(key, counter, nonce, in, out, inlen);
+        pos = chacha20_cipher_avx2(key, counter, nonce, in, out, inlen,
+                                   drounds);
         counter += (uint32_t)(pos / 64);
     }
 
     while (pos + 64 <= inlen) {
         __m128i s3 = chacha20_row3(counter, n0, n1, n2);
         __m128i v0, v1, v2, v3;
-        chacha20_core(&v0, &v1, &v2, &v3, s0, s1, s2, s3);
+        chacha20_core(&v0, &v1, &v2, &v3, s0, s1, s2, s3, drounds);
 
         __m128i i0 = _mm_loadu_si128((const __m128i *)(in + pos +  0));
         __m128i i1 = _mm_loadu_si128((const __m128i *)(in + pos + 16));
@@ -549,7 +562,7 @@
     if (pos < inlen) {
         __m128i s3 = chacha20_row3(counter, n0, n1, n2);
         __m128i v0, v1, v2, v3;
-        chacha20_core(&v0, &v1, &v2, &v3, s0, s1, s2, s3);
+        chacha20_core(&v0, &v1, &v2, &v3, s0, s1, s2, s3, drounds);
 
         uint8_t block[64];
         _mm_storeu_si128((__m128i *)(block +  0), v0);
@@ -574,16 +587,17 @@
  * gated on 'chacha20_native_available' returning 0                 */
 
 void chacha20_block_native(const uint8_t *key, uint32_t counter,
-                           const uint8_t *nonce, uint8_t *out) {
-    (void)key; (void)counter; (void)nonce; (void)out;
+                           const uint8_t *nonce, uint8_t *out,
+                           int drounds) {
+    (void)key; (void)counter; (void)nonce; (void)out; (void)drounds;
 }
 
 void chacha20_cipher_native(const uint8_t *key, uint32_t counter,
                             const uint8_t *nonce,
                             const uint8_t *in, uint8_t *out,
-                            size_t inlen) {
+                            size_t inlen, int drounds) {
     (void)key; (void)counter; (void)nonce;
-    (void)in; (void)out; (void)inlen;
+    (void)in; (void)out; (void)inlen; (void)drounds;
 }
 
 int chacha20_native_available(void) {
diff --git a/lib/Crypto/Cipher/ChaCha.hs b/lib/Crypto/Cipher/ChaCha.hs
new file mode 100644
--- /dev/null
+++ b/lib/Crypto/Cipher/ChaCha.hs
@@ -0,0 +1,132 @@
+{-# OPTIONS_HADDOCK prune #-}
+{-# LANGUAGE BangPatterns #-}
+
+-- |
+-- Module: Crypto.Cipher.ChaCha
+-- Copyright: (c) 2026 Jared Tobin
+-- License: MIT
+-- Maintainer: Jared Tobin <jared@ppad.tech>
+--
+-- ChaCha keystreams with 8, 12, or 20 rounds, using the state layout
+-- of [RFC 8439](https://datatracker.ietf.org/doc/html/rfc8439).
+--
+-- The reduced-round variants are intended for uses such as
+-- random number generation; use "Crypto.Cipher.ChaCha20" for
+-- encryption.
+
+module Crypto.Cipher.ChaCha (
+    -- * Round counts
+    Rounds(..)
+
+    -- * Keystream
+  , keystream
+
+    -- * Error information
+  , Error(..)
+
+    -- internal
+  , _keystream_into
+  ) where
+
+import Control.Monad.ST (runST)
+import Crypto.Cipher.ChaCha20 (Error(..))
+import qualified Crypto.Cipher.ChaCha20 as ChaCha20
+import qualified Crypto.Cipher.ChaCha20.Native as Native
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Internal as BI
+import qualified Data.ByteString.Unsafe as BU
+import Data.Word (Word8, Word32, Word64)
+import Foreign.ForeignPtr (withForeignPtr)
+import Foreign.Marshal.Utils (copyBytes)
+import Foreign.Ptr (Ptr, castPtr, plusPtr)
+
+-- utils ----------------------------------------------------------------------
+
+fi :: (Integral a, Num b) => a -> b
+fi = fromIntegral
+{-# INLINE fi #-}
+
+-- rounds ---------------------------------------------------------------------
+
+-- | The number of ChaCha rounds.
+data Rounds =
+    ChaCha8  -- ^ 8 rounds
+  | ChaCha12 -- ^ 12 rounds
+  | ChaCha20 -- ^ 20 rounds, as specified by RFC 8439
+  deriving (Eq, Show)
+
+-- number of double rounds
+drounds :: Rounds -> Int
+drounds r = case r of
+  ChaCha8  -> 4
+  ChaCha12 -> 6
+  ChaCha20 -> 10
+{-# INLINE drounds #-}
+
+-- keystream ------------------------------------------------------------------
+
+-- | The ChaCha keystream with the given number of rounds, starting
+--   at the given block counter.
+--
+--   The key must be exactly 256 bits, and the nonce exactly 96 bits.
+--   The counter must leave room for the requested number of bytes;
+--   non-positive numbers of bytes yield the empty string.
+--
+--   >>> import qualified Data.ByteString as BS
+--   >>> import qualified Data.ByteString.Base16 as B16
+--   >>> let key = BS.replicate 32 0
+--   >>> let non = BS.replicate 12 0
+--   >>> fmap B16.encode (keystream ChaCha8 key 0 non 16)
+--   Right "3e00ef2f895f40d67f5bb8e81f09a5a1"
+keystream
+  :: Rounds         -- ^ number of rounds
+  -> BS.ByteString  -- ^ 256-bit key
+  -> Word32         -- ^ 32-bit counter
+  -> BS.ByteString  -- ^ 96-bit nonce
+  -> Int            -- ^ number of bytes
+  -> Either Error BS.ByteString -- ^ keystream
+keystream r key@(BI.PS kfp koff kl) counter nonce@(BI.PS nfp noff nl) len
+  | kl /= 32      = Left InvalidKey
+  | nl /= 12      = Left InvalidNonce
+  | len <= 0      = Right BS.empty
+  | fi len > room = Left CounterOverflow
+  | Native.chacha20_native_available =
+      Right $ BI.unsafeCreate len $ \dst ->
+        withForeignPtr kfp $ \kp ->
+        withForeignPtr nfp $ \np ->
+          Native.keystream_into (drounds r) (kp `plusPtr` koff) counter
+            (np `plusPtr` noff) dst len
+  | otherwise = Right $ runST $
+      ChaCha20._cipher_rounds (drounds r)
+        (ChaCha20._parse_key key) counter (ChaCha20._parse_nonce nonce)
+        (BS.replicate len 0)
+  where
+    !room = (0x100000000 - fi counter) * 64 :: Word64
+
+-- | Write keystream bytes to the destination pointer, for the key and
+--   nonce at the given pointers.
+--
+--   Unchecked: the key and nonce pointers must address 32 and 12
+--   bytes respectively, the destination must have room for the given
+--   number of bytes and must not overlap the key or nonce, and the
+--   counter must leave room for the bytes.
+_keystream_into
+  :: Rounds    -- ^ number of rounds
+  -> Ptr Word8 -- ^ 256-bit key
+  -> Word32    -- ^ 32-bit counter
+  -> Ptr Word8 -- ^ 96-bit nonce
+  -> Ptr Word8 -- ^ destination
+  -> Int       -- ^ number of bytes
+  -> IO ()
+_keystream_into r kp counter np dst len
+  | Native.chacha20_native_available =
+      Native.keystream_into (drounds r) kp counter np dst len
+  | otherwise = do
+      key <- BS.packCStringLen (castPtr kp, 32)
+      non <- BS.packCStringLen (castPtr np, 12)
+      let !out = runST $
+            ChaCha20._cipher_rounds (drounds r)
+              (ChaCha20._parse_key key) counter
+              (ChaCha20._parse_nonce non) (BS.replicate len 0)
+      BU.unsafeUseAsCString out $ \src ->
+        copyBytes dst (castPtr src) len
diff --git a/lib/Crypto/Cipher/ChaCha20.hs b/lib/Crypto/Cipher/ChaCha20.hs
--- a/lib/Crypto/Cipher/ChaCha20.hs
+++ b/lib/Crypto/Cipher/ChaCha20.hs
@@ -28,6 +28,7 @@
   , ChaCha(..)
   , _chacha
   , _cipher
+  , _cipher_rounds
   , _parse_key
   , _parse_nonce
   , _quarter
@@ -257,15 +258,17 @@
   _quarter state 03 04 09 14
 {-# INLINEABLE _rounds #-}
 
+-- block function with the given number of double rounds
 _block
   :: PrimMonad m
-  => ChaCha (PrimState m)
+  => Int
+  -> ChaCha (PrimState m)
   -> Word32
   -> m BS.ByteString
-_block state@(ChaCha s) counter = do
+_block drounds state@(ChaCha s) counter = do
   _chacha_counter state counter
   i <- PA.freezePrimArray s 0 16
-  for_ [1..10 :: Int] (const (_rounds state))
+  for_ [1..drounds] (const (_rounds state))
   for_ [0..15 :: Int] $ \idx -> do
     let iv = PA.indexPrimArray i idx
     sv <- PA.readPrimArray s idx
@@ -294,7 +297,7 @@
   | kl /= 32 = Left InvalidKey
   | nl /= 12 = Left InvalidNonce
   | Native.chacha20_native_available =
-      Right (Native.block key counter nonce)
+      Right (Native.block 10 key counter nonce)
   | otherwise = pure $ runST $ do
       let k = _parse_key key
           n = _parse_nonce nonce
@@ -355,7 +358,7 @@
     | nl /= 12  = Left InvalidNonce
     | pl > room = Left CounterOverflow
     | Native.chacha20_native_available =
-        Right (Native.cipher raw_key counter raw_nonce plaintext)
+        Right (Native.cipher 10 raw_key counter raw_nonce plaintext)
     | otherwise = pure $ runST $ do
         let key = _parse_key raw_key
             non = _parse_nonce raw_nonce
@@ -370,7 +373,19 @@
   -> Nonce
   -> BS.ByteString
   -> m BS.ByteString
-_cipher key counter nonce plaintext = do
+_cipher = _cipher_rounds 10
+{-# INLINE _cipher #-}
+
+-- stream cipher with the given number of double rounds
+_cipher_rounds
+  :: PrimMonad m
+  => Int
+  -> Key
+  -> Word32
+  -> Nonce
+  -> BS.ByteString
+  -> m BS.ByteString
+_cipher_rounds drounds key counter nonce plaintext = do
   ChaCha initial <- _chacha key counter nonce
   state@(ChaCha s) <- _chacha_alloc
 
@@ -380,10 +395,10 @@
               BS.toStrict (BSB.toLazyByteString acc)
           | otherwise -> do
               PA.copyMutablePrimArray s 0 initial 0 16
-              stream <- _block state j
+              stream <- _block drounds state j
               let cip = BS.packZipWith (.^.) chunk stream
               loop (acc <> BSB.byteString cip) (j + 1) etc
 
   loop mempty counter plaintext
-{-# INLINE _cipher #-}
+{-# INLINE _cipher_rounds #-}
 
diff --git a/lib/Crypto/Cipher/ChaCha20/Native.hs b/lib/Crypto/Cipher/ChaCha20/Native.hs
--- a/lib/Crypto/Cipher/ChaCha20/Native.hs
+++ b/lib/Crypto/Cipher/ChaCha20/Native.hs
@@ -13,6 +13,7 @@
     chacha20_native_available
   , block
   , cipher
+  , keystream_into
   ) where
 
 import qualified Data.ByteString as BS
@@ -20,6 +21,7 @@
 import Data.Word (Word8, Word32)
 import Foreign.C.Types (CInt(..), CSize(..))
 import Foreign.ForeignPtr (withForeignPtr)
+import Foreign.Marshal.Utils (fillBytes)
 import Foreign.Ptr (Ptr, plusPtr)
 import System.IO.Unsafe (unsafeDupablePerformIO)
 
@@ -27,12 +29,12 @@
 
 foreign import ccall unsafe "chacha20_block_native"
   c_chacha20_block
-    :: Ptr Word8 -> Word32 -> Ptr Word8 -> Ptr Word8 -> IO ()
+    :: Ptr Word8 -> Word32 -> Ptr Word8 -> Ptr Word8 -> CInt -> IO ()
 
 foreign import ccall unsafe "chacha20_cipher_native"
   c_chacha20_cipher
     :: Ptr Word8 -> Word32 -> Ptr Word8
-    -> Ptr Word8 -> Ptr Word8 -> CSize -> IO ()
+    -> Ptr Word8 -> Ptr Word8 -> CSize -> CInt -> IO ()
 
 foreign import ccall unsafe "chacha20_native_available"
   c_chacha20_native_available :: IO CInt
@@ -51,10 +53,11 @@
   unsafeDupablePerformIO c_chacha20_native_available /= 0
 {-# NOINLINE chacha20_native_available #-}
 
--- | One 64-byte ChaCha20 keystream block for the given (already-
---   validated) key, counter, and nonce.
-block :: BS.ByteString -> Word32 -> BS.ByteString -> BS.ByteString
-block (BI.PS kfp koff _) counter (BI.PS nfp noff _) =
+-- | One 64-byte keystream block, using the given number of double
+--   rounds, for the given (already-validated) key, counter, and nonce.
+block
+  :: Int -> BS.ByteString -> Word32 -> BS.ByteString -> BS.ByteString
+block drounds (BI.PS kfp koff _) counter (BI.PS nfp noff _) =
   BI.unsafeCreate 64 $ \dst ->
     withForeignPtr kfp $ \kp0 ->
     withForeignPtr nfp $ \np0 ->
@@ -62,13 +65,15 @@
                        counter
                        (np0 `plusPtr` noff)
                        dst
+                       (fi drounds)
 
--- | XOR the plaintext with the ChaCha20 keystream derived from the
---   given (already-validated) key, counter, and nonce.
+-- | XOR the plaintext with the keystream derived, using the given
+--   number of double rounds, from the given (already-validated) key,
+--   counter, and nonce.
 cipher
-  :: BS.ByteString -> Word32 -> BS.ByteString -> BS.ByteString
+  :: Int -> BS.ByteString -> Word32 -> BS.ByteString -> BS.ByteString
   -> BS.ByteString
-cipher (BI.PS kfp koff _) counter (BI.PS nfp noff _)
+cipher drounds (BI.PS kfp koff _) counter (BI.PS nfp noff _)
        (BI.PS pfp poff plen) =
   BI.unsafeCreate plen $ \dst ->
     withForeignPtr kfp $ \kp0 ->
@@ -80,3 +85,15 @@
                         (pp0 `plusPtr` poff)
                         dst
                         (fi plen)
+                        (fi drounds)
+
+-- | Write 'len' bytes of keystream, using the given number of double
+--   rounds, for the 32-byte key and 12-byte nonce at the given
+--   pointers, to the destination, which must not overlap them. The
+--   counter must leave room for 'len' bytes.
+keystream_into
+  :: Int -> Ptr Word8 -> Word32 -> Ptr Word8 -> Ptr Word8 -> Int
+  -> IO ()
+keystream_into drounds kp counter np dst len = do
+  fillBytes dst 0 len
+  c_chacha20_cipher kp counter np dst dst (fi len) (fi drounds)
diff --git a/ppad-chacha.cabal b/ppad-chacha.cabal
--- a/ppad-chacha.cabal
+++ b/ppad-chacha.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               ppad-chacha
-version:            0.3.1
+version:            0.3.2
 synopsis:           A fast ChaCha20 stream cipher
 license:            MIT
 license-file:       LICENSE
@@ -35,7 +35,8 @@
   if flag(llvm)
     ghc-options: -fllvm -O2
   exposed-modules:
-      Crypto.Cipher.ChaCha20
+      Crypto.Cipher.ChaCha
+    , Crypto.Cipher.ChaCha20
   other-modules:
       Crypto.Cipher.ChaCha20.Native
   build-depends:
@@ -55,6 +56,7 @@
   default-language:    Haskell2010
   hs-source-dirs:      test
   main-is:             Main.hs
+  other-modules:       Vectors
 
   ghc-options:
     -rtsopts -Wall -O2
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -2,20 +2,26 @@
 {-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE MagicHash #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE UnboxedTuples #-}
 
 module Main where
 
 import Control.Monad.ST (runST)
+import qualified Crypto.Cipher.ChaCha as Reduced
 import qualified Crypto.Cipher.ChaCha20 as ChaCha
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Base16 as B16
+import qualified Data.ByteString.Internal as BI
+import qualified Data.ByteString.Unsafe as BU
 import Data.Foldable (for_)
 import Data.Maybe (fromJust)
 import qualified Data.Primitive.PrimArray as PA
 import Data.Word (Word32)
+import Foreign.Ptr (castPtr)
 import Test.Tasty
 import qualified Test.Tasty.HUnit as H
+import Vectors (V(..), vectors)
 
 main :: IO ()
 main = defaultMain $ testGroup "ppad-chacha" [
@@ -30,6 +36,12 @@
   , counter_boundary
   , counter_overflow
   , dispatch
+  , testGroup "keystream" [
+        keystream_vectors
+      , keystream_errors
+      , keystream_dispatch
+      , keystream_into
+      ]
   ]
 
 quarter :: TestTree
@@ -211,3 +223,67 @@
   where
     keys = [block_key, BS.replicate 32 0xff]
     plain = BS.pack [fromIntegral (i * 7 + 3) | i <- [0..639 :: Int]]
+
+-- keystream
+
+hex :: BS.ByteString -> BS.ByteString
+hex bs = case B16.decode bs of
+  Nothing -> error "ppad-chacha (test): invalid hex"
+  Just x  -> x
+
+-- the vectors place a 64-bit IV in the last two words of the state,
+-- which is the RFC 8439 layout with a zero first nonce word
+keystream_vectors :: TestTree
+keystream_vectors = testGroup "draft-strombergson vectors" $
+  fmap check vectors
+  where
+    check V {..} =
+      H.testCase (v_case <> " (" <> show v_rounds <> ")") $ do
+        let non = BS.replicate 4 0 <> hex v_iv
+            out = Reduced.keystream v_rounds (hex v_key) 0 non 128
+        H.assertEqual mempty (Right (hex v_keystream)) out
+
+keystream_errors :: TestTree
+keystream_errors = H.testCase "keystream errors" $ do
+  let key = BS.replicate 32 0
+      non = BS.replicate 12 0
+  H.assertEqual "key" (Left ChaCha.InvalidKey)
+    (Reduced.keystream Reduced.ChaCha8 (BS.take 31 key) 0 non 64)
+  H.assertEqual "nonce" (Left ChaCha.InvalidNonce)
+    (Reduced.keystream Reduced.ChaCha8 key 0 (BS.take 11 non) 64)
+  H.assertEqual "boundary" (Right 128) $
+    fmap BS.length
+      (Reduced.keystream Reduced.ChaCha8 key (maxBound - 1) non 128)
+  H.assertEqual "overflow" (Left ChaCha.CounterOverflow)
+    (Reduced.keystream Reduced.ChaCha8 key (maxBound - 1) non 129)
+  H.assertEqual "zero length" (Right mempty)
+    (Reduced.keystream Reduced.ChaCha8 key 0 non 0)
+  H.assertEqual "negative length" (Right mempty)
+    (Reduced.keystream Reduced.ChaCha8 key 0 non (-1))
+
+-- the dispatching 'keystream' agrees with the pure Haskell
+-- implementation for every round count and every length from 0 to
+-- 640 bytes
+keystream_dispatch :: TestTree
+keystream_dispatch = H.testCase "keystream (dispatch vs pure)" $
+  for_ rounds $ \(r, d) ->
+  for_ [1, maxBound - 9] $ \con ->
+  for_ [0..640] $ \l -> do
+    let Right out = Reduced.keystream r block_key con block_non l
+        ref = runST $
+          ChaCha._cipher_rounds d (ChaCha._parse_key block_key) con
+            (ChaCha._parse_nonce block_non) (BS.replicate l 0)
+    H.assertEqual (show r <> ", length " <> show l) ref out
+  where
+    rounds = [(Reduced.ChaCha8, 4), (Reduced.ChaCha12, 6),
+              (Reduced.ChaCha20, 10)]
+
+keystream_into :: TestTree
+keystream_into = H.testCase "_keystream_into" $
+  for_ [Reduced.ChaCha8, Reduced.ChaCha12, Reduced.ChaCha20] $ \r -> do
+    let Right ref = Reduced.keystream r block_key 7 block_non 1000
+    out <- BI.create 1000 $ \dst ->
+      BU.unsafeUseAsCString block_key $ \kp ->
+      BU.unsafeUseAsCString block_non $ \np ->
+        Reduced._keystream_into r (castPtr kp) 7 (castPtr np) dst 1000
+    H.assertEqual (show r) ref out
diff --git a/test/Vectors.hs b/test/Vectors.hs
new file mode 100644
--- /dev/null
+++ b/test/Vectors.hs
@@ -0,0 +1,242 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- Keystream vectors for ChaCha with 8, 12, and 20 rounds and 256-bit
+-- keys, from etc/draft-strombergson-chacha-test-vectors-01.txt. Each
+-- keystream covers two blocks, at counters 0 and 1, with the 64-bit
+-- IV in the last two words of the state. All values are hex-encoded.
+
+module Vectors (
+    V(..)
+  , vectors
+  ) where
+
+import Crypto.Cipher.ChaCha (Rounds(..))
+import qualified Data.ByteString as BS
+
+data V = V {
+    v_case      :: !String
+  , v_rounds    :: !Rounds
+  , v_key       :: !BS.ByteString
+  , v_iv        :: !BS.ByteString
+  , v_keystream :: !BS.ByteString
+  }
+
+vectors :: [V]
+vectors = [
+    V "TC1" ChaCha8
+      "0000000000000000000000000000000000000000000000000000000000000000"
+      "0000000000000000"
+      (mconcat [
+          "3e00ef2f895f40d67f5bb8e81f09a5a12c840ec3ce9a7f3b181be188ef711a1e"
+        , "984ce172b9216f419f445367456d5619314a42a3da86b001387bfdb80e0cfe42"
+        , "d2aefa0deaa5c151bf0adb6c01f2a5adc0fd581259f9a2aadcf20f8fd566a26b"
+        , "5032ec38bbc5da98ee0c6f568b872a65a08abf251deb21bb4b56e5d8821e68aa"
+        ])
+  , V "TC1" ChaCha12
+      "0000000000000000000000000000000000000000000000000000000000000000"
+      "0000000000000000"
+      (mconcat [
+          "9bf49a6a0755f953811fce125f2683d50429c3bb49e074147e0089a52eae155f"
+        , "0564f879d27ae3c02ce82834acfa8c793a629f2ca0de6919610be82f411326be"
+        , "0bd58841203e74fe86fc71338ce0173dc628ebb719bdcbcc151585214cc089b4"
+        , "42258dcda14cf111c602b8971b8cc843e91e46ca905151c02744a6b017e69316"
+        ])
+  , V "TC1" ChaCha20
+      "0000000000000000000000000000000000000000000000000000000000000000"
+      "0000000000000000"
+      (mconcat [
+          "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7"
+        , "da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"
+        , "9f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed"
+        , "29b721769ce64e43d57133b074d839d531ed1f28510afb45ace10a1f4b794d6f"
+        ])
+  , V "TC2" ChaCha8
+      "0100000000000000000000000000000000000000000000000000000000000000"
+      "0000000000000000"
+      (mconcat [
+          "cf5ee9a0494aa9613e05d5ed725b804b12f4a465ee635acc3a311de8740489ea"
+        , "289d04f43c7518db56eb4433e498a1238cd8464d3763ddbb9222ee3bd8fae3c8"
+        , "b4355a7d93dd8867089ee643558b95754efa2bd1a8a1e2d75bcdb32015542638"
+        , "291941feb49965587c4fdfe219cf0ec132a6cd4dc067392e67982fe53278c0b4"
+        ])
+  , V "TC2" ChaCha12
+      "0100000000000000000000000000000000000000000000000000000000000000"
+      "0000000000000000"
+      (mconcat [
+          "12056e595d56b0f6eef090f0cd25a20949248c2790525d0f930218ff0b4ddd10"
+        , "a6002239d9a454e29e107a7d06fefdfef0210feba044f9f29b1772c960dc29c0"
+        , "0c7366c5cbc604240e665eb02a69372a7af979b26fbb78092ac7c4b88029a7c8"
+        , "54513bc217bbfc7d90432e308eba15afc65aeb48ef100d5601e6afba257117a9"
+        ])
+  , V "TC2" ChaCha20
+      "0100000000000000000000000000000000000000000000000000000000000000"
+      "0000000000000000"
+      (mconcat [
+          "c5d30a7ce1ec119378c84f487d775a8542f13ece238a9455e8229e888de85bbd"
+        , "29eb63d0a17a5b999b52da22be4023eb07620a54f6fa6ad8737b71eb0464dac0"
+        , "10f656e6d1fd55053e50c4875c9930a33f6d0263bd14dfd6ab8c70521c19338b"
+        , "2308b95cf8d0bb7d202d2102780ea3528f1cb48560f76b20f382b942500fceac"
+        ])
+  , V "TC3" ChaCha8
+      "0000000000000000000000000000000000000000000000000000000000000000"
+      "0100000000000000"
+      (mconcat [
+          "2b8f4bb3798306ca5130d47c4f8d4ed13aa0edccc1be6942090faeeca0d7599b"
+        , "7ff0fe616bb25aa0153ad6fdc88b954903c22426d478b97b22b8f9b1db00cf06"
+        , "470bdffbc488a8b7c701ebf4061d75c5969186497c95367809afa80bd843b040"
+        , "a79abc6e73a91757f1db73c8eacfa543b38f289d065ab2f3032d377b8c37fe46"
+        ])
+  , V "TC3" ChaCha12
+      "0000000000000000000000000000000000000000000000000000000000000000"
+      "0100000000000000"
+      (mconcat [
+          "64b8bdf87b828c4b6dbaf7ef698de03df8b33f635714418f9836ade59be12969"
+        , "46c953a0f38ecffc9ecb98e81d5d99a5edfc8f9a0a45b9e41ef3b31f028f1d0f"
+        , "559db4a7f222c442fe23b9a2596a88285122ee4f1363896ea77ca150912ac723"
+        , "bff04b026a2f807e03b29c02077d7b06fc1ab9827c13c8013a6d83bd3b52a26f"
+        ])
+  , V "TC3" ChaCha20
+      "0000000000000000000000000000000000000000000000000000000000000000"
+      "0100000000000000"
+      (mconcat [
+          "ef3fdfd6c61578fbf5cf35bd3dd33b8009631634d21e42ac33960bd138e50d32"
+        , "111e4caf237ee53ca8ad6426194a88545ddc497a0b466e7d6bbdb0041b2f586b"
+        , "5305e5e44aff19b235936144675efbe4409eb7e8e5f1430f5f5836aeb49bb532"
+        , "8b017c4b9dc11f8a03863fa803dc71d5726b2b6b31aa32708afe5af1d6b69058"
+        ])
+  , V "TC4" ChaCha8
+      "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+      "ffffffffffffffff"
+      (mconcat [
+          "e163bbf8c9a739d18925ee8362dad2cdc973df05225afb2aa26396f2a9849a4a"
+        , "445e0547d31c1623c537df4ba85c70a9884a35bcbf3dfab077e98b0f68135f54"
+        , "81d4933f8b322ac0cd762c27235ce2b31534e0244a9a2f1fd5e94498d47ff108"
+        , "790c009cf9e1a348032a7694cb28024cd96d3498361edb1785af752d187ab54b"
+        ])
+  , V "TC4" ChaCha12
+      "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+      "ffffffffffffffff"
+      (mconcat [
+          "04bf88dae8e47a228fa47b7e6379434ba664a7d28f4dab84e5f8b464add20c3a"
+        , "caa69c5ab221a23a57eb5f345c96f4d1322d0a2ff7a9cd43401cd536639a615a"
+        , "5c9429b55ca3c1b55354559669a154aca46cd761c41ab8ace385363b95675f06"
+        , "8e18db5a673c11291bd4187892a9a3a33514f3712b26c13026103298ed76bc9a"
+        ])
+  , V "TC4" ChaCha20
+      "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+      "ffffffffffffffff"
+      (mconcat [
+          "d9bf3f6bce6ed0b54254557767fb57443dd4778911b606055c39cc25e674b836"
+        , "3feabc57fde54f790c52c8ae43240b79d49042b777bfd6cb80e931270b7f50eb"
+        , "5bac2acd86a836c5dc98c116c1217ec31d3a63a9451319f097f3b4d6dab07787"
+        , "19477d24d24b403a12241d7cca064f790f1d51ccaff6b1667d4bbca1958c4306"
+        ])
+  , V "TC5" ChaCha8
+      "5555555555555555555555555555555555555555555555555555555555555555"
+      "5555555555555555"
+      (mconcat [
+          "7cb78214e4d3465b6dc62cf7a1538c88996952b4fb72cb6105f1243ce3442e29"
+        , "75a59ebcd2b2a598290d7538491fe65bdbfefd060d88798120a70d049dc2677d"
+        , "d48ff5a2513e497a5d54802d7484c4f1083944d8d0d14d6482ce09f7e5ebf20b"
+        , "29807d62c31874d02f5d3cc85381a745ecbc60525205e300a76961bfe51ac07c"
+        ])
+  , V "TC5" ChaCha12
+      "5555555555555555555555555555555555555555555555555555555555555555"
+      "5555555555555555"
+      (mconcat [
+          "a600f07727ff93f3da00dd74cc3e8bfb5ca7302f6a0a2944953de00450eecd40"
+        , "b860f66049f2eaed63b2ef39cc310d2c488f5d9a241b615dc0ab70f921b91b95"
+        , "140eff4aa495ac61289b6bc57de072419d09daa7a7243990daf348a8f2831e59"
+        , "7cf379b3b284f00bda27a4c68085374a8a5c38ded62d1141cae0bb838ddc2232"
+        ])
+  , V "TC5" ChaCha20
+      "5555555555555555555555555555555555555555555555555555555555555555"
+      "5555555555555555"
+      (mconcat [
+          "bea9411aa453c5434a5ae8c92862f564396855a9ea6e22d6d3b50ae1b3663311"
+        , "a4a3606c671d605ce16c3aece8e61ea145c59775017bee2fa6f88afc758069f7"
+        , "e0b8f676e644216f4d2a3422d7fa36c6c4931aca950e9da42788e6d0b6d1cd83"
+        , "8ef652e97b145b14871eae6c6804c7004db5ac2fce4c68c726d004b10fcaba86"
+        ])
+  , V "TC6" ChaCha8
+      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+      "aaaaaaaaaaaaaaaa"
+      (mconcat [
+          "40f9ab86c8f9a1a0cdc05a75e5531b612d71ef7f0cf9e387df6ed6972f0aae21"
+        , "311aa581f816c90e8a99de990b6b95aac92450f4e112712667b804c99e9c6eda"
+        , "f8d144f560c8c0ea36880d3b77874c9a9103d147f6ded386284801a4ee158e5e"
+        , "a4f9c093fc55fd344c33349dc5b699e21dc83b4296f92ee3ecabf3d51f95fe3f"
+        ])
+  , V "TC6" ChaCha12
+      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+      "aaaaaaaaaaaaaaaa"
+      (mconcat [
+          "856505b01d3b47aae03d6a97aa0f033a9adcc94377babd8608864fb3f625b6e3"
+        , "14f086158f9f725d811eeb953b7f747076e4c3f639fa841fad6c9a709e621397"
+        , "6dd6ee9b5e1e2e676b1c9e2b82c2e96c1648437bff2f0126b74e8ce0a9b06d17"
+        , "20ac0b6f09086f28bc201587f0535ed9385270d08b4a9382f18f82dbde18210e"
+        ])
+  , V "TC6" ChaCha20
+      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+      "aaaaaaaaaaaaaaaa"
+      (mconcat [
+          "9aa2a9f656efde5aa7591c5fed4b35aea2895dec7cb4543b9e9f21f5e7bcbcf3"
+        , "c43c748a970888f8248393a09d43e0b7e164bc4d0b0fb240a2d72115c4808906"
+        , "72184489440545d021d97ef6b693dfe5b2c132d47e6f041c9063651f96b623e6"
+        , "2a11999a23b6f7c461b2153026ad5e866a2e597ed07b8401dec63a0934c6b2a9"
+        ])
+  , V "TC7" ChaCha8
+      "00112233445566778899aabbccddeeffffeeddccbbaa99887766554433221100"
+      "0f1e2d3c4b5a6978"
+      (mconcat [
+          "db43ad9d1e842d1272e4530e276b3f568f8859b3f7cf6d9d2c74fa53808cb515"
+        , "7a8ebf46ad3dcc4b6c7dadde131784b0120e0e22f6d5f9ffa7407d4a21b695d9"
+        , "c5dd30bf55612fab9bdd118920c19816470c7f5dcd42325dbbed8c57a56281c1"
+        , "44cb0f03e81b3004624e0650a1ce5afaf9a7cd8163f6dbd72602257dd96e471e"
+        ])
+  , V "TC7" ChaCha12
+      "00112233445566778899aabbccddeeffffeeddccbbaa99887766554433221100"
+      "0f1e2d3c4b5a6978"
+      (mconcat [
+          "7ed12a3a63912ae941ba6d4c0d5e862e568b0e5589346935505f064b8c2698db"
+        , "f7d850667d8e67be639f3b4f6a16f92e65ea80f6c7429445da1fc2c1b9365040"
+        , "e32e50c4106f3b3da1ce7ccb1e7140b153493c0f3ad9a9bcff077ec4596f1d0f"
+        , "29bf9cbaa502820f732af5a93c49eee33d1c4f12af3b4297af91fe41ea9e94a2"
+        ])
+  , V "TC7" ChaCha20
+      "00112233445566778899aabbccddeeffffeeddccbbaa99887766554433221100"
+      "0f1e2d3c4b5a6978"
+      (mconcat [
+          "9fadf409c00811d00431d67efbd88fba59218d5d6708b1d685863fabbb0e961e"
+        , "ea480fd6fb532bfd494b2151015057423ab60a63fe4f55f7a212e2167ccab931"
+        , "fbfd29cf7bc1d279eddf25dd316bb8843d6edee0bd1ef121d12fa17cbc2c574c"
+        , "ccab5e275167b08bd686f8a09df87ec3ffb35361b94ebfa13fec0e4889d18da5"
+        ])
+  , V "TC8" ChaCha8
+      "c46ec1b18ce8a878725a37e780dfb7351f68ed2e194c79fbc6aebee1a667975d"
+      "1ada31d5cf688221"
+      (mconcat [
+          "838751b42d8ddd8a3d77f48825a2ba752cf4047cb308a5978ef274973be374c9"
+        , "6ad848065871417b08f034e681fe46a93f7d5c61d1306614d4aaf257a7cff08b"
+        , "16f2fda170cc18a4b58a2667ed962774af792a6e7f3c77992540711a7a136d7e"
+        , "8a2f8d3f93816709d45a3fa5f8ce72fde15be7b841acba3a2abd557228d9fe4f"
+        ])
+  , V "TC8" ChaCha12
+      "c46ec1b18ce8a878725a37e780dfb7351f68ed2e194c79fbc6aebee1a667975d"
+      "1ada31d5cf688221"
+      (mconcat [
+          "1482072784bc6d06b4e73bdc118bc0103c7976786ca918e06986aa251f7e9cc1"
+        , "b2749a0a16ee83b4242d2e99b08d7c20092b80bc466c87283b61b1b39d0ffbab"
+        , "d94b116bc1ebdb329b9e4f620db695544a8e3d9b68473d0c975a46ad966ed631"
+        , "e42aff530ad5eac7d8047adfa1e5113c91f3e3b883f1d189ac1c8fe07ba5a42b"
+        ])
+  , V "TC8" ChaCha20
+      "c46ec1b18ce8a878725a37e780dfb7351f68ed2e194c79fbc6aebee1a667975d"
+      "1ada31d5cf688221"
+      (mconcat [
+          "f63a89b75c2271f9368816542ba52f06ed49241792302b00b5e8f80ae9a473af"
+        , "c25b218f519af0fdd406362e8d69de7f54c604a6e00f353f110f771bdca8ab92"
+        , "e5fbc34e60a1d9a9db17345b0a402736853bf910b060bdf1f897b6290f01d138"
+        , "ae2c4c90225ba9ea14d518f55929dea098ca7a6ccfe61227053c84e49a4a3332"
+        ])
+  ]
