diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,10 @@
 # Changelog
 
+- 0.4.5 (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.4.4 (2026-07-06)
   * Reverts the unrolled MAC comparison introduced in 0.4.3, which was
     found to introduce timing variation on both aarch64 and x86-64 when
diff --git a/lib/Crypto/MAC/Poly1305.hs b/lib/Crypto/MAC/Poly1305.hs
--- a/lib/Crypto/MAC/Poly1305.hs
+++ b/lib/Crypto/MAC/Poly1305.hs
@@ -27,6 +27,7 @@
   ) where
 
 import qualified Crypto.MAC.Poly1305.Arm as Arm
+import Data.Barrier (barrier)
 import qualified Data.Bits as B
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Internal as BI
@@ -160,10 +161,13 @@
       -- 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.
+      -- is ever materialised on the 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").
       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,33 @@
+{-# 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.
+barrier :: Word8 -> Word8
+barrier x = x
+{-# NOINLINE barrier #-}
diff --git a/ppad-poly1305.cabal b/ppad-poly1305.cabal
--- a/ppad-poly1305.cabal
+++ b/ppad-poly1305.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               ppad-poly1305
-version:            0.4.4
+version:            0.4.5
 synopsis:           A fast Poly1305 MAC
 license:            MIT
 license-file:       LICENSE
@@ -38,6 +38,8 @@
   exposed-modules:
       Crypto.MAC.Poly1305
       Crypto.MAC.Poly1305.Arm
+  other-modules:
+      Data.Barrier
   build-depends:
       base >= 4.9 && < 5
     , bytestring >= 0.9 && < 0.13
