poly1305

The Poly1305 message authentication code (docs.ppad.tech/poly1305).
git clone git://git.ppad.tech/poly1305.git
Log | Files | Refs | README | LICENSE

commit 2b580fad35e20f77cc8c8ec4780b3679c55664d4
parent d7cfef78410784591efe93f0e3322278029c41ed
Author: Jared Tobin <jared@jtobin.io>
Date:   Fri, 17 Jul 2026 17:58:10 -0230

lib: add barrier module

Diffstat:
Mlib/Crypto/MAC/Poly1305.hs | 8++++++--
Alib/Data/Barrier.hs | 33+++++++++++++++++++++++++++++++++
Mppad-poly1305.cabal | 2++
3 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/lib/Crypto/MAC/Poly1305.hs b/lib/Crypto/MAC/Poly1305.hs @@ -27,6 +27,7 @@ module Crypto.MAC.Poly1305 ( ) 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 @@ instance Eq MAC 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. + -- 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 @@ -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 @@ -38,6 +38,8 @@ library exposed-modules: Crypto.MAC.Poly1305 Crypto.MAC.Poly1305.Arm + other-modules: + Data.Barrier build-depends: base >= 4.9 && < 5 , bytestring >= 0.9 && < 0.13