ppad-sha256 0.3.6 → 0.3.7
raw patch · 6 files changed
+89/−1 lines, 6 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Crypto.Hash.SHA256: hash_prefixed :: ByteString -> ByteString -> ByteString
Files
- CHANGELOG +6/−0
- lib/Crypto/Hash/SHA256.hs +19/−0
- lib/Crypto/Hash/SHA256/Native.hs +16/−0
- lib/Crypto/Hash/SHA256/Pure.hs +11/−0
- ppad-sha256.cabal +1/−1
- test/Property.hs +36/−0
CHANGELOG view
@@ -1,5 +1,11 @@ # Changelog +- 0.3.7 (2026-11-13)+ * Adds 'hash_prefixed', which hashes the concatenation of two strict+ bytestrings. When the length of the first is a multiple of the+ 64-byte block size, the second is hashed in place, rather than+ being copied into a new buffer alongside it.+ - 0.3.6 (2026-10-31) * Uses the x86 SHA extensions on x86_64 CPUs that support them, detected at runtime, falling back to the pure Haskell
lib/Crypto/Hash/SHA256.hs view
@@ -18,6 +18,7 @@ module Crypto.Hash.SHA256 ( -- * SHA-256 message digest functions hash+ , hash_prefixed , Lazy.hash_lazy -- * SHA256-based MAC functions@@ -52,6 +53,24 @@ | Native.sha256_native_available = Native.hash m | otherwise = Pure.hash m {-# INLINABLE hash #-}++-- | Compute a condensed representation of the concatenation of two+-- strict bytestrings via SHA-256, i.e. @hash (p <> m)@.+--+-- When the length of the prefix @p@ is a multiple of 64 bytes (the+-- SHA-256 block size), @m@ is hashed in place, rather than being+-- copied into a new buffer. Otherwise both are copied, as for+-- @hash (p <> m)@.+--+-- The 256-bit output digest is returned as a strict bytestring.+--+-- >>> hash_prefixed "64-byte prefix" "strict bytestring input"+-- "<strict 256-bit message digest>"+hash_prefixed :: BS.ByteString -> BS.ByteString -> BS.ByteString+hash_prefixed p m+ | Native.sha256_native_available = Native.hash_prefixed p m+ | otherwise = Pure.hash_prefixed p m+{-# INLINABLE hash_prefixed #-} -- hmac ----------------------------------------------------------------------
lib/Crypto/Hash/SHA256/Native.hs view
@@ -16,6 +16,7 @@ module Crypto.Hash.SHA256.Native ( sha256_native_available , hash+ , hash_prefixed , hmac , _hmac_rr , _hmac_rsb@@ -111,6 +112,21 @@ _hash rp bp 0 m let !rs = peek_registers rp pure (cat rs)++hash_prefixed+ :: BS.ByteString+ -> BS.ByteString+ -> BS.ByteString+hash_prefixed p@(BI.PS _ _ l) m+ | l `rem` 64 /= 0 = hash (p <> m)+ | otherwise = unsafeDupablePerformIO $+ allocaBytes 32 $ \rp ->+ allocaBytes 64 $ \bp -> do+ poke_registers rp (iv ())+ hash_blocks rp bp p+ _hash rp bp (fi l) m+ let !rs = peek_registers rp+ pure (cat rs) _hash :: Ptr Word32 -- ^ register state
lib/Crypto/Hash/SHA256/Pure.hs view
@@ -15,6 +15,7 @@ module Crypto.Hash.SHA256.Pure ( hash+ , hash_prefixed , hmac , _hmac_rr , _hmac_rsb@@ -41,6 +42,16 @@ hash :: BS.ByteString -> BS.ByteString hash m = cat (_hash 0 (iv ()) m) {-# INLINABLE hash #-}++-- | Compute a condensed representation of the concatenation of two+-- strict bytestrings via SHA-256, using the pure Haskell+-- implementation. The second bytestring is not copied when the+-- length of the first is a multiple of 64.+hash_prefixed :: BS.ByteString -> BS.ByteString -> BS.ByteString+hash_prefixed p@(BI.PS _ _ l) m+ | l `rem` 64 /= 0 = hash (p <> m)+ | otherwise = cat (_hash (fi l) (_hash_blocks (iv ()) p) m)+{-# INLINABLE hash_prefixed #-} _hash :: Word64 -- ^ extra prefix length for padding calculations
ppad-sha256.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: ppad-sha256-version: 0.3.6+version: 0.3.7 synopsis: The SHA-256 and HMAC-SHA256 algorithms license: MIT license-file: LICENSE
test/Property.hs view
@@ -68,6 +68,15 @@ l <- Q.chooseInt (0, 8 * 64) Msg . BS.pack <$> Q.vectorOf l Q.arbitrary +-- block-aligned prefixes+newtype Prefix = Prefix BS.ByteString+ deriving Show++instance Q.Arbitrary Prefix where+ arbitrary = do+ k <- Q.chooseInt (0, 3)+ Prefix . BS.pack <$> Q.vectorOf (k * 64) Q.arbitrary+ -- register-sized strings newtype Bytes32 = Bytes32 BS.ByteString deriving Show@@ -101,6 +110,17 @@ hash_matches_pure :: Msg -> Bool hash_matches_pure (Msg m) = hash m == Pure.hash m +hash_prefixed_matches_hash :: Prefix -> Msg -> Bool+hash_prefixed_matches_hash (Prefix p) (Msg m) =+ let expected = hash (p <> m)+ in hash_prefixed p m == expected && Pure.hash_prefixed p m == expected++-- unaligned prefixes fall back to hashing the concatenation+hash_prefixed_unaligned :: Msg -> Msg -> Bool+hash_prefixed_unaligned (Msg p) (Msg m) =+ let expected = hash (p <> m)+ in hash_prefixed p m == expected && Pure.hash_prefixed p m == expected+ hmac_matches_pure :: Msg -> Msg -> Bool hmac_matches_pure (Msg k) (Msg m) = unmac (hmac k m) == Pure.hmac k m @@ -132,6 +152,17 @@ H.assertBool mempty $ all (\l -> hash (msg l) == Pure.hash (msg l)) [0 .. 3 * 64] +hash_prefixed_all_lengths :: TestTree+hash_prefixed_all_lengths =+ H.testCase "hash_prefixed ~ hash (all lengths)" $+ H.assertBool mempty $ and [+ hash_prefixed p (msg l) == e && Pure.hash_prefixed p (msg l) == e+ | k <- [0 .. 2]+ , l <- [0 .. 3 * 64]+ , let p = BS.reverse (msg (k * 64))+ e = hash (p <> msg l)+ ]+ hmac_all_lengths :: TestTree hmac_all_lengths = H.testCase "hmac ~ Pure.hmac (all lengths)" $ H.assertBool mempty $@@ -174,6 +205,10 @@ , testGroup "dispatch ~ pure" [ Q.testProperty "hash ~ Pure.hash" $ Q.withMaxSuccess 1000 hash_matches_pure+ , Q.testProperty "hash_prefixed p m ~ hash (p <> m)" $+ Q.withMaxSuccess 1000 hash_prefixed_matches_hash+ , Q.testProperty "hash_prefixed p m ~ hash (p <> m) (unaligned)" $+ Q.withMaxSuccess 1000 hash_prefixed_unaligned , Q.testProperty "hmac ~ Pure.hmac" $ Q.withMaxSuccess 1000 hmac_matches_pure , Q.testProperty "_hmac_rr k m ~ hmac (cat k) (cat m)" $@@ -181,6 +216,7 @@ , Q.testProperty "_hmac_rsb k v s d ~ hmac (cat k) (cat v <> s <> d)" $ Q.withMaxSuccess 1000 hmac_rsb_matches_hmac , hash_all_lengths+ , hash_prefixed_all_lengths , hmac_all_lengths , hmac_all_key_lengths , hmac_rsb_all_lengths