diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,10 @@
 # Changelog
 
+- 0.3.4 (2026-07-17)
+  * Adds an optimization-opaque barrier to MAC equality comparison
+    to prevent LLVM from introducing variable-time instructions when
+    compiling with the LLVM backend.
+
 - 0.3.3 (2026-07-17)
   * Uses a different algorithm for constant-time MAC comparison.
     This version seems to produce less microarchitectural variance on
diff --git a/lib/Crypto/AEAD/ChaCha20Poly1305.hs b/lib/Crypto/AEAD/ChaCha20Poly1305.hs
--- a/lib/Crypto/AEAD/ChaCha20Poly1305.hs
+++ b/lib/Crypto/AEAD/ChaCha20Poly1305.hs
@@ -25,6 +25,7 @@
   , _poly1305_key_gen
   ) where
 
+import Data.Barrier (barrier)
 import qualified Crypto.Cipher.ChaCha20 as ChaCha20
 import qualified Crypto.MAC.Poly1305 as Poly1305
 import Data.Bits ((.>>.))
@@ -42,7 +43,9 @@
 -- 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.
+-- heap. The accumulator is routed through 'barrier' before the
+-- zero-test so the LLVM backend cannot recover the array-equality
+-- idiom and short-circuit on the first mismatch (see "Data.Barrier").
 ct_eq :: BS.ByteString -> BS.ByteString -> Bool
 ct_eq a@(BI.PS _ _ la) b@(BI.PS _ _ lb)
     | la /= lb  = False
@@ -50,7 +53,7 @@
   where
     go :: Word8 -> Int -> Bool
     go !acc !i
-      | i == la   = acc == 0
+      | i == la   = barrier acc == 0
       | otherwise =
           let !x = BU.unsafeIndex a i
               !y = BU.unsafeIndex b i
diff --git a/lib/Data/Barrier.hs b/lib/Data/Barrier.hs
new file mode 100644
--- /dev/null
+++ b/lib/Data/Barrier.hs
@@ -0,0 +1,36 @@
+{-# OPTIONS_HADDOCK hide #-}
+
+-- |
+-- Module: Data.Barrier
+-- Copyright: (c) 2025 Jared Tobin
+-- License: MIT
+-- Maintainer: Jared Tobin <jared@ppad.tech>
+--
+-- An optimisation barrier for constant-time code.
+
+module Data.Barrier (
+    barrier
+  ) where
+
+import Data.Word (Word8)
+
+-- | Identity on 'Word8', but opaque to the optimiser. A constant-time
+--   accumulate-then-compare (e.g. an OR-fold of bytewise XORs, tested
+--   against zero) routes its accumulator through this before the
+--   zero-test, so the compiler cannot recognise it as an array-equality
+--   test and lower it to a short-circuiting byte comparison (which would
+--   leak the mismatch position).
+--
+--   Both properties are required and must not be \"tidied\" away:
+--
+--     * @NOINLINE@ -- if GHC inlines it, the LLVM backend regains the
+--       accumulator's definition and short-circuits again.
+--     * a /separate/ module -- a caller then compiles @barrier@ to an
+--       external call it cannot see through. Inline it into the caller
+--       and the barrier is gone.
+--
+--   Guarded by the substratum + censor constant-time checks; weakening
+--   either property re-introduces the timing leak.
+barrier :: Word8 -> Word8
+barrier x = x
+{-# NOINLINE barrier #-}
diff --git a/ppad-aead.cabal b/ppad-aead.cabal
--- a/ppad-aead.cabal
+++ b/ppad-aead.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               ppad-aead
-version:            0.3.3
+version:            0.3.4
 synopsis:           A pure AEAD-ChaCha20-Poly1305 construction
 license:            MIT
 license-file:       LICENSE
@@ -33,11 +33,13 @@
     ghc-options: -fllvm -O2
   exposed-modules:
       Crypto.AEAD.ChaCha20Poly1305
+  other-modules:
+      Data.Barrier
   build-depends:
       base >= 4.9 && < 5
     , bytestring >= 0.9 && < 0.13
     , ppad-chacha >= 0.2.2 && < 0.3
-    , ppad-poly1305 >= 0.4.4 && < 0.5
+    , ppad-poly1305 >= 0.4.5 && < 0.5
 
 test-suite aead-tests
   type:                exitcode-stdio-1.0
