diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,45 @@
 # CHANGELOG for crypton
 
+## 2.2.6
+
+**Fixes from a security review.**  Inputs that are invalid by the
+specifications are now refused rather than accepted: an OCB IV longer than
+15 bytes (#357), a P-384 or P-521 point whose coordinates are not of fixed
+length (#362), and a CCM message given in pieces or with AAD twice (#356).
+
+* perf(rsa): verification at 3072 and 4096 bits in C on x86-64
+  [#348](https://github.com/kazu-yamamoto/crypton/pull/348)
+* Scrypt: refuse parameters that overrun the working memory
+  [#354](https://github.com/kazu-yamamoto/crypton/pull/354)
+* AArch64 one-shot GCM: loops wrap near 4 GiB
+  [#355](https://github.com/kazu-yamamoto/crypton/pull/355)
+* AES-CCM: fail the context on a length mismatch or repeated AAD
+  [#356](https://github.com/kazu-yamamoto/crypton/pull/356)
+* AES-OCB: refuse an IV outside 1 to 15 bytes in aeadInit
+  [#357](https://github.com/kazu-yamamoto/crypton/pull/357)
+* AES-GCM-SIV: give Nothing for a tag that is not 16 bytes
+  [#358](https://github.com/kazu-yamamoto/crypton/pull/358)
+* Hash: feed every update through updateBlocks
+  [#359](https://github.com/kazu-yamamoto/crypton/pull/359)
+* PBKDF2: refuse iteration counts the fast path cannot take
+  [#360](https://github.com/kazu-yamamoto/crypton/pull/360)
+* ECC: decode P-384 and P-521 points only at their fixed length
+  [#362](https://github.com/kazu-yamamoto/crypton/pull/362)
+* P-256: negating the point at infinity gives the point at infinity
+  [#363](https://github.com/kazu-yamamoto/crypton/pull/363)
+* P-256: a zero scalar lifts to the point at infinity
+  [#364](https://github.com/kazu-yamamoto/crypton/pull/364)
+* RSA: recompute without CRT even when the key carries no d
+  [#365](https://github.com/kazu-yamamoto/crypton/pull/365)
+* fix: lengths of 4 GiB or more in ChaChaDRG and Argon2
+  [#366](https://github.com/kazu-yamamoto/crypton/pull/366)
+* fix(rsa): IFMA table select reads every entry in full
+  [#367](https://github.com/kazu-yamamoto/crypton/pull/367)
+* fix(gcm): refuse incremental input past 2^36 - 32 bytes
+  [#368](https://github.com/kazu-yamamoto/crypton/pull/368)
+* fix: doubling in GF(2^n) without branching on secret bits
+  [#369](https://github.com/kazu-yamamoto/crypton/pull/369)
+
 ## 2.2.5
 
 **RSA private-key operations check their CRT result.**  A fault in one
diff --git a/Crypto/Cipher/AES.hs b/Crypto/Cipher/AES.hs
--- a/Crypto/Cipher/AES.hs
+++ b/Crypto/Cipher/AES.hs
@@ -76,7 +76,7 @@
     ; cbcDecrypt (CSTR aes) (IV iv) = decryptCBC aes (IV iv) \
     ; ctrCombine (CSTR aes) (IV iv) = encryptCTR aes (IV iv) \
     ; aeadInit AEAD_GCM (CSTR aes) iv = gcmAeadInit aes iv \
-    ; aeadInit AEAD_OCB (CSTR aes) iv = CryptoPassed $ AEAD (ocbMode aes) (ocbInit aes iv) \
+    ; aeadInit AEAD_OCB (CSTR aes) iv = AEAD (ocbMode aes) <$> ocbInit aes iv \
     ; aeadInit (AEAD_CCM n m l) (CSTR aes) iv = AEAD (ccmMode aes) <$> ccmInit aes iv n m l \
     ; aeadInit _        _          _  = CryptoFailed CryptoError_AEADModeNotSupported \
     }; \
diff --git a/Crypto/Cipher/AES/Primitive.hs b/Crypto/Cipher/AES/Primitive.hs
--- a/Crypto/Cipher/AES/Primitive.hs
+++ b/Crypto/Cipher/AES/Primitive.hs
@@ -63,6 +63,7 @@
 import Foreign.C.String
 import Foreign.C.Types
 import Foreign.Ptr
+import Foreign.Storable (peekByteOff)
 
 import Crypto.Cipher.Types
 import Crypto.Cipher.Types.Block (IV (..))
@@ -90,7 +91,7 @@
     cbcDecrypt = decryptCBC
     ctrCombine = encryptCTR
     aeadInit AEAD_GCM aes iv = gcmAeadInit aes iv
-    aeadInit AEAD_OCB aes iv = CryptoPassed $ AEAD (ocbMode aes) (ocbInit aes iv)
+    aeadInit AEAD_OCB aes iv = AEAD (ocbMode aes) <$> ocbInit aes iv
     aeadInit (AEAD_CCM n m l) aes iv = AEAD (ccmMode aes) <$> ccmInit aes iv n m l
     aeadInit _ _ _ = CryptoFailed CryptoError_AEADModeNotSupported
 instance BlockCipher128 AES where
@@ -176,7 +177,7 @@
 sizeOCB = 160
 
 sizeCCM :: Int
-sizeCCM = 80
+sizeCCM = 84
 
 keyToPtr :: AES -> (Ptr AES -> IO a) -> IO a
 keyToPtr (AES b) f = withByteArray b (f . castPtr)
@@ -664,7 +665,9 @@
 -- needs to happen after AAD appending, or after initialization if no AAD data.
 {-# NOINLINE gcmAppendEncrypt #-}
 gcmAppendEncrypt :: ByteArray ba => AES -> AESGCM -> ba -> (ba, AESGCM)
-gcmAppendEncrypt ctx gcm input = unsafeDoIO $ withGCMKeyAndCopySt ctx gcm doEnc
+gcmAppendEncrypt ctx gcm input
+    | gcmTooLong gcm len = error gcmTooLongMessage
+    | otherwise = unsafeDoIO $ withGCMKeyAndCopySt ctx gcm doEnc
   where
     len = B.length input
     doEnc gcmStPtr aesPtr =
@@ -684,7 +687,9 @@
 -- needs to happen after AAD appending, or after initialization if no AAD data.
 {-# NOINLINE gcmAppendDecrypt #-}
 gcmAppendDecrypt :: ByteArray ba => AES -> AESGCM -> ba -> (ba, AESGCM)
-gcmAppendDecrypt ctx gcm input = unsafeDoIO $ withGCMKeyAndCopySt ctx gcm doDec
+gcmAppendDecrypt ctx gcm input
+    | gcmTooLong gcm len = error gcmTooLongMessage
+    | otherwise = unsafeDoIO $ withGCMKeyAndCopySt ctx gcm doDec
   where
     len = B.length input
     doDec gcmStPtr aesPtr =
@@ -698,6 +703,20 @@
                         (i `plusPtr` off)
                         (fromIntegral n)
 
+-- | SP 800-38D 5.2.1.1 limits a message to 2^39 - 256 bits; past that the
+-- 32-bit counter wraps and the keystream repeats.  The bytes so far are
+-- @length_input@ in @aes_gcm@ (cbits/crypton_aes.h).
+gcmTooLong :: AESGCM -> Int -> Bool
+gcmTooLong (AESGCM st) len = fromIntegral len > gcmMaxInput - used
+  where
+    used = unsafeDoIO $ withByteArray st $ \p -> peekByteOff p 312 :: IO Word64
+
+gcmMaxInput :: Word64
+gcmMaxInput = 2 ^ (36 :: Int) - 32
+
+gcmTooLongMessage :: String
+gcmTooLongMessage = "AES-GCM: a message is limited to 2^36 - 32 bytes"
+
 -- | Generate the Tag from GCM context
 {-# NOINLINE gcmFinish #-}
 gcmFinish :: AES -> AESGCM -> Int -> AuthTag
@@ -711,18 +730,11 @@
 ------------------------------------------------------------------------
 
 -- | initialize an ocb context
-{-# NOINLINE ocbInit #-}
-ocbInit :: ByteArrayAccess iv => AES -> iv -> AESOCB
-ocbInit ctx iv = unsafeDoIO $ do
-    sm <- B.alloc sizeOCB $ \ocbStPtr ->
-        withKeyAndIV ctx iv $ \k v ->
-            c_aes_ocb_init
-                (castPtr ocbStPtr)
-                k
-                v
-                (fromIntegral $ B.length iv)
-                16
-    return $ AESOCB sm
+--
+-- The IV length must be in [1..15] bytes per RFC 7253; the C would keep only
+-- the first 15, so longer IVs differing after that would share a nonce.
+ocbInit :: ByteArrayAccess iv => AES -> iv -> CryptoFailable AESOCB
+ocbInit ctx iv = ocbInitWithTagLength ctx iv 16
 
 -- | initialize an OCB context with a fixed authentication tag length.
 --
@@ -824,6 +836,9 @@
     => AES -> iv -> Int -> CCM_M -> CCM_L -> CryptoFailable AESCCM
 ccmInit ctx iv n m l
     | 15 - li /= B.length iv = CryptoFailed CryptoError_IvSizeInvalid
+    -- the length has to fit in L bytes, or it overlaps the nonce
+    | n < 0 || toInteger n >= 2 ^ (8 * li) =
+        CryptoFailed CryptoError_ParameterInvalid
     | otherwise = unsafeDoIO $ do
         sm <- B.alloc sizeCCM $ \ccmStPtr ->
             withKeyAndIV ctx iv $ \k v ->
@@ -854,24 +869,30 @@
 
 -- | append data to encrypt and append to the CCM context
 --
--- the bytearray needs to be a multiple of AES block size, unless it's the last call to this function.
--- needs to happen after AAD appending, or after initialization if no AAD data.
+-- the whole message in one call, of the length given to 'ccmInit'; anything
+-- else is an error.  needs to happen after AAD appending, or after
+-- initialization if no AAD data.
 {-# NOINLINE ccmEncrypt #-}
 ccmEncrypt :: ByteArray ba => AES -> AESCCM -> ba -> (ba, AESCCM)
 ccmEncrypt ctx ccm input
     | B.overCLength (B.length input) = error tooLongMessage
-    | otherwise = unsafeDoIO $ withCCMKeyAndCopySt ctx ccm cbcmacAndIv
+    | otherwise = case unsafeDoIO $ withCCMKeyAndCopySt ctx ccm cbcmacAndIv of
+        ((1, out), st) -> (out, st)
+        _ ->
+            error
+                "AES-CCM: the message must be given in one call, of the length given at init"
   where
     len = B.length input
     cbcmacAndIv ccmStPtr aesPtr =
-        B.alloc len $ \o ->
+        B.allocRet len $ \o ->
             withByteArray input $ \i ->
                 c_aes_ccm_encrypt (castPtr o) ccmStPtr aesPtr i (fromIntegral len)
 
 -- | append data to decrypt and append to the CCM context
 --
--- the bytearray needs to be a multiple of AES block size, unless it's the last call to this function.
--- needs to happen after AAD appending, or after initialization if no AAD data.
+-- the whole message in one call, of the length given to 'ccmInit'.  Any other
+-- length gives zeros and a context whose tag matches nothing.  needs to happen
+-- after AAD appending, or after initialization if no AAD data.
 {-# NOINLINE ccmDecrypt #-}
 ccmDecrypt :: ByteArray ba => AES -> AESCCM -> ba -> (ba, AESCCM)
 ccmDecrypt ctx ccm input
@@ -882,15 +903,17 @@
     cbcmacAndIv ccmStPtr aesPtr =
         B.alloc len $ \o ->
             withByteArray input $ \i ->
-                c_aes_ccm_decrypt (castPtr o) ccmStPtr aesPtr i (fromIntegral len)
+                void $ c_aes_ccm_decrypt (castPtr o) ccmStPtr aesPtr i (fromIntegral len)
 
--- | Generate the Tag from CCM context
+-- | Generate the Tag from CCM context.  A context that was misused (the
+-- additional data given twice, the message in pieces or of another length)
+-- gives an empty tag, which no tag compares equal to.
 {-# NOINLINE ccmFinish #-}
 ccmFinish :: AES -> AESCCM -> Int -> AuthTag
-ccmFinish ctx ccm taglen = AuthTag $ B.take taglen computeTag
+ccmFinish ctx ccm taglen = AuthTag $ if ok == 1 then B.take taglen t else B.empty
   where
-    computeTag = B.allocAndFreeze 16 $ \t ->
-        withCCMKeyAndCopySt ctx ccm (c_aes_ccm_finish (castPtr t)) >> return ()
+    (ok, t) = unsafeDoIO $ B.allocRet 16 $ \p ->
+        fst <$> withCCMKeyAndCopySt ctx ccm (c_aes_ccm_finish (castPtr p))
 
 ------------------------------------------------------------------------
 foreign import ccall "crypton_aes.h crypton_aes_initkey"
@@ -1095,11 +1118,11 @@
 
 foreign import ccall "crypton_aes.h crypton_aes_ccm_encrypt"
     c_aes_ccm_encrypt
-        :: CString -> Ptr AESCCM -> Ptr AES -> CString -> CUInt -> IO ()
+        :: CString -> Ptr AESCCM -> Ptr AES -> CString -> CUInt -> IO CInt
 
 foreign import ccall "crypton_aes.h crypton_aes_ccm_decrypt"
     c_aes_ccm_decrypt
-        :: CString -> Ptr AESCCM -> Ptr AES -> CString -> CUInt -> IO ()
+        :: CString -> Ptr AESCCM -> Ptr AES -> CString -> CUInt -> IO CInt
 
 foreign import ccall "crypton_aes.h crypton_aes_ccm_finish"
-    c_aes_ccm_finish :: CString -> Ptr AESCCM -> Ptr AES -> IO ()
+    c_aes_ccm_finish :: CString -> Ptr AESCCM -> Ptr AES -> IO CInt
diff --git a/Crypto/Cipher/AESGCMSIV.hs b/Crypto/Cipher/AESGCMSIV.hs
--- a/Crypto/Cipher/AESGCMSIV.hs
+++ b/Crypto/Cipher/AESGCMSIV.hs
@@ -145,13 +145,16 @@
 -- cipher.
 --
 -- Lengths of additional data and ciphertext must be less than 2^32 bytes,
--- otherwise an exception is thrown.
+-- otherwise an exception is thrown.  A tag that is not 16 bytes gives
+-- 'Nothing'.
 decrypt
     :: (BlockCipher128 aes, ByteArrayAccess aad, ByteArray ba)
     => aes -> Nonce -> aad -> ba -> AuthTag -> Maybe ba
 decrypt aes iv aad ciphertext (AuthTag tag)
     | lengthInvalid aad = error "AESGCMSIV: aad is too large"
     | lengthInvalid ciphertext = error "AESGCMSIV: ciphertext is too large"
+    -- the tag is also the initial counter block, read up to byte 15
+    | B.length tag /= 16 = Nothing
     | tag `B.constEq` buildTag mek ss iv = Just plaintext
     | otherwise = Nothing
   where
diff --git a/Crypto/Cipher/ChaCha.hs b/Crypto/Cipher/ChaCha.hs
--- a/Crypto/Cipher/ChaCha.hs
+++ b/Crypto/Cipher/ChaCha.hs
@@ -225,9 +225,12 @@
     -> (ba, StateSimple)
 generateSimple (StateSimple prevSt) nbBytes = unsafeDoIO $ do
     newSt <- B.copy prevSt (\_ -> return ())
+    -- in pieces, as the C takes a uint32_t; each is a multiple of the 16
+    -- bytes it rekeys after, so the stream is the same as in one call
     output <- B.alloc nbBytes $ \dstPtr ->
         B.withByteArray newSt $ \stPtr ->
-            ccrypton_chacha_random 8 dstPtr stPtr (fromIntegral nbBytes)
+            B.inCLengths nbBytes $ \off n ->
+                ccrypton_chacha_random 8 (dstPtr `plusPtr` off) stPtr (fromIntegral n)
     return (output, StateSimple newSt)
 
 -- | similar to 'generate' but accepts a number of rounds, and always generates
diff --git a/Crypto/Cipher/Types/GF.hs b/Crypto/Cipher/Types/GF.hs
--- a/Crypto/Cipher/Types/GF.hs
+++ b/Crypto/Cipher/Types/GF.hs
@@ -34,13 +34,11 @@
     | otherwise = error "unsupported block size in GF"
   where
     gf :: Word64 -> Word64 -> (Word64, Word64)
+    -- the carries are secret, so they are masks rather than branches
     gf srcLo srcHi =
-        ( (if carryLo then (.|. 1) else id) (srcHi `shiftL` 1)
-        , (if carryHi then xor 0x87 else id) $ (srcLo `shiftL` 1)
+        ( (srcHi `shiftL` 1) .|. (srcLo `shiftR` 63)
+        , (srcLo `shiftL` 1) `xor` (0x87 .&. negate (srcHi `shiftR` 63))
         )
-      where
-        carryHi = srcHi `testBit` 63
-        carryLo = srcLo `testBit` 63
     len = B.length b
 
 {-
diff --git a/Crypto/ECC.hs b/Crypto/ECC.hs
--- a/Crypto/ECC.hs
+++ b/Crypto/ECC.hs
@@ -416,18 +416,23 @@
     yb = i2ospOf_ size y
 
 decodeECPoint
-    :: (Simple.Curve curve, ByteArray bs) => bs -> CryptoFailable (Simple.Point curve)
+    :: forall curve bs
+     . (Simple.Curve curve, ByteArray bs)
+    => bs -> CryptoFailable (Simple.Point curve)
 decodeECPoint mxy = case B.uncons mxy of
     Nothing -> CryptoFailed CryptoError_PointSizeInvalid
     Just (m, xy)
+        -- one encoding per point: each coordinate exactly the field's size
+        | m == 4 && B.length xy /= 2 * siz -> CryptoFailed CryptoError_PointSizeInvalid
         -- uncompressed
         | m == 4 ->
-            let siz = B.length xy `div` 2
-                (xb, yb) = B.splitAt siz xy
+            let (xb, yb) = B.splitAt siz xy
                 x = os2ip xb
                 y = os2ip yb
              in Simple.pointFromIntegers (x, y)
         | otherwise -> CryptoFailed CryptoError_PointFormatInvalid
+  where
+    siz = Simple.curveSizeBytes (Proxy :: Proxy curve)
 
 ecPointsMulVarTime
     :: forall curve
diff --git a/Crypto/Hash.hs b/Crypto/Hash.hs
--- a/Crypto/Hash.hs
+++ b/Crypto/Hash.hs
@@ -52,11 +52,9 @@
 import Crypto.Internal.ByteArray (ByteArrayAccess, allocAndFreezePrim)
 import qualified Crypto.Internal.ByteArray as B
 import qualified Data.ByteString.Lazy as L
-import Data.Int (Int32)
-import Data.Word (Word8)
 import Foreign.Marshal.Alloc (allocaBytes)
 import qualified Foreign.Marshal.Utils as FMU
-import Foreign.Ptr (Ptr, castPtr, plusPtr)
+import Foreign.Ptr (Ptr, castPtr)
 
 -- | Hash a strict bytestring into a digest.
 hash :: forall ba a. (ByteArrayAccess ba, HashAlgorithm a) => ba -> Digest a
@@ -103,16 +101,6 @@
         mapM_ (\b -> B.withByteArray b (updateBlocks ctx (B.length b))) ls
   where
     ls = filter (not . B.null) l
-
--- | Process the data in 2GB chunks to fit in uint32_t and Int on 32 bit systems
-updateBlocks :: HashAlgorithm a => Ptr (Context a) -> Int -> Ptr Word8 -> IO ()
-updateBlocks ctx bytesLeft dataPtr
-    | bytesLeft == 0 = return ()
-    | otherwise = do
-        hashInternalUpdate ctx dataPtr (fromIntegral actuallyProcessed)
-        updateBlocks ctx (bytesLeft - actuallyProcessed) (dataPtr `plusPtr` actuallyProcessed)
-  where
-    actuallyProcessed = min bytesLeft (fromIntegral (maxBound :: Int32))
 
 -- | Finalize a context and return a digest.
 hashFinalize
diff --git a/Crypto/Hash/IO.hs b/Crypto/Hash/IO.hs
--- a/Crypto/Hash/IO.hs
+++ b/Crypto/Hash/IO.hs
@@ -60,8 +60,7 @@
         => MutableContext a -> ((Ptr (Context a) -> IO ()) -> IO ()) -> IO ()
     doUpdate _ withCtx =
         withCtx $ \ctx ->
-            B.withByteArray dat $ \d ->
-                hashInternalUpdate ctx d (fromIntegral $ B.length dat)
+            B.withByteArray dat $ updateBlocks ctx (B.length dat)
 
 -- | Finalize a mutable hash context and compute a digest
 hashMutableFinalize
diff --git a/Crypto/Hash/Types.hs b/Crypto/Hash/Types.hs
--- a/Crypto/Hash/Types.hs
+++ b/Crypto/Hash/Types.hs
@@ -18,6 +18,7 @@
     HashAlgorithmPrefix (..),
     Context (..),
     Digest (..),
+    updateBlocks,
 ) where
 
 import Control.DeepSeq (deepseq)
@@ -31,6 +32,7 @@
 import Data.ByteString.Base16 (encodeBase16)
 import Data.Char (digitToInt, isHexDigit)
 import Data.Data (Data)
+import Data.Int (Int32)
 import Data.Primitive.ByteArray (
     ByteArray,
     MutableByteArray,
@@ -41,8 +43,22 @@
     writeByteArray,
  )
 import qualified Data.Text as Text
-import Foreign.Ptr (Ptr, castPtr)
+import Foreign.Ptr (Ptr, castPtr, plusPtr)
 import GHC.TypeLits (Nat)
+
+-- | Feed a buffer of any length to 'hashInternalUpdate', in 2GB chunks: the
+-- C takes a @uint32_t@, and a longer length would be silently truncated.
+updateBlocks :: HashAlgorithm a => Ptr (Context a) -> Int -> Ptr Word8 -> IO ()
+updateBlocks ctx bytesLeft dataPtr
+    | bytesLeft <= 0 = return ()
+    | otherwise = do
+        hashInternalUpdate ctx dataPtr (fromIntegral actuallyProcessed)
+        updateBlocks
+            ctx
+            (bytesLeft - actuallyProcessed)
+            (dataPtr `plusPtr` actuallyProcessed)
+  where
+    actuallyProcessed = min bytesLeft (fromIntegral (maxBound :: Int32))
 
 -- | Class representing hashing algorithms.
 --
diff --git a/Crypto/KDF/Argon2.hs b/Crypto/KDF/Argon2.hs
--- a/Crypto/KDF/Argon2.hs
+++ b/Crypto/KDF/Argon2.hs
@@ -107,8 +107,8 @@
 -- | Hash a password with Argon2.
 --
 -- Options the underlying implementation refuses -- iterations, memory or
--- parallelism outside the range it accepts -- are reported as
--- 'CryptoError_ParameterInvalid'.
+-- parallelism outside the range it accepts, or a password or salt of 2^32
+-- bytes or more -- are reported as 'CryptoError_ParameterInvalid'.
 hash
     :: (ByteArrayAccess password, ByteArrayAccess salt, ByteArray out)
     => Options
@@ -120,6 +120,9 @@
     | saltLen < saltMinLength = CryptoFailed CryptoError_SaltTooSmall
     | outLen < outputMinLength = CryptoFailed CryptoError_OutputLengthTooSmall
     | outLen > outputMaxLength = CryptoFailed CryptoError_OutputLengthTooBig
+    -- the C keeps these lengths as uint32_t and would truncate them
+    | B.overCLength passwordLen || B.overCLength saltLen =
+        CryptoFailed CryptoError_ParameterInvalid
     | otherwise = unsafeDoIO $ do
         -- the bounds on iterations, memory and parallelism are checked by the
         -- C implementation, which reports them through its return code
diff --git a/Crypto/KDF/BCryptPBKDF.hs b/Crypto/KDF/BCryptPBKDF.hs
--- a/Crypto/KDF/BCryptPBKDF.hs
+++ b/Crypto/KDF/BCryptPBKDF.hs
@@ -27,6 +27,7 @@
     hashInternalFinalize,
     hashInternalInit,
     hashInternalUpdate,
+    updateBlocks,
  )
 import Crypto.Internal.Compat (unsafeDoIO)
 import Data.Bits
@@ -112,7 +113,7 @@
                                                 -- Hash the password.
                                                 let shaPtr = castPtr ctxPtr' :: Ptr (Context SHA512)
                                                 hashInternalInit shaPtr
-                                                hashInternalUpdate shaPtr passPtr (fromIntegral passLen)
+                                                updateBlocks shaPtr passLen passPtr
                                                 hashInternalFinalize shaPtr (castPtr passHashPtr)
                                                 -- Create a stable ByteString view of the password hash
                                                 -- (passHashFP is not modified after this point).
@@ -125,7 +126,7 @@
                                                     pokeByteOff blkPtr 3 (fromIntegral (block `shiftR` 0 :: Int) :: Word8)
                                                     -- First round (slightly different).
                                                     hashInternalInit shaPtr
-                                                    hashInternalUpdate shaPtr saltPtr (fromIntegral saltLen)
+                                                    updateBlocks shaPtr saltLen saltPtr
                                                     hashInternalUpdate shaPtr blkPtr (fromIntegral blkLen)
                                                     hashInternalFinalize shaPtr (castPtr saltHashPtr)
                                                     let saltHashBS = BSI.fromForeignPtr saltHashFP 0 hashLen
diff --git a/Crypto/KDF/PBKDF2.hs b/Crypto/KDF/PBKDF2.hs
--- a/Crypto/KDF/PBKDF2.hs
+++ b/Crypto/KDF/PBKDF2.hs
@@ -60,7 +60,7 @@
 data Parameters = Parameters
     { iterCounts :: Int
     -- ^ the number of user-defined iterations for the algorithms. e.g. WPA2 uses 4000.
-    --   (must be > 0)
+    --   (must be > 0, and below 2^32 for the fast functions)
     , outputLength :: Int
     -- ^ the number of bytes to generate out of PBKDF2
     --   (must not be negative)
@@ -76,6 +76,14 @@
     | outputLength params < 0 = Just CryptoError_ParameterInvalid
     | otherwise = Nothing
 
+-- | 'validateParameters', and an iteration count the C can take: it is a
+-- @uint32_t@ there, so 2^32 + 1 would silently run once.
+validateFastParameters :: Parameters -> Maybe CryptoError
+validateFastParameters params
+    | toInteger (iterCounts params) > toInteger (maxBound :: Word32) =
+        Just CryptoError_ParameterInvalid
+    | otherwise = validateParameters params
+
 -- | generate the pbkdf2 key derivation function from the output
 --
 -- Parameters outside the ranges documented for t'Parameters' raise
@@ -172,7 +180,7 @@
     -> salt
     -> CryptoFailable out
 tryFastPBKDF2_SHA1 params password salt
-    | Just err <- validateParameters params = CryptoFailed err
+    | Just err <- validateFastParameters params = CryptoFailed err
     | otherwise = CryptoPassed $ B.allocAndFreeze (outputLength params) $ \outPtr ->
         B.withByteArray password $ \passPtr ->
             B.withByteArray salt $ \saltPtr ->
@@ -208,7 +216,7 @@
     -> salt
     -> CryptoFailable out
 tryFastPBKDF2_SHA256 params password salt
-    | Just err <- validateParameters params = CryptoFailed err
+    | Just err <- validateFastParameters params = CryptoFailed err
     | otherwise = CryptoPassed $ B.allocAndFreeze (outputLength params) $ \outPtr ->
         B.withByteArray password $ \passPtr ->
             B.withByteArray salt $ \saltPtr ->
@@ -244,7 +252,7 @@
     -> salt
     -> CryptoFailable out
 tryFastPBKDF2_SHA512 params password salt
-    | Just err <- validateParameters params = CryptoFailed err
+    | Just err <- validateFastParameters params = CryptoFailed err
     | otherwise = CryptoPassed $ B.allocAndFreeze (outputLength params) $ \outPtr ->
         B.withByteArray password $ \passPtr ->
             B.withByteArray salt $ \saltPtr ->
diff --git a/Crypto/KDF/Scrypt.hs b/Crypto/KDF/Scrypt.hs
--- a/Crypto/KDF/Scrypt.hs
+++ b/Crypto/KDF/Scrypt.hs
@@ -34,9 +34,9 @@
     { n :: Word64
     -- ^ Cpu/Memory cost ratio. must be a power of 2 greater than 1. also known as N.
     , r :: Int
-    -- ^ Must satisfy r * p < 2^30
+    -- ^ Must be positive and satisfy r * p < 2^30
     , p :: Int
-    -- ^ Must satisfy r * p < 2^30
+    -- ^ Must be positive and satisfy r * p < 2^30
     , outputLength :: Int
     -- ^ the number of bytes to generate out of Scrypt
     }
@@ -60,7 +60,9 @@
 -- | Generate the scrypt key derivation data, reporting parameters the
 -- implementation refuses rather than raising.
 --
--- @n@ has to be a power of two, and @r@ times @p@ has to stay below 2^30.
+-- @n@ has to be a power of two greater than 1, @r@ and @p@ positive, @r@
+-- times @p@ below 2^30, and the 128 * @r@ * @n@ bytes of working memory
+-- addressable.
 tryGenerate
     :: (ByteArrayAccess password, ByteArrayAccess salt, ByteArray output)
     => Parameters
@@ -68,8 +70,7 @@
     -> salt
     -> CryptoFailable output
 tryGenerate params password salt
-    | r params * p params >= 0x40000000 = CryptoFailed CryptoError_ParameterInvalid
-    | popCount (n params) /= 1 = CryptoFailed CryptoError_ParameterInvalid
+    | not (validParameters params) = CryptoFailed CryptoError_ParameterInvalid
     | otherwise = CryptoPassed $ unsafeDoIO $ do
         let b = PBKDF2.generate prf (PBKDF2.Parameters 1 intLen) password salt :: B.Bytes
         newSalt <- B.copy b $ \bPtr ->
@@ -93,3 +94,19 @@
     prf = PBKDF2.prfHMAC SHA256
     intLen = p params * 128 * r params
 {-# NOINLINE tryGenerate #-}
+
+-- Checked in Integer, so that no product can wrap before it is compared.
+validParameters :: Parameters -> Bool
+validParameters params =
+    n params >= 2
+        && popCount (n params) == 1
+        && r' >= 1
+        && p' >= 1
+        && r' * p' < 0x40000000
+        && 128 * r' * n' <= maxInt
+        && 128 * r' * p' <= maxInt
+  where
+    n' = toInteger (n params)
+    r' = toInteger (r params)
+    p' = toInteger (p params)
+    maxInt = toInteger (maxBound :: Int)
diff --git a/Crypto/MAC/CMAC.hs b/Crypto/MAC/CMAC.hs
--- a/Crypto/MAC/CMAC.hs
+++ b/Crypto/MAC/CMAC.hs
@@ -17,7 +17,7 @@
     subKeys,
 ) where
 
-import Data.Bits (setBit, shiftL, testBit)
+import Data.Bits (setBit, shiftL, shiftR, (.&.), (.|.))
 import Data.ByteString (ByteString)
 import qualified Data.ByteString as S
 import Data.Word
@@ -97,13 +97,12 @@
     k1 = subKey ipt k0
     k2 = subKey ipt k1
 
--- polynomial multiply operation to culculate subkey
+-- polynomial multiply operation to culculate subkey; the top bit is secret,
+-- so it becomes a mask rather than a branch
 subKey :: ByteArray ba => [Word8] -> ba -> ba
 subKey ipt ws = case B.unpack ws of
     [] -> B.empty
-    w : _
-        | testBit w 7 -> B.pack ipt `bxor` shiftL1 ws
-        | otherwise -> shiftL1 ws
+    w : _ -> B.pack (map (.&. negate (w `shiftR` 7)) ipt) `bxor` shiftL1 ws
 
 shiftL1 :: ByteArray ba => ba -> ba
 shiftL1 = B.pack . shiftL1W . B.unpack
@@ -113,13 +112,7 @@
 shiftL1W ws@(_ : ns) = rec' $ zip ws (ns ++ [0])
   where
     rec' [] = []
-    rec' ((x, y) : ps) = w : rec' ps
-      where
-        w
-            | testBit y 7 = setBit sl1 0
-            | otherwise = sl1
-          where
-            sl1 = shiftL x 1
+    rec' ((x, y) : ps) = (shiftL x 1 .|. shiftR y 7) : rec' ps
 
 bxor :: ByteArray ba => ba -> ba -> ba
 bxor = B.bxor
diff --git a/Crypto/MAC/KMAC.hs b/Crypto/MAC/KMAC.hs
--- a/Crypto/MAC/KMAC.hs
+++ b/Crypto/MAC/KMAC.hs
@@ -26,7 +26,7 @@
 
 import qualified Crypto.Hash as H
 import Crypto.Hash.SHAKE (HashSHAKE (..))
-import Crypto.Hash.Types (Digest (..), HashAlgorithm (..))
+import Crypto.Hash.Types (Digest (..), HashAlgorithm (..), updateBlocks)
 import qualified Crypto.Hash.Types as H
 import Crypto.Internal.Builder
 import Crypto.Internal.ByteArray (allocAndFreezePrim)
@@ -48,8 +48,8 @@
     => name -> string -> prefix -> H.Context a
 cshakeInit n s p = H.Context $ B.allocAndFreeze c $ \(ptr :: Ptr (H.Context a)) -> do
     hashInternalInit ptr
-    B.withByteArray b $ \d -> hashInternalUpdate ptr d (fromIntegral $ B.length b)
-    B.withByteArray p $ \d -> hashInternalUpdate ptr d (fromIntegral $ B.length p)
+    B.withByteArray b $ updateBlocks ptr (B.length b)
+    B.withByteArray p $ updateBlocks ptr (B.length p)
   where
     c = hashInternalContextSize (undefined :: a)
     w = hashBlockSize (undefined :: a)
@@ -74,8 +74,7 @@
     allocAndFreezePrim (hashDigestSize (undefined :: a)) $
         \(dig :: Ptr (Digest a)) -> do
             ((!_) :: B.Bytes) <- B.copy c $ \(ctx :: Ptr (H.Context a)) -> do
-                B.withByteArray s $ \d ->
-                    hashInternalUpdate ctx d (fromIntegral $ B.length s)
+                B.withByteArray s $ updateBlocks ctx (B.length s)
                 cshakeInternalFinalize ctx dig
             return ()
 
diff --git a/Crypto/PubKey/ECC/P256.hs b/Crypto/PubKey/ECC/P256.hs
--- a/Crypto/PubKey/ECC/P256.hs
+++ b/Crypto/PubKey/ECC/P256.hs
@@ -102,9 +102,11 @@
 -- Using the curve generator as base point compute:
 --
 -- > scalar * G
+--
+-- Zero gives the point at infinity, as P-384 and P-521 do.
 toPoint :: Scalar -> Point
 toPoint s
-    | scalarIsZero s = error "cannot create point from zero"
+    | scalarIsZero s = Point (B.zero pointSize)
     | otherwise =
         withNewPoint $ \px py -> withScalar s $ \p ->
             ccrypton_p256_basepoint_mul p px py
@@ -120,9 +122,12 @@
 
 -- | Negate a point
 pointNegate :: Point -> Point
-pointNegate a = withNewPoint $ \dx dy ->
-    withPoint a $ \ax ay ->
-        ccrypton_p256e_point_negate ax ay dx dy
+pointNegate a
+    -- infinity is held as (0, 0), and p - 0 would make it (0, p)
+    | pointIsAtInfinity a = a
+    | otherwise = withNewPoint $ \dx dy ->
+        withPoint a $ \ax ay ->
+            ccrypton_p256e_point_negate ax ay dx dy
 
 -- | Multiply a point by a scalar
 --
diff --git a/Crypto/PubKey/MaskGenFunction.hs b/Crypto/PubKey/MaskGenFunction.hs
--- a/Crypto/PubKey/MaskGenFunction.hs
+++ b/Crypto/PubKey/MaskGenFunction.hs
@@ -14,6 +14,7 @@
 import Control.Monad (forM_)
 import Crypto.Hash (Context, Digest)
 import Crypto.Hash.IO (HashAlgorithm (..))
+import Crypto.Hash.Types (updateBlocks)
 import Crypto.Internal.ByteArray (ByteArray, ByteArrayAccess)
 import qualified Crypto.Internal.ByteArray as B
 import Data.Bits (shiftR)
@@ -49,8 +50,7 @@
                 allocaBytes digestLen $ \(dig :: Ptr (Digest hashAlg)) ->
                     allocaBytes 4 $ \(cnt :: Ptr Word8) -> do
                         hashInternalInit seeded
-                        B.withByteArray seed $ \p ->
-                            hashInternalUpdate seeded p (fromIntegral (B.length seed))
+                        B.withByteArray seed $ updateBlocks seeded (B.length seed)
                         forM_ [0 .. maxCounter - 1] $ \i -> do
                             copyBytes ctx seeded ctxSize
                             forM_ [0 .. 3] $ \k ->
diff --git a/Crypto/PubKey/RSA/Prim.hs b/Crypto/PubKey/RSA/Prim.hs
--- a/Crypto/PubKey/RSA/Prim.hs
+++ b/Crypto/PubKey/RSA/Prim.hs
@@ -23,7 +23,7 @@
 import Crypto.Internal.ByteArray (ByteArray)
 import Crypto.Internal.Compat (unsafeDoIO)
 import Crypto.Number.Basic (numBytes)
-import Crypto.Number.ModArithmetic (expFast, expSafe)
+import Crypto.Number.ModArithmetic (expFast, expSafe, inverse)
 import Crypto.Number.ModArithmetic.Pub (expPubBytes)
 import Crypto.Number.Serialize (i2ospOf_, os2ip)
 import qualified Crypto.Number.Serialize.Internal as Internal
@@ -57,14 +57,20 @@
 
 -- | The CRT answer m for c, raised back to e before it is let out: a fault
 -- in one half gives an m whose gcd with n is the other prime (Boneh, DeMillo
--- and Lipton).  On a mismatch it is computed again without CRT, as OpenSSL does.
+-- and Lipton).  On a mismatch it is computed again without CRT, as OpenSSL does,
+-- with d worked out from p and q for a key that does not carry it.
 crtChecked :: PrivateKey -> Integer -> Integer -> Integer
 crtChecked pk c m
-    | expFast m (public_e (private_pub pk)) n == c `mod` n = m
+    | expFast m e n == c `mod` n = m
     | private_d pk > 0 = expSafe c (private_d pk) n
-    | otherwise = m
+    | otherwise = case inverse e (lcm (p - 1) (q - 1)) of
+        Just d -> expSafe c d n
+        Nothing -> error "RSA: CRT result failed its check and no private exponent"
   where
     n = private_n pk
+    e = public_e (private_pub pk)
+    p = private_p pk
+    q = private_q pk
 
 -- | c^dP mod p and c^dQ mod q, the halves of the private-key operation: both
 -- at once where the C can (AVX-512 IFMA, and the sizes of RSA-2048, -3072 and
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -80,13 +80,19 @@
 
 The RSA rows in the tables below are the unblinded path.  A fresh blinder
 costs one more exponentiation, by the public exponent, which is the cheap
-direction: measured on the M4, signing goes from about 316 to about 331
-microseconds, under five per cent.  `signSafer` and `decryptSafer` pay less
-than that from 2.2.4: they keep the blinder of a key they have seen and
-square it for the next use, as OpenSSL does, drawing a fresh one every 32
-uses.  `Crypto.PubKey.RSA.Blinded` gives a caller the same thing to hold, a
+direction: measured on the M4, signing goes from about 316 to about 335
+microseconds, about six per cent.  `signSafer` and `decryptSafer` pay less
+than that from 2.2.4: they keep the blinder of a key they have seen and square
+it for the next use, as OpenSSL does, drawing a fresh one every 32 uses.
+`Crypto.PubKey.RSA.Blinded` gives a caller the same thing to hold, a
 `BlindedKey` passed along from one operation to the next.
 
+From 2.2.5 every private-key operation also raises its result back to the
+public exponent before letting it out, as OpenSSL does.  A fault in one half
+of a CRT computation would otherwise give an answer that reveals a prime of
+the key; that answer is now computed again without CRT instead.  It costs a
+few per cent of a signature, and the tables below include it.
+
 Performance
 -----------
 
@@ -115,33 +121,33 @@
 
 Throughput in MB/s, **higher is better**:
 
-| | crypton 1.1.5 | crypton 2.2.4 | OpenSSL | 2.2.4 / OpenSSL |
+| | crypton 1.1.5 | crypton 2.2.5 | OpenSSL | 2.2.5 / OpenSSL |
 | --- | ---: | ---: | ---: | ---: |
-| AES-128-GCM | 1335 | **6041** | 4066 | 1.49 |
-| AES-256-GCM | 1093 | **5492** | 3766 | 1.46 |
-| ChaCha20-Poly1305 | 399 | 2195 | 2222 | 0.99 |
-| SHA-1 | 727 | 1663 | 1672 | 0.99 |
-| SHA-256 | 289 | 1585 | 1576 | 1.01 |
-| SHA-512 | 448 | 770 | 749 | 1.03 |
-| SHA3-256 | 109 | 421 | 429 | 0.98 |
+| AES-128-GCM | 1335 | **6044** | 4046 | 1.49 |
+| AES-256-GCM | 1093 | **5494** | 3764 | 1.46 |
+| ChaCha20-Poly1305 | 399 | 2197 | 2212 | 0.99 |
+| SHA-1 | 729 | 1663 | 1673 | 0.99 |
+| SHA-256 | 289 | 1585 | 1571 | 1.01 |
+| SHA-512 | 450 | 770 | 749 | 1.03 |
+| SHA3-256 | 109 | 421 | 425 | 0.99 |
 
 Time per operation in microseconds, **lower is better**:
 
-| | crypton 1.1.5 | crypton 2.2.4 | OpenSSL | OpenSSL / 2.2.4 |
+| | crypton 1.1.5 | crypton 2.2.5 | OpenSSL | OpenSSL / 2.2.5 |
 | --- | ---: | ---: | ---: | ---: |
-| X25519 | 44.38 | 28.29 | 36.61 | 1.29 |
-| ECDH P-256 | 164.0 | 51.31 | 51.70 | 1.01 |
-| ECDH P-384 | 2242 | **164.3** | 836.1 | 5.09 |
-| Ed25519 sign | 43.86 | 18.50 | 33.74 | 1.82 |
-| Ed25519 verify | 48.30 | 48.43 | 109.8 | 2.27 |
-| ECDSA P-256 sign | 81.80 | 19.72 | 21.84 | 1.11 |
-| ECDSA P-256 verify | 231.8 | 70.47 | 67.55 | 0.96 |
-| ECDSA P-384 sign | 2267 | **305.2** | 879.5 | 2.88 |
-| ECDSA P-384 verify | 2698 | **473.6** | 725.2 | 1.53 |
-| RSA-2048 sign/decrypt | 760.1 | 612.3 | 659.6 | 1.08 |
-| RSA-2048 verify/encrypt | 33.61 | 23.50 | 18.88 | 0.80 |
-| RSA-3072 sign/decrypt | 2499 | 2324 | 2012 | 0.87 |
-| RSA-3072 verify/encrypt | 66.80 | 61.91 | 40.14 | 0.65 |
+| X25519 | 44.31 | 28.56 | 36.51 | 1.28 |
+| ECDH P-256 | 164.3 | 50.63 | 51.53 | 1.02 |
+| ECDH P-384 | 2241 | **164.4** | 834.7 | 5.08 |
+| Ed25519 sign | 29.33 | 18.57 | 33.68 | 1.81 |
+| Ed25519 verify | 48.48 | 47.59 | 110.7 | 2.33 |
+| ECDSA P-256 sign | 81.70 | 18.08 | 21.88 | 1.21 |
+| ECDSA P-256 verify | 231.8 | 69.63 | 67.60 | 0.97 |
+| ECDSA P-384 sign | 2273 | **303.9** | 877.6 | 2.89 |
+| ECDSA P-384 verify | 2673 | **474.3** | 722.8 | 1.52 |
+| RSA-2048 sign/decrypt | 759.5 | 633.9 | 659.4 | 1.04 |
+| RSA-2048 verify/encrypt | 33.24 | 22.70 | 18.80 | 0.83 |
+| RSA-3072 sign/decrypt | 2500 | 2232 | 2010 | 0.90 |
+| RSA-3072 verify/encrypt | 65.79 | 61.73 | 40.09 | 0.65 |
 
 ### AArch64
 
@@ -150,37 +156,37 @@
 
 Throughput in MB/s, **higher is better**:
 
-| | crypton 1.1.5 | crypton 2.2.4 | OpenSSL | 2.2.4 / OpenSSL |
+| | crypton 1.1.5 | crypton 2.2.5 | OpenSSL | 2.2.5 / OpenSSL |
 | --- | ---: | ---: | ---: | ---: |
-| AES-128-GCM | 131 | **12052** | 10922 | 1.10 |
-| AES-256-GCM | 99 | **10192** | 9208 | 1.11 |
-| ChaCha20-Poly1305 | 783 | 2342 | 2261 | 1.04 |
-| SHA-1 | 1234 | 3383 | 3347 | 1.01 |
-| SHA-256 | 486 | 3392 | 3354 | 1.01 |
-| SHA-512 | 742 | 1869 | 1860 | 1.00 |
-| SHA3-256 | 552 | 1096 | 1092 | 1.00 |
+| AES-128-GCM | 134 | **12850** | 11215 | 1.15 |
+| AES-256-GCM | 100 | **10356** | 9405 | 1.10 |
+| ChaCha20-Poly1305 | 804 | 2425 | 2302 | 1.05 |
+| SHA-1 | 1286 | 3389 | 3363 | 1.01 |
+| SHA-256 | 500 | 3397 | 3363 | 1.01 |
+| SHA-512 | 762 | 1881 | 1888 | 1.00 |
+| SHA3-256 | 564 | 1125 | 1104 | 1.02 |
 
 Time per operation in microseconds, **lower is better**:
 
-| | crypton 1.1.5 | crypton 2.2.4 | OpenSSL | OpenSSL / 2.2.4 |
+| | crypton 1.1.5 | crypton 2.2.5 | OpenSSL | OpenSSL / 2.2.5 |
 | --- | ---: | ---: | ---: | ---: |
-| X25519 | 17.92 | **12.02** | 15.32 | 1.28 |
-| ECDH P-256 | 67.50 | **19.05** | 24.36 | 1.28 |
-| ECDH P-384 | 3256 | **71.78** | 373.1 | 5.20 |
-| Ed25519 sign | 13.38 | **7.717** | 13.31 | 1.72 |
-| Ed25519 verify | 18.32 | 18.18 | 34.77 | 1.91 |
-| ECDSA P-256 sign | 31.08 | **6.500** | 10.66 | 1.64 |
-| ECDSA P-256 verify | 94.27 | **26.58** | 32.25 | 1.21 |
-| ECDSA P-384 sign | 3225 | **124.0** | 394.2 | 3.18 |
-| ECDSA P-384 verify | 3866 | **203.4** | 332.3 | 1.63 |
-| RSA-2048 sign/decrypt | 451.3 | 318.4 | 321.5 | 1.01 |
-| RSA-2048 verify/encrypt | 18.15 | 11.12 | 8.405 | 0.76 |
-| RSA-3072 sign/decrypt | 1473 | 937.4 | 931.1 | 0.99 |
-| RSA-3072 verify/encrypt | 47.95 | 43.63 | 17.89 | 0.41 |
+| X25519 | 17.43 | **11.10** | 15.29 | 1.38 |
+| ECDH P-256 | 63.98 | **19.70** | 24.14 | 1.23 |
+| ECDH P-384 | 3181 | **68.57** | 371.6 | 5.42 |
+| Ed25519 sign | 12.57 | **7.217** | 13.24 | 1.83 |
+| Ed25519 verify | 17.62 | 17.75 | 34.72 | 1.96 |
+| ECDSA P-256 sign | 30.53 | **6.333** | 10.46 | 1.65 |
+| ECDSA P-256 verify | 91.95 | **26.52** | 31.97 | 1.21 |
+| ECDSA P-384 sign | 3158 | **123.5** | 393.2 | 3.18 |
+| ECDSA P-384 verify | 3790 | **200.5** | 327.0 | 1.63 |
+| RSA-2048 sign/decrypt | 448.6 | 323.6 | 320.5 | 0.99 |
+| RSA-2048 verify/encrypt | 18.12 | 9.683 | 8.366 | 0.86 |
+| RSA-3072 sign/decrypt | 1468 | 952.9 | 925.6 | 0.97 |
+| RSA-3072 verify/encrypt | 48.07 | 20.47 | 17.79 | 0.87 |
 
 ### What the numbers say
 
-There are two changes behind the 1.1.5 column and the 2.2.4 one, not a
+There are two changes behind the 1.1.5 column and the 2.2.5 one, not a
 single steady improvement.
 
 The first, in 2.0.0, was a rewrite: the bulk algorithms moved into C, the
@@ -208,33 +214,39 @@
 it is Apache-2.0 only, and Intel and CloudFlare hold copyright in it besides
 OpenSSL, so nobody is in a position to relicense it.
 
-crypton is behind in a few rows now: RSA verification on both architectures,
-and RSA-3072 signing and ECDSA P-256 verification on x86-64.  The AES-GCM rows
-were the other half of this section until 2.1.5; they are ahead on both
-machines now, and what the instructions do is still worth setting out.
+crypton is behind in a few rows now: RSA on both architectures, except
+RSA-2048 signing on x86-64, and ECDSA P-256 verification on x86-64.  The
+AES-GCM rows were the other half of this section until 2.1.5; they are ahead
+on both machines now, and what the instructions do is still worth setting out.
 
 *RSA.*  2.0.0 made signing slower than 1.1.5 on purpose: its modular
 exponentiation stopped indexing a table with the bits of the exponent, and
-hiding the exponent is what the difference bought.  2.2.4 has repaid that
-cost on both machines, by different means.  On x86-64 s2n-bignum's
-Montgomery multiplication is twice the C's, because the C cannot form the two
-carry chains `ADCX` and `ADOX` give, and 2.2.4 signs in less than 1.1.5
-took, at 3072 bits as well as 2048.  On AArch64 s2n-bignum's routines measure
-no better than the C, so the gain there is the C's own: the multiplication
-and squaring for each key size are generated and unrolled, with the
-reduction folded into each column as in Koç, Acar and Kaliski's FIPS method,
-and signing is level with OpenSSL.  The measurements are in
-[#275](https://github.com/kazu-yamamoto/crypton/issues/275).
+hiding the exponent is what the difference bought.  2.2.5 has repaid that cost
+on both machines, by different means.  On x86-64 s2n-bignum's Montgomery
+multiplication is twice the C's, because the C cannot form the two carry
+chains `ADCX` and `ADOX` give, and 2.2.5 signs in less than 1.1.5 took, at
+3072 bits as well as 2048.  On AArch64 s2n-bignum's routines measure no better
+than the C, so the gain there is the C's own: the multiplication and squaring
+for each key size are generated and unrolled, with the reduction folded into
+each column as in Koç, Acar and Kaliski's FIPS method, and signing is within a
+few per cent of OpenSSL, the check of the result included.  The measurements
+are in [#275](https://github.com/kazu-yamamoto/crypton/issues/275).
 
 On processors with AVX-512 IFMA, Intel Xeons from Ice Lake and AMD from Zen 4,
-2.2.4 computes the two halves of the CRT together in 52-bit digits, Gueron and
-Krasnov's method and the one OpenSSL takes there.  Neither machine above has
-IFMA, so neither table shows it.
+crypton computes the two halves of the CRT together in 52-bit digits, Gueron
+and Krasnov's method and the one OpenSSL takes there.  Neither machine above
+has IFMA, so neither table shows it.
 
 Verification's exponent is 65537, seventeen bits, so there is little
-exponentiation to speed up and the work around it counts.  2.2.4 keeps R^2,
-the Montgomery constant of a modulus, for the moduli it has seen, which is
-most of what the RSA-2048 verification rows gained.
+exponentiation to speed up and the work around it counts.  crypton keeps R^2,
+the Montgomery constant of a modulus, for the moduli it has seen, and from
+2.2.5 hands the signature to the C as bytes and builds the padding without a
+list.  On macOS it verifies in its own C at every RSA size: the GMP that GHC
+builds into itself there is compiled without optimisation
+([ghc#28030](https://gitlab.haskell.org/ghc/ghc/-/work_items/28030)), which is
+most of what the RSA-3072 verification row on AArch64 gained.  Elsewhere GMP
+keeps the sizes other than 2048 bits, and on x86-64 it is the reason the
+RSA-3072 verification row is still where it was.
 
 *The wide AES instructions.*  `VAES` and `VPCLMULQDQ` do two blocks where
 `AES-NI` and `PCLMULQDQ` do one, and four in their 512-bit form.  crypton uses
@@ -253,7 +265,7 @@
 takes a block at a time where crypton takes two.  The same idea as theirs,
 one step further down the feature ladder; not a better one.
 
-The 512-bit path arrived after 2.1.2, so it is in the 2.2.4 column -- but
+The 512-bit path arrived after 2.1.2, so it is in the 2.2.5 column -- but
 neither machine in the tables above has AVX-512, so neither column shows it.
 On the runners that do, measured over 16 KiB in MB/s: an EPYC 9V45 (Zen 5)
 goes from 9616 to 14268 with it, a Xeon 6973P-C from 8095 to 9848, a Xeon
@@ -278,7 +290,7 @@
 key from the secret key every time it signs, so that a caller who passes a
 public key that does not match cannot be made to leak the private one.  That
 costs a second scalar multiplication, which OpenSSL's signing does not pay --
-and the row is still 1.72 on AArch64 and 1.82 on x86-64, so the safety is had
+and the row is still 1.83 on AArch64 and 1.81 on x86-64, so the safety is had
 for nothing here rather than paid for.
 
 Ed25519 verification wanted one too, for an unhappier reason, and 2.2.1
diff --git a/cbits/aes/armv8_impl.c b/cbits/aes/armv8_impl.c
--- a/cbits/aes/armv8_impl.c
+++ b/cbits/aes/armv8_impl.c
@@ -590,8 +590,8 @@
 	do {                                                                  \
 		poly64x2_t b_ = vreinterpretq_p64_u8(vrev64q_u8(blk));        \
 		if (bn == 0) {                                                \
-			uint32_t left_ = gtotal - gidx;                       \
-			blen = left_ < WAY ? left_ : WAY;                     \
+			uint64_t left_ = gtotal - gidx;                       \
+			blen = (uint32_t) (left_ < WAY ? left_ : WAY);        \
 			acc = vreinterpretq_u64_u8(                           \
 			    vextq_u8(vreinterpretq_u8_u64(acc),               \
 			             vreinterpretq_u8_u64(acc), 8));          \
@@ -625,9 +625,12 @@
 	uint8x16_t ek0;
 	uint64x2_t acc = vdupq_n_u64(0), gH = acc, gM = acc, gL = acc;
 	uint32x4_t base;
-	uint32_t c = 1, bn = 0, blen = 0, gidx = 0;
-	uint32_t gtotal = (aadlen + 15) / 16 + (inlen + 15) / 16 + 1;
-	uint32_t i, done;
+	uint32_t c = 1, bn = 0, blen = 0;
+	/* 64-bit: in 32 bits, i + 16 and done + 16 * WAY wrap for lengths
+	 * near 4 GiB and the loops run past the buffers. */
+	uint64_t gidx = 0;
+	uint64_t gtotal = ((uint64_t) aadlen + 15) / 16 + ((uint64_t) inlen + 15) / 16 + 1;
+	uint64_t i, done;
 	uint8_t y0[16], lenb[16];
 	uint64_t la, lc;
 
@@ -720,9 +723,12 @@
 	uint8x16_t ek0;
 	uint64x2_t acc = vdupq_n_u64(0), gH = acc, gM = acc, gL = acc;
 	uint32x4_t base;
-	uint32_t c = 1, bn = 0, blen = 0, gidx = 0;
-	uint32_t gtotal = (aadlen + 15) / 16 + (inlen + 15) / 16 + 1;
-	uint32_t i, done;
+	uint32_t c = 1, bn = 0, blen = 0;
+	/* 64-bit: in 32 bits, i + 16 and done + 16 * WAY wrap for lengths
+	 * near 4 GiB and the loops run past the buffers. */
+	uint64_t gidx = 0;
+	uint64_t gtotal = ((uint64_t) aadlen + 15) / 16 + ((uint64_t) inlen + 15) / 16 + 1;
+	uint64_t i, done;
 	uint8_t y0[16], lenb[16], want[16];
 	uint64_t la, lc;
 	uint8_t diff = 0;
diff --git a/cbits/aes/gf.c b/cbits/aes/gf.c
--- a/cbits/aes/gf.c
+++ b/cbits/aes/gf.c
@@ -38,10 +38,10 @@
 /* inplace GFMUL for xts mode */
 void crypton_aes_generic_gf_mulx(block128 *a)
 {
-	const uint64_t gf_mask = cpu_to_le64(0x8000000000000000ULL);
-	uint64_t r = ((a->q[1] & gf_mask) ? cpu_to_le64(0x87) : 0);
-	a->q[1] = cpu_to_le64((le64_to_cpu(a->q[1]) << 1) | (a->q[0] & gf_mask ? 1 : 0));
-	a->q[0] = cpu_to_le64(le64_to_cpu(a->q[0]) << 1) ^ r;
+	/* the carries are secret: shifts and a mask, not branches */
+	uint64_t lo = le64_to_cpu(a->q[0]), hi = le64_to_cpu(a->q[1]);
+	a->q[1] = cpu_to_le64((hi << 1) | (lo >> 63));
+	a->q[0] = cpu_to_le64((lo << 1) ^ ((0 - (hi >> 63)) & 0x87));
 }
 
 
diff --git a/cbits/crypton_aes.c b/cbits/crypton_aes.c
--- a/cbits/crypton_aes.c
+++ b/cbits/crypton_aes.c
@@ -28,6 +28,7 @@
  * SUCH DAMAGE.
  */
 
+#include <stddef.h>
 #include <string.h>
 #include <stdio.h>
 
@@ -612,16 +613,44 @@
 	d(output, gcm, key, input, length);
 }
 
-void crypton_aes_ccm_encrypt(uint8_t *output, aes_ccm *ccm, aes_key *key, uint8_t *input, uint32_t length)
+/*
+ * A CCM context takes the additional data at most once and then the whole
+ * message in one call, of exactly the length given at init.  Anything else
+ * fails the context: the output is zeroed and no tag is produced, since the
+ * CBC-MAC would no longer cover what was processed.
+ */
+#define CCM_PHASE_INIT   0
+#define CCM_PHASE_AAD    1
+#define CCM_PHASE_DATA   2
+#define CCM_PHASE_FAILED 3
+
+static int ccm_data_ok(uint8_t *output, aes_ccm *ccm, uint32_t length)
 {
+	if (ccm->phase > CCM_PHASE_AAD || length != ccm->length_input) {
+		ccm->phase = CCM_PHASE_FAILED;
+		memset(output, 0, length);
+		return 0;
+	}
+	ccm->phase = CCM_PHASE_DATA;
+	return 1;
+}
+
+int crypton_aes_ccm_encrypt(uint8_t *output, aes_ccm *ccm, aes_key *key, uint8_t *input, uint32_t length)
+{
 	ccm_crypt_f e = GET_CCM_ENCRYPT(key->strength);
+	if (!ccm_data_ok(output, ccm, length))
+		return 0;
 	e(output, ccm, key, input, length);
+	return 1;
 }
 
-void crypton_aes_ccm_decrypt(uint8_t *output, aes_ccm *ccm, aes_key *key, uint8_t *input, uint32_t length)
+int crypton_aes_ccm_decrypt(uint8_t *output, aes_ccm *ccm, aes_key *key, uint8_t *input, uint32_t length)
 {
 	ccm_crypt_f d = GET_CCM_DECRYPT(key->strength);
+	if (!ccm_data_ok(output, ccm, length))
+		return 0;
 	d(output, ccm, key, input, length);
+	return 1;
 }
 
 void crypton_aes_ocb_encrypt(uint8_t *output, aes_ocb *ocb, aes_key *key, uint8_t *input, uint32_t length)
@@ -708,6 +737,9 @@
 	gcm_message_init(gcm, iv, len);
 }
 
+/* Crypto.Cipher.AES.Primitive reads length_input at this offset */
+typedef char crypton_gcm_length_input_offset[offsetof(aes_gcm, length_input) == 312 ? 1 : -1];
+
 void crypton_aes_gcm_aad(aes_gcm *gcm, uint8_t *input, uint32_t length)
 {
 	gcm->length_aad += length;
@@ -995,18 +1027,17 @@
 void crypton_aes_ccm_init(aes_ccm *ccm, aes_key *key, uint8_t *nonce, uint32_t nonce_len, uint32_t input_size, int m, int l)
 {
 	memset(ccm, 0, sizeof(aes_ccm));
+	ccm->phase = CCM_PHASE_FAILED;
 
 	if (l < 2 || l > 4) return;
 	if (m != 4 && m != 6 && m != 8 && m != 10
 		   && m != 12 && m != 14 && m != 16) return;
 
-	if (nonce_len > 15 - l) {
-		nonce_len = 15 - l;
-	}
+	/* the nonce is exactly 15 - L bytes, or the message length overlaps it */
+	if (nonce_len != (uint32_t) (15 - l)) return;
+	if (input_size >= (1ull << (8*l))) return;
 
-	if (l <= 4) {
-		if (input_size >= (1ull << (8*l))) return;
-	}
+	ccm->phase = CCM_PHASE_INIT;
 
 	ccm->length_L = l;
 	ccm->length_M = m;
@@ -1025,8 +1056,11 @@
 {
 	block128 tmp;
 
-	if (ccm->length_aad != 0) return;
-
+	if (ccm->phase != CCM_PHASE_INIT) {
+		ccm->phase = CCM_PHASE_FAILED;
+		return;
+	}
+	ccm->phase = CCM_PHASE_AAD;
 	ccm->length_aad = length;
 	int len_len;
 
@@ -1057,14 +1091,26 @@
 	block128_copy_aligned(&ccm->header_cbcmac, &ccm->xi);
 }
 
-void crypton_aes_ccm_finish(uint8_t *tag, aes_ccm *ccm, aes_key *key)
+int crypton_aes_ccm_finish(uint8_t *tag, aes_ccm *ccm, aes_key *key)
 {
 	block128 iv, s0;
 
+	/* no data call is fine only for an empty message */
+	if (ccm->phase == CCM_PHASE_FAILED
+	    || (ccm->phase != CCM_PHASE_DATA && ccm->length_input != 0)) {
+		memset(tag, 0, 16);
+		return 0;
+	}
+	if (ccm->phase != CCM_PHASE_DATA && ccm->length_aad == 0) {
+		ccm_encode_b0(&ccm->b0, ccm, 0);
+		crypton_aes_encrypt_block(&ccm->xi, key, &ccm->b0);
+	}
+
 	block128_zero(&iv);
 	ccm_encode_ctr(&iv, ccm, 0);
 	crypton_aes_encrypt_block(&s0, key, &iv);
 	block128_vxor((block128*)tag, &ccm->xi, &s0);
+	return 1;
 }
 
 static inline void ocb_block_double(block128 *d, block128 *s)
diff --git a/cbits/crypton_aes.h b/cbits/crypton_aes.h
--- a/cbits/crypton_aes.h
+++ b/cbits/crypton_aes.h
@@ -104,7 +104,7 @@
 	aes_gcm_fused fused;
 } aes_gcm_key;
 
-/* size = 4*16+4*4= 80 */
+/* size = 4*16+5*4= 84 */
 typedef struct {
 	aes_block xi;
 	aes_block header_cbcmac;
@@ -114,6 +114,7 @@
 	uint32_t length_input;
 	uint32_t length_M;
 	uint32_t length_L;
+	uint32_t phase; /* CCM_PHASE_* in crypton_aes.c */
 } aes_ccm;
 
 typedef struct {
@@ -187,9 +188,9 @@
 
 void crypton_aes_ccm_init(aes_ccm *ccm, aes_key *key, uint8_t *nonce, uint32_t len, uint32_t msg_size, int m, int l);
 void crypton_aes_ccm_aad(aes_ccm *ccm, aes_key *key, uint8_t *input, uint32_t length);
-void crypton_aes_ccm_encrypt(uint8_t *output, aes_ccm *ccm, aes_key *key, uint8_t *input, uint32_t length);
-void crypton_aes_ccm_decrypt(uint8_t *output, aes_ccm *ccm, aes_key *key, uint8_t *input, uint32_t length);
-void crypton_aes_ccm_finish(uint8_t *tag, aes_ccm *ccm, aes_key *key);
+int crypton_aes_ccm_encrypt(uint8_t *output, aes_ccm *ccm, aes_key *key, uint8_t *input, uint32_t length);
+int crypton_aes_ccm_decrypt(uint8_t *output, aes_ccm *ccm, aes_key *key, uint8_t *input, uint32_t length);
+int crypton_aes_ccm_finish(uint8_t *tag, aes_ccm *ccm, aes_key *key);
 
 uint8_t *crypton_aes_cpu_init(void);
 
diff --git a/cbits/crypton_powm.c b/cbits/crypton_powm.c
--- a/cbits/crypton_powm.c
+++ b/cbits/crypton_powm.c
@@ -70,12 +70,12 @@
  * exponentiation than the padded square.  Both are still nearly twice the
  * C, since the C cannot form ADCX and ADOX's two carry chains at any size.
  *
- * 48 and 64 are 3072 and 4096 bits: finite-field Diffie-Hellman over the
- * RFC 7919 groups of those sizes, and RSA without CRT.  There is no
- * Karatsuba routine at either, and padding to the next would be wasteful,
- * so the multiplication is s2n-bignum's schoolbook one of any size --
- * plain MUL and ADC, no ADX -- and the reduction is the same ADX one.  Not
- * the gain the Karatsuba sizes have, and still well ahead of the C.
+ * 48 and 64 are 3072 and 4096 bits: RSA-3072 and RSA-4096 verification,
+ * finite-field Diffie-Hellman over the RFC 7919 groups of those sizes, and
+ * RSA without CRT.  There is no Karatsuba routine at either.  The square is
+ * built from the 16- and the 32-limb ones, three pieces and two; the
+ * multiplication is s2n-bignum's schoolbook one of any size -- plain MUL and
+ * ADC, no ADX -- and the reduction is the same ADX one.
  *
  * The reduction wants ADX, so the answer is a run-time one. */
 static int powm_s2n_usable(uint32_t n)
@@ -141,41 +141,83 @@
 	powm_s2n_finish(r, z, m, bignum_emontredc_8n(n, z, m, n0), n);
 }
 
-/* a^2 for a of 24 limbs, from three 8-limb squares and three 8-limb
- * products: a = a0 + a1 X + a2 X^2 with X = 2^512, and the cross terms, at
- * X to X^3 in c, go in doubled.  c is 48 limbs of scratch. */
-static void powm_s2n_sqr24(limb_t *z, const limb_t *a, limb_t *c)
+/* One piece of a three-piece square: k limbs, 8 or 16, squared or
+ * multiplied into 2k.  The 16-limb routines want t, 32 limbs of scratch. */
+static void powm_s2n_piece_sqr(limb_t *z, const limb_t *x, limb_t *t, uint32_t k)
 {
-	limb_t k;
+	if (k == 8)
+		bignum_sqr_8_16(z, x);
+	else
+		bignum_ksqr_16_32(z, x, t);
+}
+
+static void powm_s2n_piece_mul(limb_t *z, const limb_t *x, const limb_t *y,
+                               limb_t *t, uint32_t k)
+{
+	if (k == 8)
+		bignum_mul_8_16(z, x, y);
+	else
+		bignum_kmul_16_32(z, x, y, t);
+}
+
+/* a^2 for a of 3k limbs, from three k-limb squares and three k-limb
+ * products: a = a0 + a1 X + a2 X^2 with X = 2^(64k), and the cross terms,
+ * at X to X^3 in c, go in doubled.  c is 6k limbs of scratch.  RSA-3072's
+ * CRT half is k = 8, and its modulus k = 16. */
+static void powm_s2n_sqr3(limb_t *z, const limb_t *a, limb_t *c, limb_t *t,
+                          uint32_t k)
+{
+	limb_t carry;
 	uint32_t i;
 
-	bignum_sqr_8_16(z, a);
-	bignum_sqr_8_16(z + 16, a + 8);
-	bignum_sqr_8_16(z + 32, a + 16);
-	bignum_mul_8_16(c, a, a + 8);
-	bignum_mul_8_16(c + 16, a + 8, a + 16);
-	bignum_mul_8_16(c + 32, a, a + 16);
-	k = add_n(c + 8, c + 8, c + 32, 16);
-	for (i = 24; i < 32; i++) {
-		c[i] += k;
-		k = c[i] < k;
+	powm_s2n_piece_sqr(z, a, t, k);
+	powm_s2n_piece_sqr(z + 2 * k, a + k, t, k);
+	powm_s2n_piece_sqr(z + 4 * k, a + 2 * k, t, k);
+	powm_s2n_piece_mul(c, a, a + k, t, k);
+	powm_s2n_piece_mul(c + 2 * k, a + k, a + 2 * k, t, k);
+	powm_s2n_piece_mul(c + 4 * k, a, a + 2 * k, t, k);
+	carry = add_n(c + k, c + k, c + 4 * k, 2 * k);
+	for (i = 3 * k; i < 4 * k; i++) {
+		c[i] += carry;
+		carry = c[i] < carry;
 	}
-	k = shl1(c, 32);
-	k += add_n(z + 8, z + 8, c, 32);
-	for (i = 40; i < 48; i++) {
-		z[i] += k;
-		k = z[i] < k;
+	carry = shl1(c, 4 * k);
+	carry += add_n(z + k, z + k, c, 4 * k);
+	for (i = 5 * k; i < 6 * k; i++) {
+		z[i] += carry;
+		carry = z[i] < carry;
 	}
 }
 
+/* a^2 for a of 64 limbs, RSA-4096's modulus, from two 32-limb squares and
+ * one product: a = a0 + a1 X with X = 2^2048, and the cross term goes in
+ * doubled at X.  c is 64 limbs and t 96, scratch for the 32-limb routines. */
+static void powm_s2n_sqr64(limb_t *z, const limb_t *a, limb_t *c, limb_t *t)
+{
+	limb_t carry;
+	uint32_t i;
+
+	bignum_ksqr_32_64(z, a, t);
+	bignum_ksqr_32_64(z + 64, a + 32, t);
+	bignum_kmul_32_64(c, a, a + 32, t);
+	carry = shl1(c, 64);
+	carry += add_n(z + 32, z + 32, c, 64);
+	for (i = 96; i < 128; i++) {
+		z[i] += carry;
+		carry = z[i] < carry;
+	}
+}
+
 static void powm_s2n_sqr(limb_t *r, const limb_t *a, const limb_t *m,
                          limb_t n0, uint32_t n, limb_t *z, limb_t *scratch)
 {
 	if (n == 24) {
 		z = scratch + POWM_S2N_PAD_Z;
-		powm_s2n_sqr24(z, a, scratch + POWM_S2N_PAD_T);
-	} else if (n == 48 || n == 64)
-		bignum_sqr(2 * n, z, n, a);
+		powm_s2n_sqr3(z, a, scratch + POWM_S2N_PAD_T, NULL, 8);
+	} else if (n == 48)
+		powm_s2n_sqr3(z, a, scratch, scratch + 96, 16);
+	else if (n == 64)
+		powm_s2n_sqr64(z, a, scratch, scratch + 64);
 	else if (n == 16)
 		bignum_ksqr_16_32(z, a, scratch);
 	else
@@ -389,24 +431,25 @@
 #define POWM_PUB_MAX_EXP_BYTES 8
 
 /* Which sizes this is worth taking at is not which sizes the assembly runs
- * at.  Against GMP, which is what the caller has, on an EPYC 7763 with ADX
- * on both sides: ahead at 2048 bits, and behind everywhere else measured --
- * slightly at 1024, by more at 1536, and by more again at 3072 and 4096,
- * which is the size where GMP's own setup and multiplication are the better
- * pair.  So 2048 alone, which is RSA-2048's modulus and the size nearly
- * every RSA public key has.  It once took every size the assembly ran at,
- * on the strength of 2048 alone, and was slower than GMP at 1024. */
+ * at.  Against GMP, which is what the caller has, with ADX on both sides:
+ * ahead at 2048 bits, and at 3072 and 4096 since their squares are built
+ * from s2n-bignum's 16- and 32-limb Karatsuba routines -- level with GMP on
+ * an Intel core, well ahead on Zen 4.  Through the schoolbook routine they
+ * were behind on both.  1024 and 1536, behind once on an EPYC 7763, are left
+ * to GMP.  It once took every size the assembly ran at, on the strength of
+ * 2048 alone, and was slower than GMP at 1024. */
 static int powm_pub_wanted(uint32_t n)
 {
 #if defined(CRYPTON_POWM_PUB_ALWAYS)
 	return n > 0;
 #elif defined(CRYPTON_POWM_S2N)
-	return n == 32 && powm_s2n_usable(n);
+	return (n == 32 || n == 48 || n == 64) && powm_s2n_usable(n);
 #elif defined(CRYPTON_MONT_FIXED)
 	/* AArch64: the generated code is ahead of GMP at 2048 bits.  At the
 	 * other RSA sizes Linux's GMP is level with the C or ahead of it.  The
-	 * GMP GHC builds into itself on macOS is compiled without -O, and is
-	 * behind the C from 1024 to 4096 bits on the same M4 */
+	 * GMP GHC builds into itself on macOS is compiled without -O
+	 * (ghc/ghc#28030), and is behind the C from 1024 to 4096 bits on the
+	 * same M4 */
 #if defined(__APPLE__)
 	return n >= 16 && n <= 64;
 #else
diff --git a/cbits/crypton_powm_ifma.h b/cbits/crypton_powm_ifma.h
--- a/cbits/crypton_powm_ifma.h
+++ b/cbits/crypton_powm_ifma.h
@@ -14,6 +14,14 @@
 #define IFMA_MASK52 ((UINT64_C(1) << 52) - 1)
 #define IFMA_TARGET __attribute__((target("avx2,avx512f,avx512vl,avx512ifma")))
 
+/* a full load the compiler cannot fold into a masked one */
+IFMA_TARGET static inline __m256i ifma_load(const uint64_t *p)
+{
+	__m256i x = _mm256_loadu_si256((const __m256i *) p);
+	__asm__("" : "+v"(x));
+	return x;
+}
+
 /* 20 lanes to 52-bit digits: what is above 52 bits moves up a lane,
  * and the one-bit carries that can then remain are resolved together */
 IFMA_TARGET static inline void ifma_norm5(uint64_t *r, __m256i X0, __m256i X1, __m256i X2, __m256i X3, __m256i X4)
@@ -54,8 +62,9 @@
 	_mm256_storeu_si256((__m256i *)(r + 16), X4);
 }
 
-/* both tables' entries k1 and k2 of 32, every entry read and a mask keeping
- * one; entries are 20 words apart */
+/* both tables' entries k1 and k2 of 32: every entry loaded in full and a
+ * blend keeping one, since a masked load may not touch what it drops;
+ * entries are 20 words apart */
 IFMA_TARGET static void ifma_sel5(uint64_t *o1, const uint64_t *t1, uint64_t k1,
                                 uint64_t *o2, const uint64_t *t2, uint64_t k2)
 {
@@ -67,11 +76,16 @@
 	for (k = 0; k < 32; k++) {
 		__mmask8 s1 = _mm256_cmpeq_epi64_mask(v1, kv), s2 = _mm256_cmpeq_epi64_mask(v2, kv);
 		const uint64_t *e1 = t1 + 20 * k, *e2 = t2 + 20 * k;
-		P0 = _mm256_mask_loadu_epi64(P0, s1, e1 + 0); Q0 = _mm256_mask_loadu_epi64(Q0, s2, e2 + 0);
-		P1 = _mm256_mask_loadu_epi64(P1, s1, e1 + 4); Q1 = _mm256_mask_loadu_epi64(Q1, s2, e2 + 4);
-		P2 = _mm256_mask_loadu_epi64(P2, s1, e1 + 8); Q2 = _mm256_mask_loadu_epi64(Q2, s2, e2 + 8);
-		P3 = _mm256_mask_loadu_epi64(P3, s1, e1 + 12); Q3 = _mm256_mask_loadu_epi64(Q3, s2, e2 + 12);
-		P4 = _mm256_mask_loadu_epi64(P4, s1, e1 + 16); Q4 = _mm256_mask_loadu_epi64(Q4, s2, e2 + 16);
+		P0 = _mm256_mask_blend_epi64(s1, P0, ifma_load(e1 + 0));
+		Q0 = _mm256_mask_blend_epi64(s2, Q0, ifma_load(e2 + 0));
+		P1 = _mm256_mask_blend_epi64(s1, P1, ifma_load(e1 + 4));
+		Q1 = _mm256_mask_blend_epi64(s2, Q1, ifma_load(e2 + 4));
+		P2 = _mm256_mask_blend_epi64(s1, P2, ifma_load(e1 + 8));
+		Q2 = _mm256_mask_blend_epi64(s2, Q2, ifma_load(e2 + 8));
+		P3 = _mm256_mask_blend_epi64(s1, P3, ifma_load(e1 + 12));
+		Q3 = _mm256_mask_blend_epi64(s2, Q3, ifma_load(e2 + 12));
+		P4 = _mm256_mask_blend_epi64(s1, P4, ifma_load(e1 + 16));
+		Q4 = _mm256_mask_blend_epi64(s2, Q4, ifma_load(e2 + 16));
 		kv = _mm256_add_epi64(kv, one);
 	}
 	_mm256_storeu_si256((__m256i *)(o1 + 0), P0); _mm256_storeu_si256((__m256i *)(o2 + 0), Q0);
@@ -139,8 +153,9 @@
 	_mm256_storeu_si256((__m256i *)(r + 28), X7);
 }
 
-/* both tables' entries k1 and k2 of 32, every entry read and a mask keeping
- * one; entries are 32 words apart */
+/* both tables' entries k1 and k2 of 32: every entry loaded in full and a
+ * blend keeping one, since a masked load may not touch what it drops;
+ * entries are 32 words apart */
 IFMA_TARGET static void ifma_sel8(uint64_t *o1, const uint64_t *t1, uint64_t k1,
                                 uint64_t *o2, const uint64_t *t2, uint64_t k2)
 {
@@ -152,14 +167,22 @@
 	for (k = 0; k < 32; k++) {
 		__mmask8 s1 = _mm256_cmpeq_epi64_mask(v1, kv), s2 = _mm256_cmpeq_epi64_mask(v2, kv);
 		const uint64_t *e1 = t1 + 32 * k, *e2 = t2 + 32 * k;
-		P0 = _mm256_mask_loadu_epi64(P0, s1, e1 + 0); Q0 = _mm256_mask_loadu_epi64(Q0, s2, e2 + 0);
-		P1 = _mm256_mask_loadu_epi64(P1, s1, e1 + 4); Q1 = _mm256_mask_loadu_epi64(Q1, s2, e2 + 4);
-		P2 = _mm256_mask_loadu_epi64(P2, s1, e1 + 8); Q2 = _mm256_mask_loadu_epi64(Q2, s2, e2 + 8);
-		P3 = _mm256_mask_loadu_epi64(P3, s1, e1 + 12); Q3 = _mm256_mask_loadu_epi64(Q3, s2, e2 + 12);
-		P4 = _mm256_mask_loadu_epi64(P4, s1, e1 + 16); Q4 = _mm256_mask_loadu_epi64(Q4, s2, e2 + 16);
-		P5 = _mm256_mask_loadu_epi64(P5, s1, e1 + 20); Q5 = _mm256_mask_loadu_epi64(Q5, s2, e2 + 20);
-		P6 = _mm256_mask_loadu_epi64(P6, s1, e1 + 24); Q6 = _mm256_mask_loadu_epi64(Q6, s2, e2 + 24);
-		P7 = _mm256_mask_loadu_epi64(P7, s1, e1 + 28); Q7 = _mm256_mask_loadu_epi64(Q7, s2, e2 + 28);
+		P0 = _mm256_mask_blend_epi64(s1, P0, ifma_load(e1 + 0));
+		Q0 = _mm256_mask_blend_epi64(s2, Q0, ifma_load(e2 + 0));
+		P1 = _mm256_mask_blend_epi64(s1, P1, ifma_load(e1 + 4));
+		Q1 = _mm256_mask_blend_epi64(s2, Q1, ifma_load(e2 + 4));
+		P2 = _mm256_mask_blend_epi64(s1, P2, ifma_load(e1 + 8));
+		Q2 = _mm256_mask_blend_epi64(s2, Q2, ifma_load(e2 + 8));
+		P3 = _mm256_mask_blend_epi64(s1, P3, ifma_load(e1 + 12));
+		Q3 = _mm256_mask_blend_epi64(s2, Q3, ifma_load(e2 + 12));
+		P4 = _mm256_mask_blend_epi64(s1, P4, ifma_load(e1 + 16));
+		Q4 = _mm256_mask_blend_epi64(s2, Q4, ifma_load(e2 + 16));
+		P5 = _mm256_mask_blend_epi64(s1, P5, ifma_load(e1 + 20));
+		Q5 = _mm256_mask_blend_epi64(s2, Q5, ifma_load(e2 + 20));
+		P6 = _mm256_mask_blend_epi64(s1, P6, ifma_load(e1 + 24));
+		Q6 = _mm256_mask_blend_epi64(s2, Q6, ifma_load(e2 + 24));
+		P7 = _mm256_mask_blend_epi64(s1, P7, ifma_load(e1 + 28));
+		Q7 = _mm256_mask_blend_epi64(s2, Q7, ifma_load(e2 + 28));
 		kv = _mm256_add_epi64(kv, one);
 	}
 	_mm256_storeu_si256((__m256i *)(o1 + 0), P0); _mm256_storeu_si256((__m256i *)(o2 + 0), Q0);
@@ -242,8 +265,9 @@
 	_mm256_storeu_si256((__m256i *)(r + 36), X9);
 }
 
-/* both tables' entries k1 and k2 of 32, every entry read and a mask keeping
- * one; entries are 40 words apart */
+/* both tables' entries k1 and k2 of 32: every entry loaded in full and a
+ * blend keeping one, since a masked load may not touch what it drops;
+ * entries are 40 words apart */
 IFMA_TARGET static void ifma_sel10(uint64_t *o1, const uint64_t *t1, uint64_t k1,
                                 uint64_t *o2, const uint64_t *t2, uint64_t k2)
 {
@@ -255,16 +279,26 @@
 	for (k = 0; k < 32; k++) {
 		__mmask8 s1 = _mm256_cmpeq_epi64_mask(v1, kv), s2 = _mm256_cmpeq_epi64_mask(v2, kv);
 		const uint64_t *e1 = t1 + 40 * k, *e2 = t2 + 40 * k;
-		P0 = _mm256_mask_loadu_epi64(P0, s1, e1 + 0); Q0 = _mm256_mask_loadu_epi64(Q0, s2, e2 + 0);
-		P1 = _mm256_mask_loadu_epi64(P1, s1, e1 + 4); Q1 = _mm256_mask_loadu_epi64(Q1, s2, e2 + 4);
-		P2 = _mm256_mask_loadu_epi64(P2, s1, e1 + 8); Q2 = _mm256_mask_loadu_epi64(Q2, s2, e2 + 8);
-		P3 = _mm256_mask_loadu_epi64(P3, s1, e1 + 12); Q3 = _mm256_mask_loadu_epi64(Q3, s2, e2 + 12);
-		P4 = _mm256_mask_loadu_epi64(P4, s1, e1 + 16); Q4 = _mm256_mask_loadu_epi64(Q4, s2, e2 + 16);
-		P5 = _mm256_mask_loadu_epi64(P5, s1, e1 + 20); Q5 = _mm256_mask_loadu_epi64(Q5, s2, e2 + 20);
-		P6 = _mm256_mask_loadu_epi64(P6, s1, e1 + 24); Q6 = _mm256_mask_loadu_epi64(Q6, s2, e2 + 24);
-		P7 = _mm256_mask_loadu_epi64(P7, s1, e1 + 28); Q7 = _mm256_mask_loadu_epi64(Q7, s2, e2 + 28);
-		P8 = _mm256_mask_loadu_epi64(P8, s1, e1 + 32); Q8 = _mm256_mask_loadu_epi64(Q8, s2, e2 + 32);
-		P9 = _mm256_mask_loadu_epi64(P9, s1, e1 + 36); Q9 = _mm256_mask_loadu_epi64(Q9, s2, e2 + 36);
+		P0 = _mm256_mask_blend_epi64(s1, P0, ifma_load(e1 + 0));
+		Q0 = _mm256_mask_blend_epi64(s2, Q0, ifma_load(e2 + 0));
+		P1 = _mm256_mask_blend_epi64(s1, P1, ifma_load(e1 + 4));
+		Q1 = _mm256_mask_blend_epi64(s2, Q1, ifma_load(e2 + 4));
+		P2 = _mm256_mask_blend_epi64(s1, P2, ifma_load(e1 + 8));
+		Q2 = _mm256_mask_blend_epi64(s2, Q2, ifma_load(e2 + 8));
+		P3 = _mm256_mask_blend_epi64(s1, P3, ifma_load(e1 + 12));
+		Q3 = _mm256_mask_blend_epi64(s2, Q3, ifma_load(e2 + 12));
+		P4 = _mm256_mask_blend_epi64(s1, P4, ifma_load(e1 + 16));
+		Q4 = _mm256_mask_blend_epi64(s2, Q4, ifma_load(e2 + 16));
+		P5 = _mm256_mask_blend_epi64(s1, P5, ifma_load(e1 + 20));
+		Q5 = _mm256_mask_blend_epi64(s2, Q5, ifma_load(e2 + 20));
+		P6 = _mm256_mask_blend_epi64(s1, P6, ifma_load(e1 + 24));
+		Q6 = _mm256_mask_blend_epi64(s2, Q6, ifma_load(e2 + 24));
+		P7 = _mm256_mask_blend_epi64(s1, P7, ifma_load(e1 + 28));
+		Q7 = _mm256_mask_blend_epi64(s2, Q7, ifma_load(e2 + 28));
+		P8 = _mm256_mask_blend_epi64(s1, P8, ifma_load(e1 + 32));
+		Q8 = _mm256_mask_blend_epi64(s2, Q8, ifma_load(e2 + 32));
+		P9 = _mm256_mask_blend_epi64(s1, P9, ifma_load(e1 + 36));
+		Q9 = _mm256_mask_blend_epi64(s2, Q9, ifma_load(e2 + 36));
 		kv = _mm256_add_epi64(kv, one);
 	}
 	_mm256_storeu_si256((__m256i *)(o1 + 0), P0); _mm256_storeu_si256((__m256i *)(o2 + 0), Q0);
diff --git a/cbits/crypton_powm_ifma.py b/cbits/crypton_powm_ifma.py
--- a/cbits/crypton_powm_ifma.py
+++ b/cbits/crypton_powm_ifma.py
@@ -26,6 +26,14 @@
 
 #define IFMA_MASK52 ((UINT64_C(1) << 52) - 1)
 #define IFMA_TARGET __attribute__((target("avx2,avx512f,avx512vl,avx512ifma")))
+
+/* a full load the compiler cannot fold into a masked one */
+IFMA_TARGET static inline __m256i ifma_load(const uint64_t *p)
+{
+	__m256i x = _mm256_loadu_si256((const __m256i *) p);
+	__asm__("" : "+v"(x));
+	return x;
+}
 """
 
 FOOTER = """\
@@ -139,8 +147,9 @@
 def gen_sel(R):
     o = []
     w = o.append
-    w(f"/* both tables' entries k1 and k2 of 32, every entry read and a mask keeping")
-    w(f" * one; entries are {4 * R} words apart */")
+    w(f"/* both tables' entries k1 and k2 of 32: every entry loaded in full and a")
+    w(f" * blend keeping one, since a masked load may not touch what it drops;")
+    w(f" * entries are {4 * R} words apart */")
     w(f"IFMA_TARGET static void ifma_sel{R}(uint64_t *o1, const uint64_t *t1, uint64_t k1,")
     w(f"                                uint64_t *o2, const uint64_t *t2, uint64_t k2)")
     w("{")
@@ -153,7 +162,8 @@
     w("\t\t__mmask8 s1 = _mm256_cmpeq_epi64_mask(v1, kv), s2 = _mm256_cmpeq_epi64_mask(v2, kv);")
     w(f"\t\tconst uint64_t *e1 = t1 + {4 * R} * k, *e2 = t2 + {4 * R} * k;")
     for j in range(R):
-        w(f"\t\tP{j} = _mm256_mask_loadu_epi64(P{j}, s1, e1 + {4 * j}); Q{j} = _mm256_mask_loadu_epi64(Q{j}, s2, e2 + {4 * j});")
+        w(f"\t\tP{j} = _mm256_mask_blend_epi64(s1, P{j}, ifma_load(e1 + {4 * j}));")
+        w(f"\t\tQ{j} = _mm256_mask_blend_epi64(s2, Q{j}, ifma_load(e2 + {4 * j}));")
     w("\t\tkv = _mm256_add_epi64(kv, one);")
     w("\t}")
     for j in range(R):
diff --git a/crypton.cabal b/crypton.cabal
--- a/crypton.cabal
+++ b/crypton.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               crypton
-version:            2.2.5
+version:            2.2.6
 -- crypton's own code is BSD-3-Clause.  The parts of
 -- cbits/aes/gcm_fused_x86.c that follow picotls's fusion are MIT, and the
 -- vendored s2n-bignum assembly in cbits/s2n is taken under ISC, and the
diff --git a/tests/BlockCipher.hs b/tests/BlockCipher.hs
--- a/tests/BlockCipher.hs
+++ b/tests/BlockCipher.hs
@@ -490,6 +490,8 @@
         (mode', iv') = updateCcmInputSize mode (B.length plaintext) testIV
         updateCcmInputSize aeadmode k iv = case aeadmode of
             AEAD_CCM _ m l -> (AEAD_CCM k m l, B.take 13 (iv <> (B.replicate 15 0)))
+            -- RFC 7253 nonces are 1 to 15 bytes
+            AEAD_OCB -> (AEAD_OCB, B.take 15 iv)
             aeadOther -> (aeadOther, iv)
 
 withCtx :: Cipher c => Key c -> (c -> a) -> a
diff --git a/tests/BlockCipher/AESGCMSIVSpec.hs b/tests/BlockCipher/AESGCMSIVSpec.hs
--- a/tests/BlockCipher/AESGCMSIVSpec.hs
+++ b/tests/BlockCipher/AESGCMSIVSpec.hs
@@ -654,12 +654,36 @@
         (tag, ciphertext) = encrypt c iv aad input
      in decrypt c iv aad ciphertext tag === Just input
 
+tagKey :: AES128
+tagKey = throwCryptoError (cipherInit (B.replicate 16 1 :: ByteString))
+
+tagNonce :: Nonce
+tagNonce = throwCryptoError (nonce (B.replicate 12 3 :: ByteString))
+
+message :: ByteString
+message = "authenticated message"
+
+fullTag :: B.Bytes
+ciphertext :: ByteString
+(AuthTag fullTag, ciphertext) = encrypt tagKey tagNonce ("" :: ByteString) message
+
+withTag :: B.Bytes -> Maybe ByteString
+withTag t = decrypt tagKey tagNonce ("" :: ByteString) ciphertext (AuthTag t)
+
 spec :: Spec
 spec =
     describe "AES-GCM-SIV" $ do
         describe "KATs" $ do
             katTests "encrypt" makeEncryptionTest
             katTests "decrypt" makeDecryptionTest
+        describe "tag length" $ do
+            -- the tag doubles as the counter block, so a short one used to
+            -- be written past and a long one raised from pure code
+            it "refuses an empty tag" $ withTag B.empty `shouldBe` Nothing
+            it "refuses a 15-byte tag" $ withTag (B.take 15 fullTag) `shouldBe` Nothing
+            it "refuses a 17-byte tag" $
+                withTag (fullTag `B.append` B.singleton 0) `shouldBe` Nothing
+            it "accepts the 16-byte tag" $ withTag fullTag `shouldBe` Just message
         describe "properties" $ do
             prop "AES128" $ encDecTest (Proxy :: Proxy AES128)
             prop "AES256" $ encDecTest (Proxy :: Proxy AES256)
diff --git a/tests/BlockCipher/AESSpec.hs b/tests/BlockCipher/AESSpec.hs
--- a/tests/BlockCipher/AESSpec.hs
+++ b/tests/BlockCipher/AESSpec.hs
@@ -151,6 +151,24 @@
         eitherCryptoError (() <$ aeadInit AEAD_GCM ctx (iv :: ByteString))
     isRight = either (const False) (const True)
 
+-- RFC 7253: an OCB nonce is 1 to 15 bytes.  The C keeps at most 15, so a
+-- longer IV used to be truncated, and IVs differing only after byte 15 gave
+-- the same keystream.
+ocbIVLengthTests :: Spec
+ocbIVLengthTests =
+    describe "OCB IV length" $ do
+        it "1-byte IV accepted" $ ok (initWith (B.replicate 1 0)) `shouldBe` True
+        it "15-byte IV accepted" $ ok (initWith (B.replicate 15 0)) `shouldBe` True
+        it "empty IV rejected" $
+            initWith B.empty `shouldBe` Left CryptoError_IvSizeInvalid
+        it "16-byte IV rejected" $
+            initWith (B.replicate 16 0) `shouldBe` Left CryptoError_IvSizeInvalid
+  where
+    ctx = throwCryptoError (cipherInit (B.replicate 16 0)) :: AES.AES128
+    initWith iv =
+        eitherCryptoError (() <$ aeadInit AEAD_OCB ctx (iv :: ByteString))
+    ok = either (const False) (const True)
+
 aeadTagLengthTests :: Spec
 aeadTagLengthTests =
     describe "AEAD tag length" $ do
@@ -188,6 +206,47 @@
     openWith t = aeadSimpleDecrypt aead aad ciphertext (AuthTag (BA.convert t))
     openWith' n t = tryAeadSimpleDecrypt aead aad ciphertext n (AuthTag (BA.convert t))
 
+-- A CCM context is set up for one message of a declared length.  A message
+-- of another length used to leave the output unwritten and the tag covering
+-- nothing, so the tag of an empty message verified any ciphertext.
+ccmMisuseTests :: Spec
+ccmMisuseTests =
+    describe "CCM misuse" $ do
+        it "round-trips a message of the declared length" $
+            open (ccm 21) ct tag `shouldBe` Just message
+        it "refuses a ciphertext longer than declared" $
+            open (ccm 0) (B.replicate 32 0xab) emptyTag `shouldBe` Nothing
+        it "refuses a ciphertext shorter than declared" $
+            open (ccm 32) (B.take 5 ct) tag `shouldBe` Nothing
+        it "raises on encrypting a message of another length" $
+            evaluate (snd (aeadSimpleEncrypt (ccm 5) aad message 16))
+                `shouldThrow` anyErrorCall
+        it "raises on encrypting after additional data given twice" $
+            evaluate (fst (aeadEncrypt (twice (ccm 21)) message))
+                `shouldThrow` anyErrorCall
+        it "refuses to decrypt after additional data given twice" $
+            open' (twice (ccm 21)) ct tag `shouldBe` Nothing
+        it "refuses a length that does not fit in L bytes" $
+            initCCM 65536 `shouldBe` Left CryptoError_ParameterInvalid
+        it "refuses a negative length" $
+            initCCM (-1) `shouldBe` Left CryptoError_ParameterInvalid
+  where
+    ctx = throwCryptoError (cipherInit (B.replicate 16 0)) :: AES.AES128
+    nonce = B.replicate 13 7
+    ccm n = throwCryptoError (aeadInit (AEAD_CCM n CCM_M16 CCM_L2) ctx nonce)
+    initCCM n = eitherCryptoError (() <$ aeadInit (AEAD_CCM n CCM_M16 CCM_L2) ctx nonce)
+    aad = "header" :: ByteString
+    message = "authenticated message" :: ByteString
+    (tag, ct) = aeadSimpleEncrypt (ccm 21) aad message 16
+    (emptyTag, _) = aeadSimpleEncrypt (ccm 0) aad B.empty 16 :: (AuthTag, ByteString)
+    open a c t = aeadSimpleDecrypt a aad (c :: ByteString) t :: Maybe ByteString
+    twice a = aeadAppendHeader (aeadAppendHeader a aad) aad
+    -- the header already appended, as aeadSimpleDecrypt would append it again
+    open' a c (AuthTag t) =
+        let (p, a') = aeadDecrypt a (c :: ByteString)
+            AuthTag t' = aeadFinalize a' (BA.length t)
+         in if BA.length t' == BA.length t && BA.constEq t t' then Just p else Nothing
+
 -- The bulk loops -- eight blocks at a time under AES-NI, six at a time in
 -- the assembly -- only start once the message is long enough to fill them,
 -- and what they leave over goes down a different path.  These lengths sit
@@ -393,5 +452,7 @@
     testBlockCipher128 kats256 (undefined :: AES.AES256)
     aeadIVLengthTests
     aeadTagLengthTests
+    ccmMisuseTests
+    ocbIVLengthTests
     gcmLongTests
     oneShotTests
diff --git a/tests/ECCSpec.hs b/tests/ECCSpec.hs
--- a/tests/ECCSpec.hs
+++ b/tests/ECCSpec.hs
@@ -11,6 +11,7 @@
 
 import Data.ByteArray (convert)
 import Data.ByteArray.Encoding
+import qualified Data.ByteString as B
 
 import Imports
 
@@ -151,13 +152,13 @@
         { vpCurve = Curve ECC.Curve_P384R1
         , vpHex =
             "040836bf09614bf5b3c0ffe9b0822a2cc109a90b13d4d3510ce14f766e7d90875ec4bc8d6bee11fc1fdf97473a67884cb1e2685367bdb846c95181b0f35a35cfbee04451122cc55a1e363acaa6c002e71b0b6ff7d0f5dc830a32f0e5086189"
-        , vpError = Nothing -- ignores leading zeros
+        , vpError = Just CryptoError_PointSizeInvalid -- SEC1 2.3.4: fixed-length coordinates
         }
     , VectorPoint
         { vpCurve = Curve ECC.Curve_P384R1
         , vpHex =
             "0400000836bf09614bf5b3c0ffe9b0822a2cc109a90b13d4d3510ce14f766e7d90875ec4bc8d6bee11fc1fdf97473a67884c0000b1e2685367bdb846c95181b0f35a35cfbee04451122cc55a1e363acaa6c002e71b0b6ff7d0f5dc830a32f0e5086189"
-        , vpError = Nothing -- ignores leading zeros
+        , vpError = Just CryptoError_PointSizeInvalid -- SEC1 2.3.4: fixed-length coordinates
         }
     , VectorPoint
         { vpCurve = Curve ECC.Curve_P521R1
@@ -203,13 +204,13 @@
         { vpCurve = Curve ECC.Curve_P521R1
         , vpHex =
             "0443f91fd92d9ccd6d5584b265a2a775d222f4a41ff98190677d985e0889737cbe631d525835fe04faffcdebeccb783538280f4600ae82347b0470583abd9def3060a2e9bdc34f42b134517fc1e961befea0affd1f9666361a039192082a892dd722931d5865b62b69d7369e74895120e540cb10030cccb6049d809fbcf3f54537b378"
-        , vpError = Nothing -- ignores leading zeros
+        , vpError = Just CryptoError_PointSizeInvalid -- SEC1 2.3.4: fixed-length coordinates
         }
     , VectorPoint
         { vpCurve = Curve ECC.Curve_P521R1
         , vpHex =
             "04000043f91fd92d9ccd6d5584b265a2a775d222f4a41ff98190677d985e0889737cbe631d525835fe04faffcdebeccb783538280f4600ae82347b0470583abd9def30600000a2e9bdc34f42b134517fc1e961befea0affd1f9666361a039192082a892dd722931d5865b62b69d7369e74895120e540cb10030cccb6049d809fbcf3f54537b378"
-        , vpError = Nothing -- ignores leading zeros
+        , vpError = Just CryptoError_PointSizeInvalid -- SEC1 2.3.4: fixed-length coordinates
         }
     , VectorPoint
         { vpCurve = Curve ECC.Curve_X25519
@@ -376,6 +377,25 @@
                 bs = ECC.encodePoint prx p1 :: ByteString
                 p2 = ECC.decodePoint prx bs
              in CryptoPassed p1 == p2
+        -- one encoding per point: zero padding inside a coordinate, or an
+        -- odd total length, used to decode on P-384 and P-521
+        prop "decodePoint refuses padded or truncated coordinates" $ \testDRG ->
+            let check :: (ECC.EllipticCurve c, Eq (ECC.Point c)) => c -> Bool
+                check curve =
+                    let prx = Just curve -- using Maybe as Proxy
+                        keyPair = withTestDRG testDRG $ ECC.curveGenerateKeyPair prx
+                        bs = ECC.encodePoint prx (ECC.keypairGetPublic keyPair) :: ByteString
+                        size = (B.length bs - 1) `div` 2
+                        (x, y) = B.splitAt size (B.drop 1 bs)
+                        bad =
+                            [ B.concat ["\4\0", x, "\0", y]
+                            , B.concat ["\4", x, "\0", y]
+                            , B.init bs
+                            , bs `B.snoc` 0
+                            ]
+                     in all (isLeft' . ECC.decodePoint prx) bad
+                isLeft' r = case r of CryptoFailed _ -> True; CryptoPassed _ -> False
+             in check ECC.Curve_P256R1 && check ECC.Curve_P384R1 && check ECC.Curve_P521R1
         modifyMaxSuccess (const 20) $ prop "ECDH commutes" $ \testDRG (Curve curve) ->
             let prx = Just curve -- using Maybe as Proxy
                 (alice, bob) =
diff --git a/tests/ECDSASpec.hs b/tests/ECDSASpec.hs
--- a/tests/ECDSASpec.hs
+++ b/tests/ECDSASpec.hs
@@ -122,6 +122,19 @@
             prop "SHA256" $ propertyECDSA SHA256
             prop "SHA384" $ propertyECDSA SHA384
             prop "SHA512" $ propertyECDSA SHA512
+    -- a zero k used to raise "cannot create point from zero" on P-256,
+    -- where P-384 and P-521 gave Nothing
+    describe "zero scalar" $ do
+        it "signWith k = 0 gives Nothing on every NIST curve" $ do
+            zeroK ECDSA.Curve_P256R1 `shouldBe` True
+            zeroK ECDSA.Curve_P384R1 `shouldBe` True
+            zeroK ECDSA.Curve_P521R1 `shouldBe` True
+        it "pointBaseSmul 0 is the point at infinity on P-256" $
+            ECDSA.pointBaseSmul (Just ECDSA.Curve_P256R1) zero256
+                `shouldBe` ECDSA.pointAdd
+                    (Just ECDSA.Curve_P256R1)
+                    g256
+                    (ECDSA.pointNegate (Just ECDSA.Curve_P256R1) g256)
     describe "recovery" $ do
         modifyMaxSuccess (const 100) $ testRecover ECC.SEC_p128r1
         modifyMaxSuccess (const 100) $ testRecover ECC.SEC_p128r2
@@ -158,3 +171,21 @@
                 , eqTest "verification" True (ECDSA.verify prx hashAlg pubECDSA sigECDSA msg)
                 , eqTest "alteration" False (ECDSA.verify prx hashAlg pubECDSA sigECDSA msg')
                 ]
+
+zeroK
+    :: (ECDSA.EllipticCurveECDSA curve, ECDSA.EllipticCurveBasepointArith curve)
+    => curve -> Bool
+zeroK curve = isNothing (ECDSA.signWith prx k d SHA256 (B.pack [1, 2, 3]))
+  where
+    prx = Just curve
+    k = throwCryptoError (ECDSA.scalarFromInteger prx 0)
+    d = throwCryptoError (ECDSA.scalarFromInteger prx 12345)
+
+zero256 :: ECDSA.Scalar ECDSA.Curve_P256R1
+zero256 = throwCryptoError (ECDSA.scalarFromInteger (Just ECDSA.Curve_P256R1) 0)
+
+g256 :: ECDSA.Point ECDSA.Curve_P256R1
+g256 =
+    ECDSA.pointBaseSmul
+        (Just ECDSA.Curve_P256R1)
+        (throwCryptoError (ECDSA.scalarFromInteger (Just ECDSA.Curve_P256R1) 1))
diff --git a/tests/KDF/PBKDF2Spec.hs b/tests/KDF/PBKDF2Spec.hs
--- a/tests/KDF/PBKDF2Spec.hs
+++ b/tests/KDF/PBKDF2Spec.hs
@@ -101,6 +101,18 @@
             evaluate (fast1 (PBKDF2.Parameters 1 (-1))) `shouldThrow` cryptoError
             evaluate (fast256 (PBKDF2.Parameters 1 (-1))) `shouldThrow` cryptoError
             evaluate (fast512 (PBKDF2.Parameters 1 (-1))) `shouldThrow` cryptoError
+        -- the C takes the count as uint32_t: 2^32 + 1 used to run once,
+        -- and 2^32 hit assert(iterations) and aborted
+        it "rejects an iteration count above 2^32 - 1 in the fast path" $ do
+            let big = PBKDF2.Parameters (2 ^ (32 :: Int) + 1) 32
+            PBKDF2.tryFastPBKDF2_SHA1 big badPass badSalt `shouldBe` refused
+            PBKDF2.tryFastPBKDF2_SHA256 big badPass badSalt `shouldBe` refused
+            PBKDF2.tryFastPBKDF2_SHA512 big badPass badSalt `shouldBe` refused
+            PBKDF2.tryFastPBKDF2_SHA256
+                (PBKDF2.Parameters (2 ^ (32 :: Int)) 32)
+                badPass
+                badSalt
+                `shouldBe` refused
         it "reports them without raising" $ do
             PBKDF2.tryGenerate badPrf (PBKDF2.Parameters 0 32) badPass badSalt
                 `shouldBe` refused
diff --git a/tests/KDF/ScryptSpec.hs b/tests/KDF/ScryptSpec.hs
--- a/tests/KDF/ScryptSpec.hs
+++ b/tests/KDF/ScryptSpec.hs
@@ -44,6 +44,17 @@
         it "reports them without raising" $ do
             run' (Scrypt.Parameters 3 8 1 32) `shouldBe` refused
             run' (Scrypt.Parameters 16 1073741824 1 32) `shouldBe` refused
+        it "rejects n = 1, which the mixing loop overruns" $
+            run' (Scrypt.Parameters 1 8 1 32) `shouldBe` refused
+        it "rejects n = 0" $
+            run' (Scrypt.Parameters 0 8 1 32) `shouldBe` refused
+        it "rejects r or p that is not positive" $ do
+            run' (Scrypt.Parameters 16 0 1 32) `shouldBe` refused
+            run' (Scrypt.Parameters 16 (-1) 1 32) `shouldBe` refused
+            run' (Scrypt.Parameters 16 8 0 32) `shouldBe` refused
+            run' (Scrypt.Parameters 16 8 (-1) 32) `shouldBe` refused
+        it "rejects 128 * r * n that does not fit in an Int" $
+            run' (Scrypt.Parameters (2 ^ (57 :: Int)) 1 1 32) `shouldBe` refused
   where
     run params =
         Scrypt.generate params ("password" :: ByteString) ("salt" :: ByteString)
diff --git a/tests/PubKey/P256Spec.hs b/tests/PubKey/P256Spec.hs
--- a/tests/PubKey/P256Spec.hs
+++ b/tests/PubKey/P256Spec.hs
@@ -232,6 +232,12 @@
         it "point-mul-order" $
             P256.pointIsAtInfinity (P256.pointMul P256.scalarN point7)
                 `shouldBe` True
+        -- infinity is (0, 0); negating it used to give (0, p), which is
+        -- neither infinity nor on the curve
+        it "point-negate-infinity" $ do
+            let o = P256.pointAdd point7 (P256.pointNegate point7)
+            P256.pointIsAtInfinity (P256.pointNegate o) `shouldBe` True
+            P256.pointAdd (P256.pointNegate o) point7 `shouldBe` point7
         it "point-mul-order-minus-one" $
             P256.pointMul (unP256Scalar (P256Scalar (curveN - 1))) point7
                 `shouldBe` P256.pointNegate point7
diff --git a/tests/PubKey/RSASpec.hs b/tests/PubKey/RSASpec.hs
--- a/tests/PubKey/RSASpec.hs
+++ b/tests/PubKey/RSASpec.hs
@@ -302,6 +302,12 @@
         [ ("dP", key{RSA.private_dP = RSA.private_dP key + 1})
         , ("dQ", key{RSA.private_dQ = RSA.private_dQ key + 1})
         , ("qinv", key{RSA.private_qinv = RSA.private_qinv key + 1})
+        , -- with no d to fall back on, the faulty result used to go out
+          ("dP, no d", key{RSA.private_dP = RSA.private_dP key + 1, RSA.private_d = 0})
+        ,
+            ( "qinv, no d"
+            , key{RSA.private_qinv = RSA.private_qinv key + 1, RSA.private_d = 0}
+            )
         ]
 
 -- | Building a key from its two primes has to arrive at the key the vectors
