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:
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