chacha

The ChaCha20 stream cipher (docs.ppad.tech/chacha).
git clone git://git.ppad.tech/chacha.git
Log | Files | Refs | LICENSE

commit c8020aeca19443c657a234640944b6195bb4635d
parent 87c9dca265c7ff26e2a5aa0767d1780d21a50fc8
Author: Jared Tobin <jared@jtobin.io>
Date:   Fri,  7 Mar 2025 16:11:41 +0400

lib: some renaming

(I'm growing to love underscores.)

Diffstat:
Dlib/Crypto/Cipher/ChaCha.hs | 300-------------------------------------------------------------------------------
Alib/Crypto/Cipher/ChaCha20.hs | 335+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mppad-chacha.cabal | 6+++---
Mtest/Main.hs | 20++++++++++----------
4 files changed, 348 insertions(+), 313 deletions(-)

diff --git a/lib/Crypto/Cipher/ChaCha.hs b/lib/Crypto/Cipher/ChaCha.hs @@ -1,300 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE MagicHash #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE UnboxedTuples #-} - -module Crypto.Cipher.ChaCha where - -import qualified Data.Bits as B -import Data.Bits ((.|.), (.<<.), (.^.)) -import qualified Data.ByteString as BS -import qualified Data.ByteString.Builder as BSB -import qualified Data.ByteString.Internal as BI -import qualified Data.ByteString.Unsafe as BU -import Control.Monad.Primitive (PrimMonad, PrimState) -import Data.Foldable (for_) -import qualified Data.Primitive.PrimArray as PA -import Foreign.ForeignPtr -import GHC.Exts -import GHC.Word - --- utils ---------------------------------------------------------------------- - --- keystroke saver -fi :: (Integral a, Num b) => a -> b -fi = fromIntegral -{-# INLINE fi #-} - --- parse strict ByteString in LE order to Word32 (verbatim from --- Data.Binary) -unsafe_word32le :: BS.ByteString -> Word32 -unsafe_word32le s = - (fi (s `BU.unsafeIndex` 3) `B.unsafeShiftL` 24) .|. - (fi (s `BU.unsafeIndex` 2) `B.unsafeShiftL` 16) .|. - (fi (s `BU.unsafeIndex` 1) `B.unsafeShiftL` 8) .|. - (fi (s `BU.unsafeIndex` 0)) -{-# INLINE unsafe_word32le #-} - -data WSPair = WSPair - {-# UNPACK #-} !Word32 - {-# UNPACK #-} !BS.ByteString - --- variant of Data.ByteString.splitAt that behaves like an incremental --- Word32 parser -unsafe_parseWsPair :: BS.ByteString -> WSPair -unsafe_parseWsPair (BI.BS x l) = - WSPair (unsafe_word32le (BI.BS x 4)) (BI.BS (plusForeignPtr x 4) (l - 4)) -{-# INLINE unsafe_parseWsPair #-} - --- chacha quarter round ------------------------------------------------------- - --- RFC8439 2.2 -quarter - :: PrimMonad m - => ChaCha (PrimState m) - -> Int - -> Int - -> Int - -> Int - -> m () -quarter (ChaCha m) i0 i1 i2 i3 = do - !(W32# a) <- PA.readPrimArray m i0 - !(W32# b) <- PA.readPrimArray m i1 - !(W32# c) <- PA.readPrimArray m i2 - !(W32# d) <- PA.readPrimArray m i3 - - let !(# a1, b1, c1, d1 #) = quarter# a b c d - - PA.writePrimArray m i0 (W32# a1) - PA.writePrimArray m i1 (W32# b1) - PA.writePrimArray m i2 (W32# c1) - PA.writePrimArray m i3 (W32# d1) - --- for easy testing -quarter' - :: Word32 -> Word32 -> Word32 -> Word32 -> (Word32, Word32, Word32, Word32) -quarter' (W32# a) (W32# b) (W32# c) (W32# d) = - let !(# a', b', c', d' #) = quarter# a b c d - in (W32# a', W32# b', W32# c', W32# d') -{-# INLINE quarter' #-} - --- RFC8439 2.1 -quarter# - :: Word32# -> Word32# -> Word32# -> Word32# - -> (# Word32#, Word32#, Word32#, Word32# #) -quarter# a b c d = - let a0 = plusWord32# a b - d0 = xorWord32# d a0 - d1 = rotateL# d0 16# - - c0 = plusWord32# c d1 - b0 = xorWord32# b c0 - b1 = rotateL# b0 12# - - a1 = plusWord32# a0 b1 - d2 = xorWord32# d1 a1 - d3 = rotateL# d2 8# - - c1 = plusWord32# c0 d3 - b2 = xorWord32# b1 c1 - b3 = rotateL# b2 7# - - in (# a1, b3, c1, d3 #) -{-# INLINE quarter# #-} - -rotateL# :: Word32# -> Int# -> Word32# -rotateL# w i - | isTrue# (i ==# 0#) = w - | otherwise = wordToWord32# ( - ((word32ToWord# w) `uncheckedShiftL#` i) - `or#` ((word32ToWord# w) `uncheckedShiftRL#` (32# -# i))) -{-# INLINE rotateL# #-} - --- key and nonce parsing ------------------------------------------------------ - -data Key = Key { - k0 :: {-# UNPACK #-} !Word32 - , k1 :: {-# UNPACK #-} !Word32 - , k2 :: {-# UNPACK #-} !Word32 - , k3 :: {-# UNPACK #-} !Word32 - , k4 :: {-# UNPACK #-} !Word32 - , k5 :: {-# UNPACK #-} !Word32 - , k6 :: {-# UNPACK #-} !Word32 - , k7 :: {-# UNPACK #-} !Word32 - } - deriving (Eq, Show) - --- parse strict 256-bit bytestring (length unchecked) to key -parse_key :: BS.ByteString -> Key -parse_key bs = - let !(WSPair k0 t0) = unsafe_parseWsPair bs - !(WSPair k1 t1) = unsafe_parseWsPair t0 - !(WSPair k2 t2) = unsafe_parseWsPair t1 - !(WSPair k3 t3) = unsafe_parseWsPair t2 - !(WSPair k4 t4) = unsafe_parseWsPair t3 - !(WSPair k5 t5) = unsafe_parseWsPair t4 - !(WSPair k6 t6) = unsafe_parseWsPair t5 - !(WSPair k7 t7) = unsafe_parseWsPair t6 - in if BS.null t7 - then Key {..} - else error "ppad-chacha (parse_key): bytes remaining" - -data Nonce = Nonce { - n0 :: {-# UNPACK #-} !Word32 - , n1 :: {-# UNPACK #-} !Word32 - , n2 :: {-# UNPACK #-} !Word32 - } - deriving (Eq, Show) - --- parse strict 96-bit bytestring (length unchecked) to nonce -parse_nonce :: BS.ByteString -> Nonce -parse_nonce bs = - let !(WSPair n0 t0) = unsafe_parseWsPair bs - !(WSPair n1 t1) = unsafe_parseWsPair t0 - !(WSPair n2 t2) = unsafe_parseWsPair t1 - in if BS.null t2 - then Nonce {..} - else error "ppad-chacha (parse_nonce): bytes remaining" - --- chacha20 block function ---------------------------------------------------- - -newtype ChaCha s = ChaCha (PA.MutablePrimArray s Word32) - deriving Eq - -chacha - :: PrimMonad m - => Key - -> Word32 - -> Nonce - -> m (ChaCha (PrimState m)) -chacha key counter nonce = do - state <- _chacha_alloc - _chacha_set state key counter nonce - pure state - --- allocate a new chacha state -_chacha_alloc :: PrimMonad m => m (ChaCha (PrimState m)) -_chacha_alloc = fmap ChaCha (PA.newPrimArray 16) -{-# INLINE _chacha_alloc #-} - --- XX can be optimised more (only change counter) - --- set the values of a chacha state -_chacha_set - :: PrimMonad m - => ChaCha (PrimState m) - -> Key - -> Word32 - -> Nonce - -> m () -_chacha_set (ChaCha arr) Key {..} counter Nonce {..}= do - PA.writePrimArray arr 00 0x61707865 - PA.writePrimArray arr 01 0x3320646e - PA.writePrimArray arr 02 0x79622d32 - PA.writePrimArray arr 03 0x6b206574 - PA.writePrimArray arr 04 k0 - PA.writePrimArray arr 05 k1 - PA.writePrimArray arr 06 k2 - PA.writePrimArray arr 07 k3 - PA.writePrimArray arr 08 k4 - PA.writePrimArray arr 09 k5 - PA.writePrimArray arr 10 k6 - PA.writePrimArray arr 11 k7 - PA.writePrimArray arr 12 counter - PA.writePrimArray arr 13 n0 - PA.writePrimArray arr 14 n1 - PA.writePrimArray arr 15 n2 -{-# INLINEABLE _chacha_set #-} - -_chacha_counter - :: PrimMonad m - => ChaCha (PrimState m) - -> Word32 - -> m () -_chacha_counter (ChaCha arr) counter = - PA.writePrimArray arr 12 counter - --- two full rounds (eight quarter rounds) -rounds :: PrimMonad m => ChaCha (PrimState m) -> m () -rounds state = do - quarter state 00 04 08 12 - quarter state 01 05 09 13 - quarter state 02 06 10 14 - quarter state 03 07 11 15 - quarter state 00 05 10 15 - quarter state 01 06 11 12 - quarter state 02 07 08 13 - quarter state 03 04 09 14 -{-# INLINEABLE rounds #-} - -_block - :: PrimMonad m - => ChaCha (PrimState m) - -> Word32 - -> m BS.ByteString -_block state@(ChaCha s) counter = do - _chacha_counter state counter - i <- PA.freezePrimArray s 0 16 - for_ [1..10 :: Int] (const (rounds state)) - for_ [0..15 :: Int] $ \idx -> do - let iv = PA.indexPrimArray i idx - sv <- PA.readPrimArray s idx - PA.writePrimArray s idx (iv + sv) - serialize state - -serialize :: PrimMonad m => ChaCha (PrimState m) -> m BS.ByteString -serialize (ChaCha m) = do - w64_0 <- w64 <$> PA.readPrimArray m 00 <*> PA.readPrimArray m 01 - w64_1 <- w64 <$> PA.readPrimArray m 02 <*> PA.readPrimArray m 03 - w64_2 <- w64 <$> PA.readPrimArray m 04 <*> PA.readPrimArray m 05 - w64_3 <- w64 <$> PA.readPrimArray m 06 <*> PA.readPrimArray m 07 - w64_4 <- w64 <$> PA.readPrimArray m 08 <*> PA.readPrimArray m 09 - w64_5 <- w64 <$> PA.readPrimArray m 10 <*> PA.readPrimArray m 11 - w64_6 <- w64 <$> PA.readPrimArray m 12 <*> PA.readPrimArray m 13 - w64_7 <- w64 <$> PA.readPrimArray m 14 <*> PA.readPrimArray m 15 - pure . BS.toStrict . BSB.toLazyByteString . mconcat $ - [w64_0, w64_1, w64_2, w64_3, w64_4, w64_5, w64_6, w64_7] - where - w64 a b = BSB.word64LE (fi a .|. (fi b .<<. 32)) - --- chacha20 encryption -------------------------------------------------------- - -encrypt - :: PrimMonad m - => BS.ByteString - -> Word32 - -> BS.ByteString - -> BS.ByteString - -> m BS.ByteString -encrypt raw_key@(BI.PS _ _ kl) counter raw_nonce@(BI.PS _ _ nl) plaintext - | kl /= 32 = error "ppad-chacha (encrypt): invalid key" - | nl /= 12 = error "ppad-chacha (encrypt): invalid nonce" - | otherwise = do - let key = parse_key raw_key - non = parse_nonce raw_nonce - _encrypt key counter non plaintext - -_encrypt - :: PrimMonad m - => Key - -> Word32 - -> Nonce - -> BS.ByteString - -> m BS.ByteString -_encrypt key counter nonce plaintext = do - ChaCha initial <- chacha key counter nonce - state@(ChaCha s) <- _chacha_alloc - - let loop acc !j bs = case BS.splitAt 64 bs of - (chunk@(BI.PS _ _ l), etc) - | l == 0 && BS.length etc == 0 -> pure $ - BS.toStrict (BSB.toLazyByteString acc) - | otherwise -> do - PA.copyMutablePrimArray s 0 initial 0 16 - stream <- _block state j - let cip = BS.packZipWith (.^.) chunk stream - loop (acc <> BSB.byteString cip) (j + 1) etc - - loop mempty counter plaintext -{-# INLINE _encrypt #-} - diff --git a/lib/Crypto/Cipher/ChaCha20.hs b/lib/Crypto/Cipher/ChaCha20.hs @@ -0,0 +1,335 @@ +{-# OPTIONS_HADDOCK prune #-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE MagicHash #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE UnboxedTuples #-} + +module Crypto.Cipher.ChaCha20 ( + -- * ChaCha20 block function + block + + -- * ChaCha20 stream cipher + , encrypt + + -- testing + , ChaCha(..) + , _chacha + , _parse_key + , _parse_nonce + , _quarter + , _quarter_pure + , _rounds + ) where + +import qualified Data.Bits as B +import Data.Bits ((.|.), (.<<.), (.^.)) +import qualified Data.ByteString as BS +import qualified Data.ByteString.Builder as BSB +import qualified Data.ByteString.Internal as BI +import qualified Data.ByteString.Unsafe as BU +import Control.Monad.Primitive (PrimMonad, PrimState) +import Data.Foldable (for_) +import qualified Data.Primitive.PrimArray as PA +import Foreign.ForeignPtr +import GHC.Exts +import GHC.Word + +-- utils ---------------------------------------------------------------------- + +-- keystroke saver +fi :: (Integral a, Num b) => a -> b +fi = fromIntegral +{-# INLINE fi #-} + +-- parse strict ByteString in LE order to Word32 (verbatim from +-- Data.Binary) +unsafe_word32le :: BS.ByteString -> Word32 +unsafe_word32le s = + (fi (s `BU.unsafeIndex` 3) `B.unsafeShiftL` 24) .|. + (fi (s `BU.unsafeIndex` 2) `B.unsafeShiftL` 16) .|. + (fi (s `BU.unsafeIndex` 1) `B.unsafeShiftL` 8) .|. + (fi (s `BU.unsafeIndex` 0)) +{-# INLINE unsafe_word32le #-} + +data WSPair = WSPair + {-# UNPACK #-} !Word32 + {-# UNPACK #-} !BS.ByteString + +-- variant of Data.ByteString.splitAt that behaves like an incremental +-- Word32 parser +unsafe_parseWsPair :: BS.ByteString -> WSPair +unsafe_parseWsPair (BI.BS x l) = + WSPair (unsafe_word32le (BI.BS x 4)) (BI.BS (plusForeignPtr x 4) (l - 4)) +{-# INLINE unsafe_parseWsPair #-} + +-- chacha quarter round ------------------------------------------------------- + +-- RFC8439 2.2 +_quarter + :: PrimMonad m + => ChaCha (PrimState m) + -> Int + -> Int + -> Int + -> Int + -> m () +_quarter (ChaCha m) i0 i1 i2 i3 = do + !(W32# a) <- PA.readPrimArray m i0 + !(W32# b) <- PA.readPrimArray m i1 + !(W32# c) <- PA.readPrimArray m i2 + !(W32# d) <- PA.readPrimArray m i3 + + let !(# a1, b1, c1, d1 #) = quarter# a b c d + + PA.writePrimArray m i0 (W32# a1) + PA.writePrimArray m i1 (W32# b1) + PA.writePrimArray m i2 (W32# c1) + PA.writePrimArray m i3 (W32# d1) +{-# INLINEABLE _quarter #-} + +_quarter_pure + :: Word32 -> Word32 -> Word32 -> Word32 -> (Word32, Word32, Word32, Word32) +_quarter_pure (W32# a) (W32# b) (W32# c) (W32# d) = + let !(# a', b', c', d' #) = quarter# a b c d + in (W32# a', W32# b', W32# c', W32# d') +{-# INLINE _quarter_pure #-} + +-- RFC8439 2.1 +quarter# + :: Word32# -> Word32# -> Word32# -> Word32# + -> (# Word32#, Word32#, Word32#, Word32# #) +quarter# a b c d = + let a0 = plusWord32# a b + d0 = xorWord32# d a0 + d1 = rotateL# d0 16# + + c0 = plusWord32# c d1 + b0 = xorWord32# b c0 + b1 = rotateL# b0 12# + + a1 = plusWord32# a0 b1 + d2 = xorWord32# d1 a1 + d3 = rotateL# d2 8# + + c1 = plusWord32# c0 d3 + b2 = xorWord32# b1 c1 + b3 = rotateL# b2 7# + + in (# a1, b3, c1, d3 #) +{-# INLINE quarter# #-} + +rotateL# :: Word32# -> Int# -> Word32# +rotateL# w i + | isTrue# (i ==# 0#) = w + | otherwise = wordToWord32# ( + ((word32ToWord# w) `uncheckedShiftL#` i) + `or#` ((word32ToWord# w) `uncheckedShiftRL#` (32# -# i))) +{-# INLINE rotateL# #-} + +-- key and nonce parsing ------------------------------------------------------ + +data Key = Key { + k0 :: {-# UNPACK #-} !Word32 + , k1 :: {-# UNPACK #-} !Word32 + , k2 :: {-# UNPACK #-} !Word32 + , k3 :: {-# UNPACK #-} !Word32 + , k4 :: {-# UNPACK #-} !Word32 + , k5 :: {-# UNPACK #-} !Word32 + , k6 :: {-# UNPACK #-} !Word32 + , k7 :: {-# UNPACK #-} !Word32 + } + deriving (Eq, Show) + +-- parse strict 256-bit bytestring (length unchecked) to key +_parse_key :: BS.ByteString -> Key +_parse_key bs = + let !(WSPair k0 t0) = unsafe_parseWsPair bs + !(WSPair k1 t1) = unsafe_parseWsPair t0 + !(WSPair k2 t2) = unsafe_parseWsPair t1 + !(WSPair k3 t3) = unsafe_parseWsPair t2 + !(WSPair k4 t4) = unsafe_parseWsPair t3 + !(WSPair k5 t5) = unsafe_parseWsPair t4 + !(WSPair k6 t6) = unsafe_parseWsPair t5 + !(WSPair k7 t7) = unsafe_parseWsPair t6 + in if BS.null t7 + then Key {..} + else error "ppad-chacha (_parse_key): bytes remaining" + +data Nonce = Nonce { + n0 :: {-# UNPACK #-} !Word32 + , n1 :: {-# UNPACK #-} !Word32 + , n2 :: {-# UNPACK #-} !Word32 + } + deriving (Eq, Show) + +-- parse strict 96-bit bytestring (length unchecked) to nonce +_parse_nonce :: BS.ByteString -> Nonce +_parse_nonce bs = + let !(WSPair n0 t0) = unsafe_parseWsPair bs + !(WSPair n1 t1) = unsafe_parseWsPair t0 + !(WSPair n2 t2) = unsafe_parseWsPair t1 + in if BS.null t2 + then Nonce {..} + else error "ppad-chacha (_parse_nonce): bytes remaining" + +-- chacha20 block function ---------------------------------------------------- + +newtype ChaCha s = ChaCha (PA.MutablePrimArray s Word32) + deriving Eq + +_chacha + :: PrimMonad m + => Key + -> Word32 + -> Nonce + -> m (ChaCha (PrimState m)) +_chacha key counter nonce = do + state <- _chacha_alloc + _chacha_set state key counter nonce + pure state + +-- allocate a new chacha state +_chacha_alloc :: PrimMonad m => m (ChaCha (PrimState m)) +_chacha_alloc = fmap ChaCha (PA.newPrimArray 16) +{-# INLINE _chacha_alloc #-} + +-- set the values of a chacha state +_chacha_set + :: PrimMonad m + => ChaCha (PrimState m) + -> Key + -> Word32 + -> Nonce + -> m () +_chacha_set (ChaCha arr) Key {..} counter Nonce {..}= do + PA.writePrimArray arr 00 0x61707865 + PA.writePrimArray arr 01 0x3320646e + PA.writePrimArray arr 02 0x79622d32 + PA.writePrimArray arr 03 0x6b206574 + PA.writePrimArray arr 04 k0 + PA.writePrimArray arr 05 k1 + PA.writePrimArray arr 06 k2 + PA.writePrimArray arr 07 k3 + PA.writePrimArray arr 08 k4 + PA.writePrimArray arr 09 k5 + PA.writePrimArray arr 10 k6 + PA.writePrimArray arr 11 k7 + PA.writePrimArray arr 12 counter + PA.writePrimArray arr 13 n0 + PA.writePrimArray arr 14 n1 + PA.writePrimArray arr 15 n2 +{-# INLINEABLE _chacha_set #-} + +_chacha_counter + :: PrimMonad m + => ChaCha (PrimState m) + -> Word32 + -> m () +_chacha_counter (ChaCha arr) counter = + PA.writePrimArray arr 12 counter + +-- two full rounds (eight quarter rounds) +_rounds :: PrimMonad m => ChaCha (PrimState m) -> m () +_rounds state = do + _quarter state 00 04 08 12 + _quarter state 01 05 09 13 + _quarter state 02 06 10 14 + _quarter state 03 07 11 15 + _quarter state 00 05 10 15 + _quarter state 01 06 11 12 + _quarter state 02 07 08 13 + _quarter state 03 04 09 14 +{-# INLINEABLE _rounds #-} + +_block + :: PrimMonad m + => ChaCha (PrimState m) + -> Word32 + -> m BS.ByteString +_block state@(ChaCha s) counter = do + _chacha_counter state counter + i <- PA.freezePrimArray s 0 16 + for_ [1..10 :: Int] (const (_rounds state)) + for_ [0..15 :: Int] $ \idx -> do + let iv = PA.indexPrimArray i idx + sv <- PA.readPrimArray s idx + PA.writePrimArray s idx (iv + sv) + serialize state + +block + :: PrimMonad m + => BS.ByteString + -> Word32 + -> BS.ByteString + -> m BS.ByteString +block key@(BI.PS _ _ kl) counter nonce@(BI.PS _ _ nl) + | kl /= 32 = error "ppad-chacha (block): invalid key" + | nl /= 12 = error "ppad-chacha (block): invalid nonce" + | otherwise = do + let k = _parse_key key + n = _parse_nonce nonce + state@(ChaCha s) <- _chacha k counter n + i <- PA.freezePrimArray s 0 16 + for_ [1..10 :: Int] (const (_rounds state)) + for_ [0..15 :: Int] $ \idx -> do + let iv = PA.indexPrimArray i idx + sv <- PA.readPrimArray s idx + PA.writePrimArray s idx (iv + sv) + serialize state + +serialize :: PrimMonad m => ChaCha (PrimState m) -> m BS.ByteString +serialize (ChaCha m) = do + w64_0 <- w64 <$> PA.readPrimArray m 00 <*> PA.readPrimArray m 01 + w64_1 <- w64 <$> PA.readPrimArray m 02 <*> PA.readPrimArray m 03 + w64_2 <- w64 <$> PA.readPrimArray m 04 <*> PA.readPrimArray m 05 + w64_3 <- w64 <$> PA.readPrimArray m 06 <*> PA.readPrimArray m 07 + w64_4 <- w64 <$> PA.readPrimArray m 08 <*> PA.readPrimArray m 09 + w64_5 <- w64 <$> PA.readPrimArray m 10 <*> PA.readPrimArray m 11 + w64_6 <- w64 <$> PA.readPrimArray m 12 <*> PA.readPrimArray m 13 + w64_7 <- w64 <$> PA.readPrimArray m 14 <*> PA.readPrimArray m 15 + pure . BS.toStrict . BSB.toLazyByteString . mconcat $ + [w64_0, w64_1, w64_2, w64_3, w64_4, w64_5, w64_6, w64_7] + where + w64 a b = BSB.word64LE (fi a .|. (fi b .<<. 32)) + +-- chacha20 encryption -------------------------------------------------------- + +encrypt + :: PrimMonad m + => BS.ByteString + -> Word32 + -> BS.ByteString + -> BS.ByteString + -> m BS.ByteString +encrypt raw_key@(BI.PS _ _ kl) counter raw_nonce@(BI.PS _ _ nl) plaintext + | kl /= 32 = error "ppad-chacha (encrypt): invalid key" + | nl /= 12 = error "ppad-chacha (encrypt): invalid nonce" + | otherwise = do + let key = _parse_key raw_key + non = _parse_nonce raw_nonce + _encrypt key counter non plaintext + +_encrypt + :: PrimMonad m + => Key + -> Word32 + -> Nonce + -> BS.ByteString + -> m BS.ByteString +_encrypt key counter nonce plaintext = do + ChaCha initial <- _chacha key counter nonce + state@(ChaCha s) <- _chacha_alloc + + let loop acc !j bs = case BS.splitAt 64 bs of + (chunk@(BI.PS _ _ l), etc) + | l == 0 && BS.length etc == 0 -> pure $ + BS.toStrict (BSB.toLazyByteString acc) + | otherwise -> do + PA.copyMutablePrimArray s 0 initial 0 16 + stream <- _block state j + let cip = BS.packZipWith (.^.) chunk stream + loop (acc <> BSB.byteString cip) (j + 1) etc + + loop mempty counter plaintext +{-# INLINE _encrypt #-} + diff --git a/ppad-chacha.cabal b/ppad-chacha.cabal @@ -1,7 +1,7 @@ cabal-version: 3.0 name: ppad-chacha version: 0.1.0 -synopsis: A pure ChaCha stream cipher +synopsis: A pure ChaCha20 stream cipher license: MIT license-file: LICENSE author: Jared Tobin @@ -11,7 +11,7 @@ build-type: Simple tested-with: GHC == 9.8.1 extra-doc-files: CHANGELOG description: - A pure ChaCha stream cipher + A pure ChaCha20 stream cipher source-repository head type: git @@ -23,7 +23,7 @@ library ghc-options: -Wall exposed-modules: - Crypto.Cipher.ChaCha + Crypto.Cipher.ChaCha20 build-depends: base >= 4.9 && < 5 , bytestring >= 0.9 && < 0.13 diff --git a/test/Main.hs b/test/Main.hs @@ -5,7 +5,7 @@ module Main where -import qualified Crypto.Cipher.ChaCha as ChaCha +import qualified Crypto.Cipher.ChaCha20 as ChaCha import qualified Data.ByteString as BS import qualified Data.ByteString.Base16 as B16 import Data.Foldable (for_) @@ -27,7 +27,7 @@ main = defaultMain $ testGroup "ppad-chacha" [ quarter :: TestTree quarter = H.testCase "quarter round" $ do let e = (0xea2a92f4, 0xcb1cf8ce, 0x4581472e, 0x5881c4bb) - o = ChaCha.quarter' 0x11111111 0x01020304 0x9b8d6f43 0x01234567 + o = ChaCha._quarter_pure 0x11111111 0x01020304 0x9b8d6f43 0x01234567 H.assertEqual mempty e o quarter_fullstate :: TestTree @@ -41,7 +41,7 @@ quarter_fullstate = H.testCase "quarter round (full chacha state)" $ do ] hot <- PA.unsafeThawPrimArray inp - ChaCha.quarter (ChaCha.ChaCha hot) 2 7 8 13 + ChaCha._quarter (ChaCha.ChaCha hot) 2 7 8 13 o <- PA.unsafeFreezePrimArray hot @@ -64,9 +64,9 @@ block_non = fromJust $ B16.decode "000000090000004a00000000" chacha20_block_init :: TestTree chacha20_block_init = H.testCase "chacha20 state init" $ do - let key = ChaCha.parse_key block_key - non = ChaCha.parse_nonce block_non - ChaCha.ChaCha foo <- ChaCha.chacha key 1 non + let key = ChaCha._parse_key block_key + non = ChaCha._parse_nonce block_non + ChaCha.ChaCha foo <- ChaCha._chacha key 1 non state <- PA.freezePrimArray foo 0 16 let ref = PA.primArrayFromList [ 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574 @@ -78,10 +78,10 @@ chacha20_block_init = H.testCase "chacha20 state init" $ do chacha20_rounds :: TestTree chacha20_rounds = H.testCase "chacha20 20 rounds" $ do - let key = ChaCha.parse_key block_key - non = ChaCha.parse_nonce block_non - state@(ChaCha.ChaCha s) <- ChaCha.chacha key 1 non - for_ [1..10 :: Int] (const (ChaCha.rounds state)) + let key = ChaCha._parse_key block_key + non = ChaCha._parse_nonce block_non + state@(ChaCha.ChaCha s) <- ChaCha._chacha key 1 non + for_ [1..10 :: Int] (const (ChaCha._rounds state)) out <- PA.freezePrimArray s 0 16