diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,12 +1,32 @@
 # Changelog
 
+## 0.3.1 (2026-11-13)
+
+* Performance:
+  - Tagged hashes of 64 KiB or more are hashed chunk by chunk instead of
+    being joined first, so nonce generation, `newSession` and
+    `deterministicSign` no longer copy long messages and extra inputs. With
+    a 128 MiB message, peak memory falls from about 660 MB to 141 MB for
+    nonce generation and from 530 MB to 141 MB for `newSession`. Shorter
+    inputs keep the hardware-accelerated hash
+* Documentation:
+  - Cap the size of messages from untrusted parties: `verify` still copies
+    the message (in `ppad-secp256k1`)
+  - The coordinator's signer index is trusted: take it from the authenticated
+    connection, never from the message. A participant that chooses the index
+    can get an honest signer blamed (it cannot get an invalid signature
+    accepted)
+  - Secret keys have no `Encode` instance but can be exported with
+    `secKeyToBytes`; the README and the 0.3.0 entry below said secrets cannot
+    be encoded
+
 ## 0.3.0 (2026-11-12)
 
 A redesigned, hard-to-misuse API. Every value of an exported type is valid by
-construction, secrets cannot be printed, compared or encoded, public values
-have BIP-0327 byte encodings, and each kind of failure has its own error type.
-This release breaks every 0.2 program; the migration table below maps the old
-names to the new ones.
+construction, secrets cannot be printed or compared and have no `Encode`
+instance, public values have BIP-0327 byte encodings, and each kind of
+failure has its own error type. This release breaks every 0.2 program; the
+migration table below maps the old names to the new ones.
 
 * Breaking API changes (old -> new):
   - `SecKey` (a newtype over `Integer` with `Num`, `Read`, `Eq`, `Ord`) ->
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -112,10 +112,12 @@
 contributions as bytes, by signer index (the position of the signer's key in
 the key aggregation context). It decodes and verifies each nonce and partial
 signature as it arrives and blames the signer of an invalid, conflicting or
-missing one (`Blame`). The coordinator can be one of the signers or a
-separate, untrusted party: a wrong aggregate nonce can only make the session
-fail. With `Control.Monad (foldM)`, `Data.Bifunctor (first)` and the module
-imported qualified as `Coordinator`:
+missing one (`Blame`). The signer index is trusted, so take it from the
+authenticated connection a contribution arrived on, never from the message.
+The coordinator can be one of the signers or a separate, untrusted party: a
+wrong aggregate nonce can only make the session fail. With
+`Control.Monad (foldM)`, `Data.Bifunctor (first)` and the module imported
+qualified as `Coordinator`:
 
 <!-- coordinator start -->
 ```haskell
@@ -183,8 +185,15 @@
 
 The API is hard to misuse by construction: every value of an exported type is
 valid (decoders and smart constructors check points, scalar ranges and
-lengths), secret keys and nonces have no `Show`, `Eq`, `Ord` or encoding,
-and errors carry no secrets.
+lengths), secret keys and nonces have no `Show`, `Eq`, `Ord` or `Encode`
+instance, and errors carry no secrets. A secret key leaves the library only
+through `secKeyToBytes`, and a secret nonce only through the `Unsafe`
+module.
+
+Messages can be any length, as BIP-327 allows. Nonce generation and signing
+hash long messages without copying them, but `verify` (BIP-340 verification
+in `ppad-secp256k1`) copies the message once more, so cap the size of
+messages from untrusted parties.
 
 The test suite runs the official [BIP-0327][bip327] test vectors, loaded
 from the upstream JSON files, with exact-output assertions; invalid-encoding
diff --git a/internal/Crypto/Curve/Secp256k1/MuSig2/Internal/Hash.hs b/internal/Crypto/Curve/Secp256k1/MuSig2/Internal/Hash.hs
--- a/internal/Crypto/Curve/Secp256k1/MuSig2/Internal/Hash.hs
+++ b/internal/Crypto/Curve/Secp256k1/MuSig2/Internal/Hash.hs
@@ -9,10 +9,17 @@
 The BIP-340 tagged hashes used by BIP-327 and BIP-341, with the tag prefix
 \(SHA256(tag) \| SHA256(tag)\) computed once per tag.
 
+Each hash takes its input as a list of chunks, hashed as their
+concatenation. Inputs shorter than 'streamThreshold' bytes are joined and
+hashed with the hardware-accelerated @hash@ of @ppad-sha256@; longer ones
+(long messages or extra inputs) are hashed chunk by chunk with @hash_lazy@,
+so the library never copies a long message to hash it.
+
 Not part of the public API.
 -}
 module Crypto.Curve.Secp256k1.MuSig2.Internal.Hash (
   taggedHash,
+  streamThreshold,
   hashKeyAggList,
   hashKeyAggCoef,
   hashNonceCoef,
@@ -23,54 +30,71 @@
   hashTapTweak,
 ) where
 
-import Crypto.Hash.SHA256 (hash)
+import Crypto.Hash.SHA256 (hash, hash_lazy)
 import Data.ByteString (ByteString)
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Lazy as BL
 
-{- | @hash_tag(x) = SHA256(SHA256(tag) || SHA256(tag) || x)@.
+{- | @hash_tag(x) = SHA256(SHA256(tag) || SHA256(tag) || x)@, where \(x\) is
+the concatenation of the chunks.
 
 Partially apply it to a tag to share the prefix between calls.
 -}
-taggedHash :: ByteString -> ByteString -> ByteString
-taggedHash tag = let prefix = th <> th in \x -> hash (prefix <> x)
+taggedHash :: ByteString -> [ByteString] -> ByteString
+taggedHash tag = \xs ->
+  let chunks = prefix : xs
+   in if sum (map BS.length xs) < streamThreshold
+        then hash (BS.concat chunks)
+        else hash_lazy (BL.fromChunks chunks)
  where
   th = hash tag
+  prefix = th <> th
 
+{- | Inputs of at least this many bytes (64 KiB) are hashed chunk by chunk
+instead of being joined first.
+
+Below it, joining costs less than the slower chunked hash; above it, the
+copy would dominate memory use.
+-}
+streamThreshold :: Int
+streamThreshold = 65536
+
 -- | @hash_KeyAgg list@.
-hashKeyAggList :: ByteString -> ByteString
+hashKeyAggList :: [ByteString] -> ByteString
 hashKeyAggList = taggedHash "KeyAgg list"
 {-# NOINLINE hashKeyAggList #-}
 
 -- | @hash_KeyAgg coefficient@.
-hashKeyAggCoef :: ByteString -> ByteString
+hashKeyAggCoef :: [ByteString] -> ByteString
 hashKeyAggCoef = taggedHash "KeyAgg coefficient"
 {-# NOINLINE hashKeyAggCoef #-}
 
 -- | @hash_MuSig/noncecoef@.
-hashNonceCoef :: ByteString -> ByteString
+hashNonceCoef :: [ByteString] -> ByteString
 hashNonceCoef = taggedHash "MuSig/noncecoef"
 {-# NOINLINE hashNonceCoef #-}
 
 -- | @hash_BIP0340/challenge@.
-hashChallenge :: ByteString -> ByteString
+hashChallenge :: [ByteString] -> ByteString
 hashChallenge = taggedHash "BIP0340/challenge"
 {-# NOINLINE hashChallenge #-}
 
 -- | @hash_MuSig/aux@.
-hashAux :: ByteString -> ByteString
+hashAux :: [ByteString] -> ByteString
 hashAux = taggedHash "MuSig/aux"
 {-# NOINLINE hashAux #-}
 
 -- | @hash_MuSig/nonce@.
-hashNonce :: ByteString -> ByteString
+hashNonce :: [ByteString] -> ByteString
 hashNonce = taggedHash "MuSig/nonce"
 {-# NOINLINE hashNonce #-}
 
 -- | @hash_MuSig/deterministic/nonce@.
-hashDetNonce :: ByteString -> ByteString
+hashDetNonce :: [ByteString] -> ByteString
 hashDetNonce = taggedHash "MuSig/deterministic/nonce"
 {-# NOINLINE hashDetNonce #-}
 
 -- | @hash_TapTweak@ (BIP-341).
-hashTapTweak :: ByteString -> ByteString
+hashTapTweak :: [ByteString] -> ByteString
 hashTapTweak = taggedHash "TapTweak"
 {-# NOINLINE hashTapTweak #-}
diff --git a/internal/Crypto/Curve/Secp256k1/MuSig2/Internal/KeyAgg.hs b/internal/Crypto/Curve/Secp256k1/MuSig2/Internal/KeyAgg.hs
--- a/internal/Crypto/Curve/Secp256k1/MuSig2/Internal/KeyAgg.hs
+++ b/internal/Crypto/Curve/Secp256k1/MuSig2/Internal/KeyAgg.hs
@@ -37,7 +37,7 @@
 import qualified Data.List.NonEmpty as NE
 import Data.Map.Strict (Map)
 import qualified Data.Map.Strict as Map
-import Data.Maybe (fromMaybe)
+import Data.Maybe (maybeToList)
 import Data.Sequence (Seq)
 import qualified Data.Sequence as Seq
 
@@ -89,11 +89,11 @@
           }
  where
   encs = map pubKeyBytes (toList pks)
-  l = hashKeyAggList (BS.concat encs)
+  l = hashKeyAggList encs
   pk2 = find (/= NE.head (fmap pubKeyBytes pks)) encs
   coef enc
     | Just enc == pk2 = 1
-    | otherwise = hashToScalar (hashKeyAggCoef (l <> enc))
+    | otherwise = hashToScalar (hashKeyAggCoef [l, enc])
   coefs = map coef encs
   q = sumPoints (zipWith term coefs (toList pks))
   term a pk
@@ -137,7 +137,7 @@
     | t >= curveOrder -> Left TaprootTweakOutOfRange
     | otherwise -> applyXOnlyTweak (Tweak t) ctx
  where
-  t = bytesToInteger (hashTapTweak (xOnlyBytes (aggXOnlyPubKey ctx) <> fromMaybe BS.empty root))
+  t = bytesToInteger (hashTapTweak (xOnlyBytes (aggXOnlyPubKey ctx) : maybeToList root))
 
 -- | The aggregate key \(Q\), including the tweaks applied so far.
 aggPubKey :: KeyAggContext -> PubKey
diff --git a/internal/Crypto/Curve/Secp256k1/MuSig2/Internal/Nonce.hs b/internal/Crypto/Curve/Secp256k1/MuSig2/Internal/Nonce.hs
--- a/internal/Crypto/Curve/Secp256k1/MuSig2/Internal/Nonce.hs
+++ b/internal/Crypto/Curve/Secp256k1/MuSig2/Internal/Nonce.hs
@@ -277,26 +277,26 @@
  where
   rand' = case nonceSecKey inputs of
     Nothing -> rand
-    Just sk -> BS.packZipWith xor (secScalarToBytes (secKeyScalar sk)) (hashAux rand)
+    Just sk -> BS.packZipWith xor (secScalarToBytes (secKeyScalar sk)) (hashAux [rand])
   pkBytes = pubKeyBytes pk
   aggpk = maybe BS.empty xOnlyBytes (nonceAggPubKey inputs)
   msgPrefixed = case nonceMessage inputs of
-    Nothing -> BS.singleton 0
-    Just m -> BS.singleton 1 <> bigEndian 8 (BS.length m) <> m
+    Nothing -> [BS.singleton 0]
+    Just m -> [BS.singleton 1, bigEndian 8 (BS.length m), m]
   extra = fromMaybe BS.empty (nonceExtraInput inputs)
+  -- Chunks, not one concatenation: the message and the extra input may be
+  -- long, and are hashed twice.
   prefix =
-    BS.concat
-      [ rand'
-      , bigEndian 1 (BS.length pkBytes)
-      , pkBytes
-      , bigEndian 1 (BS.length aggpk)
-      , aggpk
-      , msgPrefixed
-      , bigEndian 4 (BS.length extra)
-      , extra
-      ]
+    [ rand'
+    , bigEndian 1 (BS.length pkBytes)
+    , pkBytes
+    , bigEndian 1 (BS.length aggpk)
+    , aggpk
+    ]
+      ++ msgPrefixed
+      ++ [bigEndian 4 (BS.length extra), extra]
   k :: Word8 -> SecScalar
-  k i = secScalarFromBytesModQ (hashNonce (prefix <> BS.singleton i))
+  k i = secScalarFromBytesModQ (hashNonce (prefix ++ [BS.singleton i]))
 
 {- | The scalars \((k_1, k_2)\) of BIP-327 @DeterministicSign@ (reduced
 modulo \(n\), possibly zero):
@@ -323,10 +323,10 @@
 deterministicNonceScalars sk aggOther aggpk msg rand = (k 0, k 1)
  where
   skBytes = secScalarToBytes (secKeyScalar sk)
-  sk' = maybe skBytes (BS.packZipWith xor skBytes . hashAux) rand
-  prefix = BS.concat [sk', aggNonceBytes aggOther, xOnlyBytes aggpk, bigEndian 8 (BS.length msg), msg]
+  sk' = maybe skBytes (BS.packZipWith xor skBytes . hashAux . pure) rand
+  prefix = [sk', aggNonceBytes aggOther, xOnlyBytes aggpk, bigEndian 8 (BS.length msg), msg]
   k :: Word8 -> SecScalar
-  k i = secScalarFromBytesModQ (hashDetNonce (prefix <> BS.singleton i))
+  k i = secScalarFromBytesModQ (hashDetNonce (prefix ++ [BS.singleton i]))
 
 -- | @bytes(n, x)@: the \(n\)-byte big-endian encoding of a length or counter.
 bigEndian :: (Integral a, Bits a) => Int -> a -> ByteString
diff --git a/internal/Crypto/Curve/Secp256k1/MuSig2/Internal/Session.hs b/internal/Crypto/Curve/Secp256k1/MuSig2/Internal/Session.hs
--- a/internal/Crypto/Curve/Secp256k1/MuSig2/Internal/Session.hs
+++ b/internal/Crypto/Curve/Secp256k1/MuSig2/Internal/Session.hs
@@ -94,13 +94,13 @@
     , sesB = b
     , sesRx = BS.drop 1 rBytes
     , sesREven = hasEvenYBytes rBytes
-    , sesE = hashToScalar (hashChallenge (BS.drop 1 rBytes <> qx <> msg))
+    , sesE = hashToScalar (hashChallenge [BS.drop 1 rBytes, qx, msg])
     , sesQEven = pubKeyHasEvenY q
     }
  where
   q = kacQ ctx
   qx = BS.drop 1 (pubKeyBytes q)
-  b = hashToScalar (hashNonceCoef (aggNonceBytes aggNonce <> qx <> msg))
+  b = hashToScalar (hashNonceCoef [aggNonceBytes aggNonce, qx, msg])
   r' = add (aggNonceR1 aggNonce) (mulPublic (aggNonceR2 aggNonce) b)
   r = if isInfinity r' then _CURVE_G else r'
   rBytes = serialize_point r
diff --git a/lib/Crypto/Curve/Secp256k1/MuSig2.hs b/lib/Crypto/Curve/Secp256k1/MuSig2.hs
--- a/lib/Crypto/Curve/Secp256k1/MuSig2.hs
+++ b/lib/Crypto/Curve/Secp256k1/MuSig2.hs
@@ -60,6 +60,13 @@
   signature ("Crypto.Curve.Secp256k1.MuSig2.Coordinator" does this). 'sign'
   checks its own result.
 
+* __Bound the size of untrusted messages.__ BIP-327 allows messages of any
+  length, and every function that takes one holds it in memory whole.
+  'nonceGen', 'newSession' and 'deterministicSign' hash long messages and
+  extra inputs without copying them, but 'verify' (BIP-340 verification in
+  @ppad-secp256k1@) copies the message once more. If messages come from
+  untrusted parties, cap their size before passing them in.
+
 * __Constant-time arithmetic on secrets.__ Secret keys and nonces are never
   held in an 'Integer'. They are parsed, range-checked, reduced from hashes,
   combined (\(k_1 + b k_2\) and \(k + e a d\) in 'sign'), negated and
diff --git a/lib/Crypto/Curve/Secp256k1/MuSig2/Coordinator.hs b/lib/Crypto/Curve/Secp256k1/MuSig2/Coordinator.hs
--- a/lib/Crypto/Curve/Secp256k1/MuSig2/Coordinator.hs
+++ b/lib/Crypto/Curve/Secp256k1/MuSig2/Coordinator.hs
@@ -39,6 +39,16 @@
 accepted is a 'DuplicateContribution'. The finishing functions blame every
 signer whose contribution is still missing. Blames carry no secrets.
 
+The signer index passed to 'receivePubNonce' and 'receivePartialSig' is
+trusted: take it from the authenticated connection the bytes arrived on (for
+example, a roster that maps each signer's authenticated identity to its
+index), never from the message itself. A participant that can choose the
+index can send a valid nonce in an honest signer's slot first: the honest
+signer's own nonce is then blamed as a 'DuplicateContribution', and its
+partial signature, checked against the wrong nonce, as invalid. It cannot
+make the coordinator accept an invalid signature, but it can make the
+session fail with an honest signer blamed.
+
 == Trust
 
 The coordinator learns nothing secret and cannot forge signatures, so the
@@ -246,7 +256,7 @@
 round is unchanged.
 -}
 receivePubNonce ::
-  -- | Signer index.
+  -- | Signer index, from the authenticated connection the bytes arrived on.
   Int ->
   -- | The encoded public nonce.
   ByteString ->
@@ -339,7 +349,7 @@
 'InvalidPartialSignature'. On 'Left' the round is unchanged.
 -}
 receivePartialSig ::
-  -- | Signer index.
+  -- | Signer index, from the authenticated connection the bytes arrived on.
   Int ->
   -- | The encoded partial signature.
   ByteString ->
diff --git a/musig2.cabal b/musig2.cabal
--- a/musig2.cabal
+++ b/musig2.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               musig2
-version:            0.3.0
+version:            0.3.1
 synopsis:           MuSig2 library
 license:            MIT
 license-file:       LICENSE
@@ -113,6 +113,7 @@
     Encoding
     Example
     Generators
+    LongInputs
     NonceSafety
     OfficialVectors
     Properties
diff --git a/test/LongInputs.hs b/test/LongInputs.hs
new file mode 100644
--- /dev/null
+++ b/test/LongInputs.hs
@@ -0,0 +1,109 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+{- | Tagged hashes of long inputs, which are hashed chunk by chunk instead of
+being joined first ('streamThreshold').
+
+The official vectors and the differential corpus use short messages, so
+these tests cover the chunked path: the tagged hash against plain SHA-256 on
+both sides of the threshold, NonceGen against the BIP-327 formula computed
+here with one strict hash, and signing sessions whose final signature is
+checked by @ppad-secp256k1@'s BIP-340 verification, which hashes the message
+itself.
+-}
+module LongInputs (testLongInputs) where
+
+import Crypto.Curve.Secp256k1.MuSig2
+import Crypto.Curve.Secp256k1.MuSig2.Internal.Hash (streamThreshold, taggedHash)
+import Crypto.Curve.Secp256k1.MuSig2.Internal.Scalar (bytesToInteger, integerToBytes32, modQ)
+import Crypto.Curve.Secp256k1.MuSig2.Unsafe (nonceGenWithRand, secNonceToBytes)
+import qualified Crypto.Hash.SHA256 as SHA256
+import qualified Data.ByteString as BS
+import Data.List.NonEmpty (NonEmpty (..))
+import Data.Word (Word8)
+import Test.Tasty
+import Test.Tasty.HUnit
+import Util (secKeyFromInt, unsafeRight)
+
+testLongInputs :: TestTree
+testLongInputs =
+  testGroup
+    "long inputs"
+    [ testCase "taggedHash matches SHA-256 of the concatenation around the threshold" $
+        mapM_ checkTagged [0, 1, 63, 64, streamThreshold - 1, streamThreshold, streamThreshold + 1, 3 * streamThreshold + 17]
+    , testCase "NonceGen with a long message and extra input matches the BIP-327 formula" $ do
+        let sk = secKeyFromInt 0x1234
+            pk = secKeyPubKey sk
+            rand = BS.replicate 32 0x5a
+            msg = filler 0x11 (2 * streamThreshold + 5)
+            extra = filler 0x22 (streamThreshold + 3)
+            inputs = noNonceInputs{nonceMessage = Just msg, nonceExtraInput = Just extra}
+        (sn, _) <- unsafeRight <$> nonceGenWithRand rand pk inputs
+        bytes <- maybe (assertFailure "fresh nonce already used") pure =<< secNonceToBytes sn
+        let expected i =
+              integerToBytes32 . modQ . bytesToInteger . strictTagged "MuSig/nonce" $
+                BS.concat
+                  [ rand
+                  , BS.singleton 33
+                  , encode pk
+                  , BS.singleton 0
+                  , BS.singleton 1
+                  , bigEndian 8 (BS.length msg)
+                  , msg
+                  , bigEndian 4 (BS.length extra)
+                  , extra
+                  , BS.singleton i
+                  ]
+        BS.take 64 bytes @?= expected 0 <> expected 1
+    , testCase "a session with a long message signs and verifies" $ do
+        let msg = filler 0x33 (3 * streamThreshold + 1)
+            sks = secKeyFromInt 7 :| [secKeyFromInt 8]
+            ctx = unsafeRight (keyAgg (fmap secKeyPubKey sks))
+            nonceFor sk = unsafeRight <$> nonceGen (secKeyPubKey sk) noNonceInputs{nonceMessage = Just msg, nonceExtraInput = Just msg}
+        nonces <- traverse nonceFor sks
+        let session = newSession ctx (nonceAgg (fmap snd nonces)) msg
+        psigs <- traverse (\(sk, (sn, _)) -> unsafeRight <$> sign sn sk session) (zipNE sks nonces)
+        assertBool "partial signatures verify" $
+          and (zipWith3 (partialSigVerify session) (toListNE (fmap secKeyPubKey sks)) (toListNE (fmap snd nonces)) (toListNE psigs))
+        assertBool "final signature verifies" $
+          verify (aggXOnlyPubKey ctx) msg (partialSigAgg session psigs)
+    , testCase "deterministicSign with a long message verifies" $ do
+        let msg = filler 0x44 (streamThreshold + 9)
+            sk1 = secKeyFromInt 11
+            sk2 = secKeyFromInt 12
+            ctx = unsafeRight (keyAgg (secKeyPubKey sk1 :| [secKeyPubKey sk2]))
+        (sn1, pn1) <- unsafeRight <$> nonceGen (secKeyPubKey sk1) noNonceInputs
+        let (pn2, psig2) = unsafeRight (deterministicSign sk2 (nonceAgg (pn1 :| [])) ctx msg Nothing)
+            session = newSession ctx (nonceAgg (pn1 :| [pn2])) msg
+        psig1 <- unsafeRight <$> sign sn1 sk1 session
+        assertBool "deterministic partial signature verifies" (partialSigVerify session (secKeyPubKey sk2) pn2 psig2)
+        assertBool "final signature verifies" $
+          verify (aggXOnlyPubKey ctx) msg (partialSigAgg session (psig1 :| [psig2]))
+    ]
+ where
+  zipNE (a :| as) (b :| bs) = (a, b) :| zip as bs
+  toListNE (a :| as) = a : as
+
+-- | Checks 'taggedHash' on @n@ bytes split into uneven chunks, empty ones included.
+checkTagged :: Int -> Assertion
+checkTagged n = taggedHash tag (chunksOf' input) @?= strictTagged tag input
+ where
+  tag = "test/long"
+  input = filler 0x77 n
+  chunksOf' bs = BS.empty : go 1 bs
+  go k bs
+    | BS.null bs = [BS.empty]
+    | otherwise = let (a, rest) = BS.splitAt (k * 1000 + 7) bs in a : go (k + 1) rest
+
+-- | The BIP-340 tagged hash computed with one strict SHA-256 call.
+strictTagged :: BS.ByteString -> BS.ByteString -> BS.ByteString
+strictTagged tag x = SHA256.hash (th <> th <> x)
+ where
+  th = SHA256.hash tag
+
+-- | @n@ bytes counting up from @start@.
+filler :: Word8 -> Int -> BS.ByteString
+filler start n = fst (BS.unfoldrN n (\w -> Just (w, w + 1)) start)
+
+-- | @n@-byte big-endian encoding.
+bigEndian :: Int -> Int -> BS.ByteString
+bigEndian n x = BS.pack [fromIntegral (x `div` (256 ^ i)) | i <- [n - 1, n - 2 .. 0]]
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -6,6 +6,7 @@
 import DiffCorpus (diffCorpusTests)
 import Encoding (testEncoding)
 import Example (testExample)
+import LongInputs (testLongInputs)
 import NonceSafety (testNonceSafety)
 import OfficialVectors (officialVectorTests)
 import Properties (testProtocol)
@@ -15,7 +16,7 @@
 
 -- | Unit tests.
 unitTests :: TestTree
-unitTests = testGroup "Unit Tests" [testExample, testSignVerify, testNonceSafety, testApplyTweaks, testBip327Regressions, testCoordinator]
+unitTests = testGroup "Unit Tests" [testExample, testSignVerify, testNonceSafety, testApplyTweaks, testBip327Regressions, testCoordinator, testLongInputs]
 
 -- | Property tests.
 propertyTests :: TestTree
