sha256

Pure Haskell SHA-256, HMAC-SHA256 (docs.ppad.tech/sha256).
git clone git://git.ppad.tech/sha256.git
Log | Files | Refs | README | LICENSE

commit 214244046766764b89600d701d350a568fc5ee92
parent 4716cd5b4e673e9cb66e4e5e427e5464a7c10977
Author: Jared Tobin <jared@jtobin.io>
Date:   Sun, 11 Jan 2026 12:50:28 +0400

test: add property tests

Diffstat:
Mppad-sha256.cabal | 3+++
Mtest/Main.hs | 2++
Atest/Property.hs | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 70 insertions(+), 0 deletions(-)

diff --git a/ppad-sha256.cabal b/ppad-sha256.cabal @@ -49,6 +49,7 @@ test-suite sha256-tests hs-source-dirs: test main-is: Main.hs other-modules: + Property Wycheproof ghc-options: @@ -60,8 +61,10 @@ test-suite sha256-tests , bytestring , ppad-base16 , ppad-sha256 + , quickcheck-instances , tasty , tasty-hunit + , tasty-quickcheck , text benchmark sha256-bench diff --git a/test/Main.hs b/test/Main.hs @@ -14,6 +14,7 @@ import qualified Data.Text.Encoding as TE import qualified Data.Text.IO as TIO import Test.Tasty import Test.Tasty.HUnit +import qualified Property import qualified Wycheproof as W main :: IO () @@ -24,6 +25,7 @@ main = do Just w -> defaultMain $ testGroup "ppad-sha256" [ unit_tests , wycheproof_tests w + , Property.properties ] wycheproof_tests :: W.Wycheproof -> TestTree diff --git a/test/Property.hs b/test/Property.hs @@ -0,0 +1,65 @@ +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module Property ( + properties + ) where + +import qualified Data.ByteString as BS +import qualified Data.ByteString.Lazy as BL +import Crypto.Hash.SHA256 +import Test.Tasty +import qualified Test.Tasty.QuickCheck as Q +import Test.QuickCheck.Instances.ByteString () + +-- strict/lazy hash equivalence +hash_equiv :: BS.ByteString -> Bool +hash_equiv bs = hash bs == hash_lazy (BL.fromStrict bs) + +-- strict/lazy hmac equivalence +hmac_equiv :: BS.ByteString -> BS.ByteString -> Bool +hmac_equiv k m = hmac k m == hmac_lazy k (BL.fromStrict m) + +-- hash output is always 32 bytes +hash_length :: BS.ByteString -> Bool +hash_length bs = BS.length (hash bs) == 32 + +-- hmac output is always 32 bytes +hmac_length :: BS.ByteString -> BS.ByteString -> Bool +hmac_length k m = let MAC h = hmac k m in BS.length h == 32 + +-- hash_lazy produces same result regardless of chunking +hash_chunking :: BS.ByteString -> [Q.Positive Int] -> Bool +hash_chunking bs chunks = + let lazy_single = BL.fromStrict bs + lazy_chunked = BL.fromChunks (chunk_by (map Q.getPositive chunks) bs) + in hash_lazy lazy_single == hash_lazy lazy_chunked + +-- hmac_lazy produces same result regardless of chunking +hmac_chunking :: BS.ByteString -> BS.ByteString -> [Q.Positive Int] -> Bool +hmac_chunking k m chunks = + let lazy_single = BL.fromStrict m + lazy_chunked = BL.fromChunks (chunk_by (map Q.getPositive chunks) m) + in hmac_lazy k lazy_single == hmac_lazy k lazy_chunked + +chunk_by :: [Int] -> BS.ByteString -> [BS.ByteString] +chunk_by _ bs | BS.null bs = [] +chunk_by [] bs = [bs] +chunk_by (n:ns) bs = + let (h, t) = BS.splitAt n bs + in h : chunk_by ns t + +properties :: TestTree +properties = testGroup "properties" [ + Q.testProperty "hash == hash_lazy" $ + Q.withMaxSuccess 1000 hash_equiv + , Q.testProperty "hmac == hmac_lazy" $ + Q.withMaxSuccess 1000 hmac_equiv + , Q.testProperty "hash output is 32 bytes" $ + Q.withMaxSuccess 1000 hash_length + , Q.testProperty "hmac output is 32 bytes" $ + Q.withMaxSuccess 1000 hmac_length + , Q.testProperty "hash_lazy chunking-invariant" $ + Q.withMaxSuccess 1000 hash_chunking + , Q.testProperty "hmac_lazy chunking-invariant" $ + Q.withMaxSuccess 1000 hmac_chunking + ]