poly1305

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

commit 268bab002f8fe1706d5becb31ba843be46ab009a
parent a54bf8752fca63fff2d945776f9460f3a9b8a878
Author: Jared Tobin <jared@jtobin.io>
Date:   Sun, 26 Apr 2026 07:18:12 -0230

lib: add MAC type with constant-time equality

Diffstat:
Mlib/Crypto/MAC/Poly1305.hs | 42+++++++++++++++++++++++++++++++++++-------
1 file changed, 35 insertions(+), 7 deletions(-)

diff --git a/lib/Crypto/MAC/Poly1305.hs b/lib/Crypto/MAC/Poly1305.hs @@ -1,5 +1,7 @@ {-# OPTIONS_HADDOCK prune #-} {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE ViewPatterns #-} @@ -16,13 +18,15 @@ module Crypto.MAC.Poly1305 ( -- * Poly1305 message authentication code - mac + MAC(..) + , mac -- testing , _poly1305_loop , _roll16 ) where +import qualified Data.Bits as B import qualified Data.ByteString as BS import qualified Data.ByteString.Internal as BI import qualified Data.ByteString.Unsafe as BU @@ -129,11 +133,35 @@ clamp :: Wider -> Wider clamp r = r `W.and` 0x0ffffffc0ffffffc0ffffffc0fffffff {-# INLINE clamp #-} --- | Produce a Poly1305 MAC for the provided message, given the provided --- key. +-- | A Poly1305 message authentication code. -- --- Per RFC8439: the key, which is essentially a /one-time/ key, should --- be unique, and MUST be unpredictable for each invocation. +-- Note that you should compare MACs for equality using the 'Eq' +-- instance, which performs the comparison in constant time, instead +-- of unwrapping and comparing the underlying 'ByteStrings'. +-- +-- >>> let Just foo@(MAC bs0) = mac key "hi" +-- >>> let Just bar@(MAC bs1) = mac key "there" +-- >>> foo == bar -- do this +-- False +-- >>> bs0 == bs1 -- don't do this +-- False +newtype MAC = MAC BS.ByteString + deriving newtype Show + +instance Eq MAC where + -- | A constant-time equality check for message authentication codes. + -- + -- 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 + +-- | Produce a Poly1305 MAC for the provided message, given the +-- provided key. +-- +-- Per RFC8439: the key, which is essentially a /one-time/ key, +-- should be unique, and MUST be unpredictable for each invocation. -- -- The key must be exactly 256 bits in length. -- @@ -142,12 +170,12 @@ clamp r = r `W.and` 0x0ffffffc0ffffffc0ffffffc0fffffff mac :: BS.ByteString -- ^ 256-bit one-time key -> BS.ByteString -- ^ arbitrary-length message - -> Maybe BS.ByteString -- ^ 128-bit message authentication code + -> Maybe MAC -- ^ 128-bit message authentication code mac key@(BI.PS _ _ kl) msg | kl /= 32 = Nothing | otherwise = let (clamp . _roll16 -> r, _roll16 -> s) = BS.splitAt 16 key - in pure (_poly1305_loop r s msg) + in pure $! (MAC (_poly1305_loop r s msg)) -- p = 2^130 - 5 --