ppad-chacha 0.3.1 → 0.3.2
raw patch · 10 files changed
+582/−58 lines, 10 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Crypto.Cipher.ChaCha: ChaCha12 :: Rounds
+ Crypto.Cipher.ChaCha: ChaCha20 :: Rounds
+ Crypto.Cipher.ChaCha: ChaCha8 :: Rounds
+ Crypto.Cipher.ChaCha: CounterOverflow :: Error
+ Crypto.Cipher.ChaCha: InvalidKey :: Error
+ Crypto.Cipher.ChaCha: InvalidNonce :: Error
+ Crypto.Cipher.ChaCha: _keystream_into :: Rounds -> Ptr Word8 -> Word32 -> Ptr Word8 -> Ptr Word8 -> Int -> IO ()
+ Crypto.Cipher.ChaCha: data Error
+ Crypto.Cipher.ChaCha: data Rounds
+ Crypto.Cipher.ChaCha: instance GHC.Classes.Eq Crypto.Cipher.ChaCha.Rounds
+ Crypto.Cipher.ChaCha: instance GHC.Show.Show Crypto.Cipher.ChaCha.Rounds
+ Crypto.Cipher.ChaCha: keystream :: Rounds -> ByteString -> Word32 -> ByteString -> Int -> Either Error ByteString
Files
- CHANGELOG +4/−0
- bench/Main.hs +10/−0
- bench/Weight.hs +12/−0
- cbits/chacha20_native.c +52/−38
- lib/Crypto/Cipher/ChaCha.hs +132/−0
- lib/Crypto/Cipher/ChaCha20.hs +23/−8
- lib/Crypto/Cipher/ChaCha20/Native.hs +27/−10
- ppad-chacha.cabal +4/−2
- test/Main.hs +76/−0
- test/Vectors.hs +242/−0
CHANGELOG view
@@ -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
bench/Main.hs view
@@ -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 ]
bench/Weight.hs view
@@ -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
cbits/chacha20_native.c view
@@ -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) {
+ lib/Crypto/Cipher/ChaCha.hs view
@@ -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
lib/Crypto/Cipher/ChaCha20.hs view
@@ -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 #-}
lib/Crypto/Cipher/ChaCha20/Native.hs view
@@ -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)
ppad-chacha.cabal view
@@ -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
test/Main.hs view
@@ -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
+ test/Vectors.hs view
@@ -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"+ ])+ ]