aead

Pure Haskell AEAD-ChaCha20-Poly1305 (docs.ppad.tech/aead).
git clone git://git.ppad.tech/aead.git
Log | Files | Refs | README | LICENSE

commit 47c5985a71b1c9af1d16b2d4d0f525c0123072fb
parent 55d52287a32462bfad65008771c9949eb9dea0b6
Author: Jared Tobin <jared@jtobin.io>
Date:   Sat, 27 Dec 2025 09:19:24 -0330

lib: use fixed internally

Also adds a constant-time bytestring equality function.

Diffstat:
MREADME.md | 16++++++----------
Mflake.lock | 51+++++++++++++++++++++++++++++++++++++++++++--------
Mflake.nix | 15+++++++++++++--
Mlib/Crypto/AEAD/ChaCha20Poly1305.hs | 10+++++++++-
Mppad-aead.cabal | 2+-
5 files changed, 72 insertions(+), 22 deletions(-)

diff --git a/README.md b/README.md @@ -58,16 +58,16 @@ benchmark suite): ``` benchmarking ppad-aead/encrypt - time 10.03 μs (10.02 μs .. 10.03 μs) + time 2.589 μs (2.587 μs .. 2.592 μs) 1.000 R² (1.000 R² .. 1.000 R²) - mean 10.04 μs (10.04 μs .. 10.05 μs) - std dev 9.024 ns (7.330 ns .. 11.99 ns) + mean 2.595 μs (2.594 μs .. 2.597 μs) + std dev 5.384 ns (4.491 ns .. 6.511 ns) benchmarking ppad-aead/decrypt - time 10.06 μs (10.05 μs .. 10.07 μs) + time 2.625 μs (2.618 μs .. 2.636 μs) 1.000 R² (1.000 R² .. 1.000 R²) - mean 10.07 μs (10.06 μs .. 10.08 μs) - std dev 26.50 ns (21.66 ns .. 32.02 ns) + mean 2.614 μs (2.610 μs .. 2.621 μs) + std dev 15.99 ns (11.98 ns .. 27.52 ns) ``` ## Security @@ -76,10 +76,6 @@ This library aims at the maximum security achievable in a garbage-collected language under an optimizing compiler such as GHC, in which strict constant-timeness can be [challenging to achieve][const]. -Note that *at present* we use GHC's native variable-length Integer -type internally (relevant to Poly1305 MAC handling), and make no "hard" -guarantees of constant-time execution. - The AEAD-ChaCha20-Poly1305 implementation within passes all test vectors from RFC8439, as well as the available [Project Wycheproof vectors][wyche], using the ChaCha20 cipher from diff --git a/flake.lock b/flake.lock @@ -85,11 +85,11 @@ ] }, "locked": { - "lastModified": 1750501666, - "narHash": "sha256-vIZrbi7ozlsZ0yMUxz7BAX7KsgTvsWS+MfCJEfGxu+o=", + "lastModified": 1766837471, + "narHash": "sha256-v2LKjKLK6gWWjKA1dzgFVihTrfODorlNfNBhWlia3g0=", "ref": "master", - "rev": "9adca9651d3098b8fa2ce48f663c5ac6105dc90e", - "revCount": 20, + "rev": "6752c4336c1b581ae7875b1c9a0eaed115e420a3", + "revCount": 23, "type": "git", "url": "git://git.ppad.tech/chacha.git" }, @@ -99,6 +99,40 @@ "url": "git://git.ppad.tech/chacha.git" } }, + "ppad-fixed": { + "inputs": { + "flake-utils": [ + "ppad-poly1305", + "ppad-fixed", + "ppad-nixpkgs", + "flake-utils" + ], + "nixpkgs": [ + "ppad-poly1305", + "ppad-fixed", + "ppad-nixpkgs", + "nixpkgs" + ], + "ppad-nixpkgs": [ + "ppad-poly1305", + "ppad-nixpkgs" + ] + }, + "locked": { + "lastModified": 1766767330, + "narHash": "sha256-YtJJJT/TwKxIFUHk5DCqLV64mtC1O5z6ncSbxcIct7E=", + "ref": "master", + "rev": "e9498b179a8c88328af5f0c1518870bd1b5dd42e", + "revCount": 268, + "type": "git", + "url": "git://git.ppad.tech/fixed.git" + }, + "original": { + "ref": "master", + "type": "git", + "url": "git://git.ppad.tech/fixed.git" + } + }, "ppad-nixpkgs": { "inputs": { "flake-utils": "flake-utils", @@ -134,16 +168,17 @@ "ppad-base16": [ "ppad-base16" ], + "ppad-fixed": "ppad-fixed", "ppad-nixpkgs": [ "ppad-nixpkgs" ] }, "locked": { - "lastModified": 1750502056, - "narHash": "sha256-0KJgniap/pyKsEvTh2SgA/D4zzX194P/oAzJv3fSVdM=", + "lastModified": 1766836438, + "narHash": "sha256-0C5beSCaiTprBjpZkbJFAonRuvMaj8AbVB1WNMYDEro=", "ref": "master", - "rev": "9a67f66c8d0a9292aa5b86a620be6c4d8c7d66d2", - "revCount": 17, + "rev": "7fa9ffd429c84c64563ce1c056e0aa3afe26943d", + "revCount": 21, "type": "git", "url": "git://git.ppad.tech/poly1305.git" }, diff --git a/flake.nix b/flake.nix @@ -41,12 +41,21 @@ pkgs = import nixpkgs { inherit system; }; hlib = pkgs.haskell.lib; + llvm = pkgs.llvmPackages_15.llvm; + + poly1305 = ppad-poly1305.packages.${system}.default; + poly1305-llvm = + hlib.addBuildTools + (hlib.enableCabalFlag poly1305 "llvm") + [ llvm ]; hpkgs = pkgs.haskell.packages.ghc981.extend (new: old: { - ${lib} = old.callCabal2nixWithOptions lib ./. "--enable-profiling" {}; ppad-base16 = ppad-base16.packages.${system}.default; ppad-chacha = ppad-chacha.packages.${system}.default; - ppad-poly1305 = ppad-poly1305.packages.${system}.default; + ppad-poly1305 = poly1305-llvm; + ${lib} = new.callCabal2nixWithOptions lib ./. "--enable-profiling" { + ppad-poly1305 = new.ppad-poly1305; + }; }); cc = pkgs.stdenv.cc; @@ -64,6 +73,7 @@ buildInputs = [ cabal cc + llvm ]; inputsFrom = builtins.attrValues self.packages.${system}; @@ -76,6 +86,7 @@ echo "cc: $(${cc}/bin/cc --version)" echo "ghc: $(${ghc}/bin/ghc --version)" echo "cabal: $(${cabal}/bin/cabal --version)" + echo "llc: $(${llvm}/bin/llc --version | head -2 | tail -1)" ''; }; } diff --git a/lib/Crypto/AEAD/ChaCha20Poly1305.hs b/lib/Crypto/AEAD/ChaCha20Poly1305.hs @@ -28,6 +28,7 @@ module Crypto.AEAD.ChaCha20Poly1305 ( import qualified Crypto.Cipher.ChaCha20 as ChaCha20 import qualified Crypto.MAC.Poly1305 as Poly1305 import Data.Bits ((.>>.)) +import qualified Data.Bits as B import qualified Data.ByteString as BS import qualified Data.ByteString.Internal as BI import Data.Word (Word64) @@ -36,6 +37,13 @@ fi :: (Integral a, Num b) => a -> b fi = fromIntegral {-# INLINE fi #-} +-- constant-time equality comparison on bytestrings +ct_eq :: BS.ByteString -> BS.ByteString -> Bool +ct_eq a@(BI.PS _ _ la) b@(BI.PS _ _ lb) + | la /= lb = False + | otherwise = BS.foldl' (B..|.) 0 (BS.packZipWith B.xor a b) == 0 +{-# INLINE ct_eq #-} + -- little-endian bytestring encoding unroll :: Word64 -> BS.ByteString unroll i = case i of @@ -144,7 +152,7 @@ decrypt aad key nonce (cip, mac) case Poly1305.mac otk md3 of Nothing -> Left InvalidKey Just tag - | mac == tag -> case ChaCha20.cipher key 1 nonce cip of + | ct_eq mac tag -> case ChaCha20.cipher key 1 nonce cip of Left ChaCha20.InvalidKey -> Left InvalidKey Left ChaCha20.InvalidNonce -> Left InvalidNonce Right v -> pure v diff --git a/ppad-aead.cabal b/ppad-aead.cabal @@ -30,7 +30,7 @@ library base >= 4.9 && < 5 , bytestring >= 0.9 && < 0.13 , ppad-chacha >= 0.2 && < 0.3 - , ppad-poly1305 >= 0.3 && < 0.4 + , ppad-poly1305 >= 0.4 && < 0.5 test-suite aead-tests type: exitcode-stdio-1.0