commit 280ce9622c9b8fe456ca9fd8d153d5f5fb7e26bb
parent 71b15349810b0a76cd49263f433bee826fa10163
Author: Jared Tobin <jared@jtobin.io>
Date: Fri, 3 Jul 2026 16:34:31 -0230
lib: fuse the constant-time MAC comparison
Fold the bytewise XORs of the two buffers into the accumulator
directly, rather than materialising their XOR as an intermediate
ByteString via packZipWith: no per-comparison allocation, and no
secret-derived difference bytes ever hit the heap. Semantics and
the data-independent full walk are unchanged.
The implementation was checked with ppad-censor against
ppad-sha256's identical instance: mismatch-position independence
(byte 0 vs byte 31) passes at full budget under wall clock, with
the mean position effect bounded within ~1.4 ns per call.
Diffstat:
2 files changed, 19 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
@@ -1,5 +1,10 @@
# Changelog
+- Unreleased
+ * The constant-time 'Eq' on 'MAC' now folds over the two buffers
+ directly, rather than materialising an intermediate ByteString
+ holding their XOR on the heap. Semantics are unchanged.
+
- 0.2.2 (2026-02-01)
* The library has been refactored substantially to achieve greater
control over heap allocation, particularly around HMAC calculation.
diff --git a/lib/Crypto/Hash/SHA512/Internal.hs b/lib/Crypto/Hash/SHA512/Internal.hs
@@ -83,8 +83,20 @@ instance Eq MAC where
--
-- Runs in variable-time only for invalid inputs.
(MAC a@(BI.PS _ _ la)) == (MAC b@(BI.PS _ _ lb))
- | la /= lb = False
- | otherwise = BS.foldl' (B..|.) 0 (BS.packZipWith B.xor a b) == 0
+ | la /= lb = False
+ | otherwise = go 0 0
+ where
+ -- fused fold: OR the bytewise XORs into an accumulator
+ -- directly, rather than via packZipWith, so no intermediate
+ -- ByteString holding the (secret-derived) difference bytes
+ -- is ever materialised on the heap.
+ go :: Word8 -> Int -> Bool
+ go !acc !i
+ | i == la = acc == 0
+ | otherwise =
+ let !x = BU.unsafeIndex a i
+ !y = BU.unsafeIndex b i
+ in go (acc B..|. B.xor x y) (i + 1)
-- | SHA512 block.
newtype Block = Block