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:
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