diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,89 @@
+## Version 0.13 (2026-06-06)
+
+- Update to *crypton >= 1.1.0* and *ram*.  There are no
+  functional changes in this release.  Until the release
+  of 0.14, any behavioural changes (bug fixes, etc) will
+  be released to both the 0.13 and 0.12 series.
+
+## Version 0.12 (2025-08-18)
+
+- GHC 9.6 is now the earliest supported version.
+
+- Changed the header protection data types for better ergonomics
+  ([#125](https://github.com/frasertweedale/hs-jose/issues/125)).
+  Previously, `()` was used for serialisations that only support
+  protected headers (thus, a single constructor).  This release
+  introduces the new singleton data type `RequiredProtected` to
+  replace the use of `()` for this purpose.  This is a breaking
+  change and some library users will need to update their code.
+
+  The `Protection` type has been renamed to `OptionalProtection`,
+  with the old name retained as a (deprecated) type synonym.
+
+  The `ProtectionIndicator` class has been renamed to
+  `ProtectionSupport`, with the old name retained as a (deprecated)
+  type synonym.
+
+  Added some convenience header and header parameter constructors:
+  `newJWSHeaderProtected`, `newHeaderParamProtected` and
+  `newHeaderParamUnprotected`.
+
+- Generalised the types of `signJWT`, `verifyJWT` and related
+  functions to accept custom JWS header types.  Added new type
+  synonym `SignedJWTWithHeader h`.  This change could break some
+  applications by introducing ambiguity.  The solution is to use
+  a type annotation, type application, or explicit coercion
+  function, as in the below examples:
+
+  ```haskell
+  -- type application
+  {-# LANGUAGE TypeApplications #-}
+  decodeCompact @SignedJWT s >>= verifyClaims settings k
+
+  -- type annotation
+  do
+    jwt <- decodeCompact s
+    verifyClaims settings k (jwt :: SignedJWT)
+
+  -- coercion function
+  let
+    fixType = id :: SignedJWT -> SignedJWT
+  in
+    verifyClaims settings k . fixType =<< decodeCompact s
+  ```
+
+- Added `unsafeGetPayload`, `unsafeGetJWTPayload` and
+  `unsafeGetJWTClaimsSet` functions.  These enable access to
+  the JWS/JWT payload without cryptographic verification.  As
+  the name implies, these should be used with the utmost caution!
+  ([#126](https://github.com/frasertweedale/hs-jose/issues/126))
+
+- Add `Crypto.JOSE.JWK.negotiateJWSAlg` which chooses the
+  cryptographically strongest JWS algorithm for a given key,
+  restricted to a given set of algorithms.  ([#118][])
+
+- Added new conversion functions `Crypto.JOSE.JWK.fromX509PubKey`
+  and `Crypto.JOSE.JWK.fromX509PrivKey`.  These convert from the
+  `Data.X509.PubKey` and `Data.X509.PrivKey` types, which can be
+  read via the *crypton-x509-store* package.  They supports RSA,
+  NIST ECC, and Edwards curve key types (Ed25519, Ed448, X25519,
+  X448).
+
+- Updated `Crypto.JOSE.JWK.fromX509Certificate` to support Edwards
+  curve key types (Ed25519, Ed448, X25519, X448).
+
+- Added `Crypto.JOSE.JWK.fromRSAPublic :: RSA.PublicKey -> JWK`.
+
+- Added `Ord` instance for `StringOrURI` ([#134]; contributed by
+  Chris Penner).
+
+- Added `Semigroup` and `Monoid` instances for `JWKSet`
+  ([#135]; contributed by Torgeir Strand Henriksen).
+
+[#134]: https://github.com/frasertweedale/hs-jose/pull/134
+[#135]: https://github.com/frasertweedale/hs-jose/pull/135
+
+
 ## Version 0.11 (2023-10-31)
 
 - Migrate to the *crypton* library ecosystem.  *crypton* was a hard
@@ -51,6 +137,8 @@
 [#91]: https://github.com/frasertweedale/hs-jose/issues/91
 [#106]: https://github.com/frasertweedale/hs-jose/issues/106
 [#107]: https://github.com/frasertweedale/hs-jose/issues/107
+[#118]: https://github.com/frasertweedale/hs-jose/issues/118
+[#122]: https://github.com/frasertweedale/hs-jose/issues/122
 
 
 ## Older versions
diff --git a/example/JWS.hs b/example/JWS.hs
--- a/example/JWS.hs
+++ b/example/JWS.hs
@@ -39,7 +39,7 @@
   payload <- L.readFile payloadFilename
   result <- runJOSE $ do
     h <- makeJWSHeader k
-    signJWS payload [(h :: JWSHeader Protection, k)]
+    signJWS payload [(h :: JWSHeader OptionalProtection, k)]
   case result of
     Left e -> print (e :: Error) >> exitFailure
     Right jws -> L.putStr (encode jws)
diff --git a/example/KeyDB.hs b/example/KeyDB.hs
--- a/example/KeyDB.hs
+++ b/example/KeyDB.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-
 module KeyDB
   (
     KeyDB(..)
diff --git a/example/Main.hs b/example/Main.hs
--- a/example/Main.hs
+++ b/example/Main.hs
@@ -1,5 +1,4 @@
 {-# OPTIONS_GHC -fno-warn-incomplete-patterns #-}
-{-# LANGUAGE FlexibleContexts #-}
 
 import Data.Maybe (fromJust)
 import System.Environment (getArgs)
@@ -75,7 +74,7 @@
   let
     aud' = fromJust $ preview stringOrUri aud
     conf = defaultJWTValidationSettings (== aud')
-    go k = runJOSE $ decodeCompact jwtData >>= verifyClaims conf k
+    go k = runJOSE $ decodeCompact @SignedJWT jwtData >>= verifyClaims conf k
 
   jwkDir <- isDirectory <$> getFileStatus jwkFilename
   result <-
diff --git a/jose.cabal b/jose.cabal
--- a/jose.cabal
+++ b/jose.cabal
@@ -1,6 +1,6 @@
 cabal-version:       2.2
 name:                jose
-version:             0.11
+version:             0.13
 synopsis:
   JSON Object Signing and Encryption (JOSE) and JSON Web Token (JWT) library
 description:
@@ -24,59 +24,47 @@
 license:             Apache-2.0
 license-file:        LICENSE
 extra-source-files:
+  test/data/fido.jwt
+extra-doc-files:
   CHANGELOG.md
   README.md
-  test/data/fido.jwt
 author:              Fraser Tweedale
 maintainer:          frase@frase.id.au
-copyright:           Copyright (C) 2013-2021  Fraser Tweedale
+copyright:           Copyright (C) 2013-2025  Fraser Tweedale
 category:            Cryptography
 build-type:          Simple
 tested-with:
-  GHC ==8.8.4 || ==8.10.7 || ==9.0.2 || ==9.2.7 || ==9.4.7 || ==9.6.3 || == 9.8.1
+  GHC ==9.6.7 || ==9.8.4 || ==9.10.3 || ==9.12.4 || ==9.14.1
 
 flag demos
   description: Build demonstration programs
   default: False
 
 common common
-  default-language: Haskell2010
+  default-language: GHC2021
+  default-extensions: LambdaCase
   ghc-options:
     -Wall
     -Widentities
     -Wincomplete-record-updates
     -Wincomplete-uni-patterns
     -Werror=missing-methods
-  if impl(ghc >= 8.0)
-    ghc-options:
-      -Wcompat
-      -Wnoncanonical-monad-instances
-      -Wredundant-constraints
-  if impl(ghc >= 8.2)
-    ghc-options:
-      -fhide-source-paths
-  if impl(ghc >= 8.4)
-    ghc-options:
-      -Wmissing-export-lists
-      -Wpartial-fields
-  if impl(ghc >= 8.10)
-    ghc-options:
-      -Wunused-packages
-  if impl(ghc >= 9.0)
-    ghc-options:
-      -Winvalid-haddock
-      -Werror=unicode-bidirectional-format-characters
-  if impl(ghc >= 9.2)
-    ghc-options:
-      -Wimplicit-lift
-      -Woperator-whitespace
-      -Wredundant-bang-patterns
-  if impl(ghc >= 9.4)
-    ghc-options:
-      -Wredundant-strictness-flags
+    -Wcompat
+    -Wnoncanonical-monad-instances
+    -Wredundant-constraints
+    -fhide-source-paths
+    -Wmissing-export-lists
+    -Wpartial-fields
+    -Wunused-packages
+    -Winvalid-haddock
+    -Werror=unicode-bidirectional-format-characters
+    -Wimplicit-lift
+    -Woperator-whitespace
+    -Wredundant-bang-patterns
+    -Wredundant-strictness-flags
 
   build-depends:
-    base >= 4.13 && < 5
+    base >= 4.18 && < 5
     , bytestring >= 0.10 && < 0.13
     , lens >= 4.16
     , mtl >= 2.2.1
@@ -110,11 +98,11 @@
     , aeson >= 2.0.1.0 && < 3
     , base64-bytestring >= 1.2.1.0 && < 1.3
     , concise >= 0.1
-    , containers >= 0.5
-    , crypton >= 0.31
-    , memory >= 0.7
-    , monad-time >= 0.3
-    , template-haskell >= 2.11
+    , containers >= 0.5.8
+    , crypton >= 1.1.0
+    , ram >= 0.19
+    , monad-time >= 0.4
+    , template-haskell >= 2.12
     , text >= 1.1
     , time >= 1.5
     , network-uri >= 2.6
diff --git a/src/Crypto/JOSE/AESKW.hs b/src/Crypto/JOSE/AESKW.hs
--- a/src/Crypto/JOSE/AESKW.hs
+++ b/src/Crypto/JOSE/AESKW.hs
@@ -12,8 +12,6 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
-{-# LANGUAGE ScopedTypeVariables #-}
-
 {- |
 
 Advanced Encryption Standard (AES) Key Wrap Algorithm;
diff --git a/src/Crypto/JOSE/Error.hs b/src/Crypto/JOSE/Error.hs
--- a/src/Crypto/JOSE/Error.hs
+++ b/src/Crypto/JOSE/Error.hs
@@ -12,8 +12,6 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TemplateHaskell #-}
 
 {-|
diff --git a/src/Crypto/JOSE/Header.hs b/src/Crypto/JOSE/Header.hs
--- a/src/Crypto/JOSE/Header.hs
+++ b/src/Crypto/JOSE/Header.hs
@@ -12,9 +12,6 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
-{-# LANGUAGE KindSignatures #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-
 {-|
 
 Types and functions for working with JOSE header parameters.
@@ -22,10 +19,19 @@
 -}
 module Crypto.JOSE.Header
   (
-  -- * Defining header data types
+  -- * Constructing header parameters
     HeaderParam(..)
-  , ProtectionIndicator(..)
-  , Protection(..)
+  , newHeaderParamProtected
+  , newHeaderParamUnprotected
+
+  -- ** Header protection support
+  , ProtectionSupport(..)
+  , ProtectionIndicator
+
+  , OptionalProtection(..)
+  , RequiredProtection(..)
+  , Protection
+
   , protection
   , isProtected
   , param
@@ -87,7 +93,7 @@
 class HasParams (a :: Type -> Type) where
   -- | Return a list of parameters,
   -- each paired with whether it is protected or not.
-  params :: ProtectionIndicator p => a p -> [(Bool, Pair)]
+  params :: ProtectionSupport p => a p -> [(Bool, Pair)]
 
   -- | List of "known extensions", i.e. keys that may appear in the
   -- "crit" header parameter.
@@ -95,7 +101,7 @@
   extensions = const []
 
   parseParamsFor
-    :: (HasParams b, ProtectionIndicator p)
+    :: (HasParams b, ProtectionSupport p)
     => Proxy b -> Maybe Object -> Maybe Object -> Parser (a p)
 
 -- | Parse a pair of objects (protected and unprotected header)
@@ -105,14 +111,14 @@
 -- to access "known extensions" understood by the target type.)
 --
 parseParams
-  :: forall a p. (HasParams a, ProtectionIndicator p)
+  :: forall a p. (HasParams a, ProtectionSupport p)
   => Maybe Object -- ^ protected header
   -> Maybe Object -- ^ unprotected header
   -> Parser (a p)
 parseParams = parseParamsFor (Proxy :: Proxy a)
 
 protectedParams
-  :: (HasParams a, ProtectionIndicator p)
+  :: (HasParams a, ProtectionSupport p)
   => a p -> Maybe Value {- ^ Object -}
 protectedParams h =
   case (map snd . filter fst . params) h of
@@ -122,7 +128,7 @@
 -- | Return the base64url-encoded protected parameters
 --
 protectedParamsEncoded
-  :: (HasParams a, ProtectionIndicator p)
+  :: (HasParams a, ProtectionSupport p)
   => a p -> L.ByteString
 protectedParamsEncoded =
   maybe mempty (review base64url . encode) . protectedParams
@@ -130,19 +136,18 @@
 -- | Return unprotected params as a JSON 'Value' (always an object)
 --
 unprotectedParams
-  :: (HasParams a, ProtectionIndicator p)
+  :: (HasParams a, ProtectionSupport p)
   => a p -> Maybe Value {- ^ Object -}
 unprotectedParams h =
   case (map snd . filter (not . fst) . params) h of
     [] -> Nothing
     xs -> Just (object xs)
 
--- | Whether a header is protected or unprotected
---
-data Protection = Protected | Unprotected
-  deriving (Eq, Show)
 
-class Eq a => ProtectionIndicator a where
+-- | Class that defines the protected and (if supported) unprotected values
+-- for a protection indicator data type.
+--
+class Eq a => ProtectionSupport a where
   -- | Get a value for indicating protection.
   getProtected :: a
 
@@ -150,12 +155,33 @@
   -- if the type does not support unprotected headers.
   getUnprotected :: Maybe a
 
-instance ProtectionIndicator Protection where
+type ProtectionIndicator = ProtectionSupport
+{-# DEPRECATED ProtectionIndicator "renamed to 'ProtectionSupport." #-}
+
+
+
+-- | Use this protection type when the serialisation supports both
+-- protected and unprotected headers.
+--
+data OptionalProtection = Protected | Unprotected
+  deriving (Eq, Show)
+
+instance ProtectionSupport OptionalProtection where
   getProtected = Protected
   getUnprotected = Just Unprotected
 
-instance ProtectionIndicator () where
-  getProtected = ()
+type Protection = OptionalProtection
+{-# DEPRECATED Protection "renamed to 'OptionalProtection'." #-}
+
+
+-- | Use this protection type when the serialisation only supports
+-- protected headers.
+--
+data RequiredProtection = RequiredProtection
+  deriving (Eq, Show)
+
+instance ProtectionSupport RequiredProtection where
+  getProtected = RequiredProtection
   getUnprotected = Nothing
 
 
@@ -178,10 +204,19 @@
 {-# ANN param "HLint: ignore Avoid lambda" #-}
 
 -- | Getter for whether a parameter is protected
-isProtected :: (ProtectionIndicator p) => Getter (HeaderParam p a) Bool
+isProtected :: (ProtectionSupport p) => Getter (HeaderParam p a) Bool
 isProtected = protection . to (== getProtected)
 
 
+-- | Convenience constructor for a protected 'HeaderParam'.
+newHeaderParamProtected :: (ProtectionIndicator p) => a -> HeaderParam p a
+newHeaderParamProtected = HeaderParam getProtected
+
+-- | Convenience constructor for a protected 'HeaderParam'.
+newHeaderParamUnprotected :: a -> HeaderParam OptionalProtection a
+newHeaderParamUnprotected = HeaderParam Unprotected
+
+
 {- $parsing
 
 The 'parseParamsFor' function defines the parser for a header type.
@@ -224,7 +259,7 @@
 -- the protected or the unprotected header.
 --
 headerOptional
-  :: (FromJSON a, ProtectionIndicator p)
+  :: (FromJSON a, ProtectionSupport p)
   => T.Text
   -> Maybe Object
   -> Maybe Object
@@ -236,7 +271,7 @@
 -- but with an explicit argument for the parser.
 --
 headerOptional'
-  :: (ProtectionIndicator p)
+  :: (ProtectionSupport p)
   => (Value -> Parser a)
   -> T.Text
   -> Maybe Object
@@ -274,7 +309,7 @@
 -- the protected or the unprotected header.
 --
 headerRequired
-  :: (FromJSON a, ProtectionIndicator p)
+  :: (FromJSON a, ProtectionSupport p)
   => T.Text
   -> Maybe Object
   -> Maybe Object
diff --git a/src/Crypto/JOSE/JWA/JWK.hs b/src/Crypto/JOSE/JWA/JWK.hs
--- a/src/Crypto/JOSE/JWA/JWK.hs
+++ b/src/Crypto/JOSE/JWA/JWK.hs
@@ -12,11 +12,9 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
-{-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE NoMonomorphismRestriction #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
-{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TemplateHaskell #-}
 
 {-|
@@ -30,12 +28,13 @@
 
   -- * Parameters for Elliptic Curve Keys
   , Crv(..)
-  , ECKeyParameters
+  , ECKeyParameters(ECKeyParameters)
   , ecCrv, ecX, ecY, ecD
   , curve
   , point
   , ecPrivateKey
   , ecParametersFromX509
+  , ecParametersFromX509Priv
   , genEC
 
   -- * Parameters for RSA Keys
@@ -188,8 +187,12 @@
       $ maybe (Object mempty) toJSON rsaOptionalParameters
 
 
--- | Parameters for Elliptic Curve Keys
+-- | Parameters for Elliptic Curve Keys.
 --
+-- @
+-- ECKeyParameters crv x y (Just d)
+-- @
+--
 data ECKeyParameters = ECKeyParameters
   { _ecCrv :: Crv
   , _ecX :: Types.SizedBase64Integer
@@ -302,19 +305,51 @@
 ecPrivateKey (ECKeyParameters _ _ _ (Just (Types.SizedBase64Integer _ d))) = pure d
 ecPrivateKey _ = throwing _KeyMismatch "Not an EC private key"
 
+-- | See also 'Crypto.JOSE.JWK.fromX509PubKey' and
+-- 'Crypto.JOSE.JWK.fromX509Certificate'.
+--
 ecParametersFromX509 :: (MonadError e m, AsError e) => X509.PubKeyEC -> m ECKeyParameters
-ecParametersFromX509 pubKeyEC = do
-  ecCurve <- maybe (throwing _KeyMismatch "Invalid EC point") pure $ X509.EC.ecPubKeyCurve pubKeyEC
-  curveName <- maybe (throwing _KeyMismatch "Unknown curve") pure $ X509.EC.ecPubKeyCurveName pubKeyEC
-  crv <- maybe (throwing _KeyMismatch "Unsupported curve TODO ") pure $ preview fromCurveName curveName
-  pt <- maybe (throwing _KeyMismatch "Invalid EC point") pure $ X509.EC.unserializePoint ecCurve (X509.pubkeyEC_pub pubKeyEC)
+ecParametersFromX509 k = do
+  curveName <-
+    maybe (throwing _KeyMismatch "Unknown curve") pure
+      $ X509.EC.ecPubKeyCurveName k
+  crv <-
+    maybe (throwing _KeyMismatch "Unsupported curve") pure
+      $ preview fromCurveName curveName
+  pt <-
+    maybe (throwing _KeyMismatch "Invalid EC point") pure
+      $ X509.EC.unserializePoint (ECC.getCurveByName curveName) (X509.pubkeyEC_pub k)
   (x, y) <- case pt of
-    ECC.PointO    -> throwing _KeyMismatch "Cannot use point at infinity"
+    ECC.PointO ->
+      throwing _KeyMismatch "Cannot use point at infinity"
     ECC.Point x y ->
       pure (Types.makeSizedBase64Integer x, Types.makeSizedBase64Integer y)
   pure $ ECKeyParameters crv x y Nothing
 
--- | Parameters for RSA Keys
+-- | See also 'Crypto.JOSE.JWK.fromX509PrivKey'.
+--
+ecParametersFromX509Priv :: (MonadError e m, AsError e) => X509.PrivKeyEC -> m ECKeyParameters
+ecParametersFromX509Priv k = do
+  curveName <-
+    maybe (throwing _KeyMismatch "Unknown curve") pure
+      $ X509.EC.ecPrivKeyCurveName k
+  crv <-
+    maybe (throwing _KeyMismatch "Unsupported curve") pure
+      $ preview fromCurveName curveName
+  let d = privkeyEC_priv k
+  (x, y) <- case ECC.generateQ (ECC.getCurveByName curveName) d of
+    ECC.PointO ->
+      throwing _KeyMismatch "Cannot use point at infinity"
+    ECC.Point x y ->
+      pure (Types.makeSizedBase64Integer x, Types.makeSizedBase64Integer y)
+  pure $ ECKeyParameters crv x y (Just $ Types.makeSizedBase64Integer d)
+
+
+-- | Parameters for RSA Keys.
+--
+-- @
+-- RSAKeyParameters modulus exponent (Just privateParams)
+-- @
 --
 data RSAKeyParameters = RSAKeyParameters
   { _rsaN :: Types.Base64Integer
diff --git a/src/Crypto/JOSE/JWE.hs b/src/Crypto/JOSE/JWE.hs
--- a/src/Crypto/JOSE/JWE.hs
+++ b/src/Crypto/JOSE/JWE.hs
@@ -12,10 +12,7 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
-{-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TupleSections #-}
 
 module Crypto.JOSE.JWE
   (
@@ -82,7 +79,7 @@
   }
   deriving (Eq, Show)
 
-newJWEHeader :: ProtectionIndicator p => AlgWithParams -> Enc -> JWEHeader p
+newJWEHeader :: (ProtectionSupport p) => AlgWithParams -> Enc -> JWEHeader p
 newJWEHeader alg enc =
   JWEHeader (Just alg) (HeaderParam getProtected enc) z z z z z z z z z z z
   where z = Nothing
@@ -134,7 +131,7 @@
     <*> o .:? "encrypted_key"
 
 parseRecipient
-  :: (HasParams a, ProtectionIndicator p)
+  :: (HasParams a, ProtectionSupport p)
   => Maybe Object -> Maybe Object -> Value -> Parser (JWERecipient a p)
 parseRecipient hp hu = withObject "JWE Recipient" $ \o -> do
   hr <- o .:? "header"
@@ -153,7 +150,7 @@
   , _jweRecipients :: [JWERecipient a p]
   }
 
-instance (HasParams a, ProtectionIndicator p) => FromJSON (JWE a p) where
+instance (HasParams a, ProtectionSupport p) => FromJSON (JWE a p) where
   parseJSON = withObject "JWE JSON Serialization" $ \o -> do
     hpB64 <- o .:? "protected"
     hp <- maybe
diff --git a/src/Crypto/JOSE/JWK.hs b/src/Crypto/JOSE/JWK.hs
--- a/src/Crypto/JOSE/JWK.hs
+++ b/src/Crypto/JOSE/JWK.hs
@@ -1,4 +1,4 @@
--- Copyright (C) 2013, 2014, 2015, 2016, 2017  Fraser Tweedale
+-- Copyright (C) 2013-2023  Fraser Tweedale
 --
 -- Licensed under the Apache License, Version 2.0 (the "License");
 -- you may not use this file except in compliance with the License.
@@ -12,12 +12,10 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE NoMonomorphismRestriction #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE TemplateHaskell #-}
-{-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE ViewPatterns #-}
 
 {-|
@@ -69,8 +67,11 @@
   -- * Converting from other key formats
   , fromKeyMaterial
   , fromRSA
+  , fromRSAPublic
   , fromOctets
   , fromX509Certificate
+  , fromX509PubKey
+  , fromX509PrivKey
 
   -- * JWK Thumbprint
   , thumbprint
@@ -83,6 +84,7 @@
 
   -- Miscellaneous
   , checkJWK
+  , negotiateJWSAlg
   , bestJWSAlg
 
   , module Crypto.JOSE.JWA.JWK
@@ -91,6 +93,7 @@
 import Control.Applicative
 import Control.Monad ((>=>))
 import Data.Function (on)
+import Data.List (find)
 import Data.Maybe (catMaybes)
 import Data.Word (Word8)
 
@@ -99,6 +102,10 @@
 import Control.Monad.Except (MonadError, runExcept)
 import Control.Monad.Error.Lens (throwing, throwing_)
 import Crypto.Hash
+import qualified Crypto.PubKey.Ed25519 as Ed25519
+import qualified Crypto.PubKey.Ed448 as Ed448
+import qualified Crypto.PubKey.Curve25519 as Curve25519
+import qualified Crypto.PubKey.Curve448 as Curve448
 import qualified Crypto.PubKey.RSA as RSA
 import Data.Aeson
 import Data.Aeson.Types (explicitParseFieldMaybe')
@@ -242,12 +249,7 @@
 fromRSAPublic :: RSA.PublicKey -> JWK
 fromRSAPublic = fromKeyMaterial . RSAKeyMaterial . toRSAPublicKeyParameters
 
--- | Convert an EC public key into a JWK
---
-fromECPublic :: (AsError e, MonadError e m) => X509.PubKeyEC -> m JWK
-fromECPublic = fmap (fromKeyMaterial . ECKeyMaterial) . ecParametersFromX509
 
-
 -- | Convert octet string into a JWK
 --
 fromOctets :: Cons s s Word8 Word8 => s -> JWK
@@ -257,10 +259,45 @@
 {-# INLINE fromOctets #-}
 
 
+-- | Convert from a 'X509.PubKey' (such as can be read via the
+-- /crypton-x509-store/ package).  Supports RSA, ECDSA, Ed25519,
+-- Ed448, X25519 and X448 keys.
+--
+fromX509PubKey :: (AsError e, MonadError e m) => X509.PubKey -> m JWK
+fromX509PubKey = \case
+  X509.PubKeyRSA k      -> pure (fromRSAPublic k)
+  X509.PubKeyEC k       -> fromECPublic k
+  X509.PubKeyX25519 k   -> fromOKP $ X25519Key k Nothing
+  X509.PubKeyX448 k     -> fromOKP $ X448Key k Nothing
+  X509.PubKeyEd25519 k  -> fromOKP $ Ed25519Key k Nothing
+  X509.PubKeyEd448 k    -> fromOKP $ Ed448Key k Nothing
+  _ -> throwing _KeyMismatch "X.509 key type not supported"
+  where
+    fromECPublic = fmap (fromKeyMaterial . ECKeyMaterial) . ecParametersFromX509
+    fromOKP = pure . fromKeyMaterial . OKPKeyMaterial
+
+-- | Convert from a 'X509.PrivKey' (such as can be read via the
+-- /crypton-x509-store/ package).  Supports RSA, ECDSA, Ed25519,
+-- Ed448, X25519 and X448 keys.
+--
+fromX509PrivKey :: (AsError e, MonadError e m) => X509.PrivKey -> m JWK
+fromX509PrivKey = \case
+  X509.PrivKeyRSA k      -> pure (fromRSA k)
+  X509.PrivKeyEC k       -> fromEC k
+  X509.PrivKeyX25519 k   -> fromOKP $ X25519Key (Curve25519.toPublic k) (Just k)
+  X509.PrivKeyX448 k     -> fromOKP $ X448Key (Curve448.toPublic k) (Just k)
+  X509.PrivKeyEd25519 k  -> fromOKP $ Ed25519Key (Ed25519.toPublic k) (Just k)
+  X509.PrivKeyEd448 k    -> fromOKP $ Ed448Key (Ed448.toPublic k) (Just k)
+  _ -> throwing _KeyMismatch "X.509 key type not supported"
+  where
+    fromEC = fmap (fromKeyMaterial . ECKeyMaterial) . ecParametersFromX509Priv
+    fromOKP = pure . fromKeyMaterial . OKPKeyMaterial
+
+
 -- | Convert an X.509 certificate into a JWK.
 --
--- Supports RSA and ECDSA (when the curve is supported).  Other key types
--- will throw 'AlgorithmNotImplemented'.
+-- Supports RSA, ECDSA (curves defined for use in JOSE), and Edwards
+-- curves (Ed25519, Ed448, X25519, X448).
 --
 -- The @"x5c"@ field of the resulting JWK contains the certificate.
 --
@@ -268,10 +305,7 @@
   :: (AsError e, MonadError e m)
   => X509.SignedCertificate -> m JWK
 fromX509Certificate cert = do
-  k <- case (X509.certPubKey . X509.signedObject . X509.getSigned) cert of
-    X509.PubKeyRSA k -> pure (fromRSAPublic k)
-    X509.PubKeyEC k -> fromECPublic k
-    _ -> throwing _KeyMismatch "X.509 key type not supported"
+  k <- fromX509PubKey . X509.certPubKey . X509.signedObject $ X509.getSigned cert
   pure $ k & set jwkX5cRaw (Just (pure cert))
 
 fromX509CertificateMaybe :: X509.SignedCertificate -> Maybe JWK
@@ -288,7 +322,7 @@
 
 -- | RFC 7517 §5.  JWK Set Format
 --
-newtype JWKSet = JWKSet [JWK] deriving (Eq, Show)
+newtype JWKSet = JWKSet [JWK] deriving (Eq, Show, Semigroup, Monoid)
 
 instance FromJSON JWKSet where
   parseJSON = withObject "JWKSet" (\o -> JWKSet <$> o .: "keys")
@@ -317,33 +351,75 @@
 -- | Choose the cryptographically strongest JWS algorithm for a
 -- given key.  The JWK "alg" algorithm parameter is ignored.
 --
+-- See also 'negotiateJWSAlg'.
+--
+-- @
+-- bestJWSAlg k = negotiateJWSAlg k Nothing
+-- @
+--
 bestJWSAlg
   :: (MonadError e m, AsError e)
   => JWK
   -> m JWA.JWS.Alg
-bestJWSAlg jwk = case view jwkMaterial jwk of
-  ECKeyMaterial k -> pure $ case view ecCrv k of
-    P_256 -> JWA.JWS.ES256
-    P_384 -> JWA.JWS.ES384
-    P_521 -> JWA.JWS.ES512
-    Secp256k1 -> JWA.JWS.ES256K
-  RSAKeyMaterial k ->
-    let
+bestJWSAlg jwk = chooseJWSAlg jwk Nothing
+
+-- | Choose the cryptographically strongest JWS algorithm for a
+-- given key, restricted to a given set of algorithms.  This
+-- function supports negotiation use cases where verifier's
+-- supported algorithms are advertised or known.
+--
+-- Throws an error if the key is too small or cannot be used for
+-- signing, or if there is no overlap between the allowed algorithms
+-- and the algorithms supported by the key type.
+--
+-- RSASSA-PSS algorithms are preferred over RSASSA-PKCS1-v1_5.
+--
+-- The JWK "alg" parameter is ignored.
+--
+negotiateJWSAlg
+  :: (MonadError e m, AsError e)
+  => JWK
+  -> NonEmpty JWA.JWS.Alg
+  -> m JWA.JWS.Alg
+negotiateJWSAlg jwk = chooseJWSAlg jwk . Just
+
+-- | General implementation used by 'bestJWSAlg' and 'negotiateJWSAlg'.
+--
+chooseJWSAlg
+  :: (MonadError e m, AsError e)
+  => JWK
+  -> Maybe (NonEmpty JWA.JWS.Alg)
+  -> m JWA.JWS.Alg
+chooseJWSAlg jwk allowed = case view jwkMaterial jwk of
+  ECKeyMaterial k -> case view ecCrv k of
+    P_256     | ok JWA.JWS.ES256  -> pure JWA.JWS.ES256
+    P_384     | ok JWA.JWS.ES384  -> pure JWA.JWS.ES384
+    P_521     | ok JWA.JWS.ES512  -> pure JWA.JWS.ES512
+    Secp256k1 | ok JWA.JWS.ES256K -> pure JWA.JWS.ES256K
+    _                             -> negoFail
+  RSAKeyMaterial k
+    | n < 2 ^ (2040 :: Integer) -> throwing_ _KeySizeTooSmall
+    | otherwise                 -> maybe negoFail pure (find ok rsaAlgs)
+    where
       Types.Base64Integer n = view rsaN k
-    in
-      if n >= 2 ^ (2040 :: Integer)
-      then pure JWA.JWS.PS512
-      else throwing_ _KeySizeTooSmall
+      rsaAlgs =
+        [ JWA.JWS.PS512 , JWA.JWS.PS384 , JWA.JWS.PS256
+        , JWA.JWS.RS512 , JWA.JWS.RS384 , JWA.JWS.RS256 ]
   OctKeyMaterial (OctKeyParameters (Types.Base64Octets k))
-    | B.length k >= 512 `div` 8 -> pure JWA.JWS.HS512
-    | B.length k >= 384 `div` 8 -> pure JWA.JWS.HS384
-    | B.length k >= 256 `div` 8 -> pure JWA.JWS.HS256
-    | otherwise -> throwing_ _KeySizeTooSmall
+    | B.length k >= 512 `div` 8, ok JWA.JWS.HS512 -> pure JWA.JWS.HS512
+    | B.length k >= 384 `div` 8, ok JWA.JWS.HS384 -> pure JWA.JWS.HS384
+    | B.length k >= 256 `div` 8, ok JWA.JWS.HS256 -> pure JWA.JWS.HS256
+    | B.length k >= 256 `div` 8                   -> negoFail
+    | otherwise                                   -> throwing_ _KeySizeTooSmall
   OKPKeyMaterial k -> case k of
-    (Ed25519Key _ _)  -> pure JWA.JWS.EdDSA
-    (Ed448Key _ _)    -> pure JWA.JWS.EdDSA
-    (X25519Key _ _)   -> throwing _KeyMismatch "Cannot sign with X25519 key"
-    (X448Key _ _)     -> throwing _KeyMismatch "Cannot sign with X448 key"
+    (X25519Key _ _)                     -> throwing _KeyMismatch "Cannot sign with X25519 key"
+    (X448Key _ _)                       -> throwing _KeyMismatch "Cannot sign with X448 key"
+    (Ed25519Key _ _) | ok JWA.JWS.EdDSA -> pure JWA.JWS.EdDSA
+    (Ed448Key _ _)   | ok JWA.JWS.EdDSA -> pure JWA.JWS.EdDSA
+    _                                   -> negoFail
+  where
+    ok alg = maybe True (alg `elem`) allowed
+    negoFail = throwing _AlgorithmMismatch "Algorithm negotation failed"
 
 
 -- | Compute the JWK Thumbprint of a JWK
diff --git a/src/Crypto/JOSE/JWK/Store.hs b/src/Crypto/JOSE/JWK/Store.hs
--- a/src/Crypto/JOSE/JWK/Store.hs
+++ b/src/Crypto/JOSE/JWK/Store.hs
@@ -12,9 +12,6 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-
 {-|
 
 Key stores.  Instances are provided for 'JWK' and 'JWKSet'.  These
diff --git a/src/Crypto/JOSE/JWS.hs b/src/Crypto/JOSE/JWS.hs
--- a/src/Crypto/JOSE/JWS.hs
+++ b/src/Crypto/JOSE/JWS.hs
@@ -34,12 +34,9 @@
 
 -}
 
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE UndecidableInstances #-}
 {-# LANGUAGE MonoLocalBinds #-}
-{-# LANGUAGE ScopedTypeVariables #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
 module Crypto.JOSE.JWS
@@ -55,6 +52,7 @@
 
   -- * JWS creation
   , newJWSHeader
+  , newJWSHeaderProtected
   , makeJWSHeader
   , signJWS
 
@@ -71,6 +69,9 @@
   , HasAlgorithms(..)
   , HasValidationPolicy(..)
 
+  -- * Access payload without verification
+  , unsafeGetPayload
+
   -- * Signature data
   , signatures
   , Signature
@@ -238,12 +239,17 @@
 
 
 -- | Construct a minimal header with the given algorithm and
--- protection indicator for the /alg/ header.
+-- protection value for the @"alg"@ header.
 --
 newJWSHeader :: (p, Alg) -> JWSHeader p
 newJWSHeader a = JWSHeader (uncurry HeaderParam a) z z z z z z z z z z
   where z = Nothing
 
+-- | Construct a minimal JWS header with the given @"alg"@ header
+-- value, to be carried as a protected header.
+newJWSHeaderProtected :: (ProtectionSupport p) => Alg -> JWSHeader p
+newJWSHeaderProtected a = newJWSHeader (getProtected, a)
+
 -- | Make a JWS header for the given signing key.
 --
 -- Uses 'bestJWSAlg' to choose the algorithm.
@@ -254,7 +260,7 @@
 -- May return 'KeySizeTooSmall' or 'KeyMismatch'.
 --
 makeJWSHeader
-  :: forall e m p. (MonadError e m, AsError e, ProtectionIndicator p)
+  :: forall e m p. (MonadError e m, AsError e, ProtectionSupport p)
   => JWK
   -> m (JWSHeader p)
 makeJWSHeader k = do
@@ -301,7 +307,7 @@
 instance (Eq (a p)) => Eq (Signature p a) where
   Signature _ h s == Signature _ h' s' = h == h' && s == s'
 
-instance (HasParams a, ProtectionIndicator p) => FromJSON (Signature p a) where
+instance (HasParams a, ProtectionSupport p) => FromJSON (Signature p a) where
   parseJSON = withObject "signature" (\o -> Signature
     <$> (Just <$> (o .: "protected" <|> pure ""))  -- raw protected header
     <*> do
@@ -318,7 +324,7 @@
     <*> o .: "signature"
     )
 
-instance (HasParams a, ProtectionIndicator p) => ToJSON (Signature p a) where
+instance (HasParams a, ProtectionSupport p) => ToJSON (Signature p a) where
   toJSON s@(Signature _ h sig) =
     let
       pro = case rawProtectedHeader s of
@@ -363,11 +369,10 @@
       ]
 
 
--- | JSON Web Signature data type.  The payload can only be
--- accessed by verifying the JWS.
+-- | JSON Web Signature data type.
 --
 -- Parameterised by the signature container type, the header
--- 'ProtectionIndicator' type, and the header record type.
+-- 'ProtectionSupport' type, and the header record type.
 --
 -- Use 'encode' and 'decode' to convert a JWS to or from JSON.
 -- When encoding a @'JWS' []@ with exactly one signature, the
@@ -380,28 +385,33 @@
 -- 'encodeCompact').
 --
 -- Use 'signJWS' to create a signed/MACed JWS.
---
--- Use 'verifyJWS' to verify a JWS and extract the payload.
+
+-- Use 'verifyJWS', 'verifyJWS'' or 'verifyJWSWithPayload' to verify
+-- a JWS and extract the payload.
 --
+-- Applications generally should not access a payload without
+-- first verifying it.  If you have an exceptional use case, you
+-- can use 'unsafeGetPayload' to access the payload.
+
 data JWS t p a = JWS Types.Base64Octets (t (Signature p a))
 
 -- | A JWS that allows multiple signatures, and cannot use
 -- the /compact serialisation/.  Headers may be 'Protected'
 -- or 'Unprotected'.
 --
-type GeneralJWS = JWS [] Protection
+type GeneralJWS = JWS [] OptionalProtection
 
 -- | A JWS with one signature, which uses the
 -- /flattened serialisation/.  Headers may be 'Protected'
 -- or 'Unprotected'.
 --
-type FlattenedJWS = JWS Identity Protection
+type FlattenedJWS = JWS Identity OptionalProtection
 
 -- | A JWS with one signature which only allows protected
 -- parameters.  Can use the /flattened serialisation/ or
 -- the /compact serialisation/.
 --
-type CompactJWS = JWS Identity ()
+type CompactJWS = JWS Identity RequiredProtection
 
 instance (Eq (t (Signature p a))) => Eq (JWS t p a) where
   JWS p sigs == JWS p' sigs' = p == p' && sigs == sigs'
@@ -412,30 +422,44 @@
 signatures :: Foldable t => Fold (JWS t p a) (Signature p a)
 signatures = folding (\(JWS _ sigs) -> sigs)
 
-instance (HasParams a, ProtectionIndicator p) => FromJSON (JWS [] p a) where
+instance (HasParams a, ProtectionSupport p) => FromJSON (JWS [] p a) where
   parseJSON v =
     withObject "JWS JSON serialization" (\o -> JWS
       <$> o .: "payload"
       <*> o .: "signatures") v
     <|> fmap (\(JWS p (Identity s)) -> JWS p [s]) (parseJSON v)
 
-instance (HasParams a, ProtectionIndicator p) => FromJSON (JWS Identity p a) where
+instance (HasParams a, ProtectionSupport p) => FromJSON (JWS Identity p a) where
   parseJSON =
     withObject "Flattened JWS JSON serialization" $ \o ->
       if M.member "signatures" o
       then fail "\"signatures\" member MUST NOT be present"
       else (\p s -> JWS p (pure s)) <$> o .: "payload" <*> parseJSON (Object o)
 
-instance (HasParams a, ProtectionIndicator p) => ToJSON (JWS [] p a) where
+instance (HasParams a, ProtectionSupport p) => ToJSON (JWS [] p a) where
   toJSON (JWS p [s]) = Types.insertToObject "payload" p (toJSON s)
   toJSON (JWS p ss) = object ["payload" .= p, "signatures" .= ss]
 
-instance (HasParams a, ProtectionIndicator p) => ToJSON (JWS Identity p a) where
+instance (HasParams a, ProtectionSupport p) => ToJSON (JWS Identity p a) where
   toJSON (JWS p (Identity s)) = Types.insertToObject "payload" p (toJSON s)
 
 
+-- | Get the payload __without verifying it__.  Do not use this
+-- function unless you have a compelling reason.
+--
+-- Most applications should use 'verifyJWSWithPayload', 'verifyJWS'
+-- or 'verifyJWS'' to verify the JWS and access the payload.
+--
+unsafeGetPayload
+  :: (Cons s s Word8 Word8, AsEmpty s)
+  => (s -> m payload)   -- ^ Function to decode payload
+  -> JWS t p a          -- ^ JWS
+  -> m payload
+unsafeGetPayload dec (JWS (Types.Base64Octets s) _) = views recons dec s
+
+
 signingInput
-  :: (HasParams a, ProtectionIndicator p)
+  :: (HasParams a, ProtectionSupport p)
   => Signature p a
   -> Types.Base64Octets
   -> B.ByteString
@@ -449,7 +473,7 @@
 -- Application code should never need to use this.  It is exposed
 -- for testing purposes.
 rawProtectedHeader
-  :: (HasParams a, ProtectionIndicator p)
+  :: (HasParams a, ProtectionSupport p)
   => Signature p a -> B.ByteString
 rawProtectedHeader (Signature raw h _) =
   maybe (view recons $ protectedParamsEncoded h) T.encodeUtf8 raw
@@ -459,13 +483,13 @@
 -- The operation is defined only when there is exactly one
 -- signature and returns Nothing otherwise
 --
-instance HasParams a => ToCompact (JWS Identity () a) where
+instance (HasParams a) => ToCompact (JWS Identity RequiredProtection a) where
   toCompact (JWS p (Identity s@(Signature _ _ (Types.Base64Octets sig)))) =
     [ view recons $ signingInput s p
     , review Types.base64url sig
     ]
 
-instance HasParams a => FromCompact (JWS Identity () a) where
+instance (HasParams a) => FromCompact (JWS Identity RequiredProtection a) where
   fromCompact xs = case xs of
     [h, p, s] -> do
       (h', p', s') <- (,,) <$> t 0 h <*> t 1 p <*> t 2 s
@@ -488,7 +512,7 @@
   :: ( Cons s s Word8 Word8
      , HasJWSHeader a, HasParams a, MonadRandom m, AsError e, MonadError e m
      , Traversable t
-     , ProtectionIndicator p
+     , ProtectionSupport p
      )
   => s          -- ^ Payload
   -> t (a p, JWK) -- ^ Traversable of header, key pairs
@@ -500,7 +524,7 @@
 
 mkSignature
   :: ( HasJWSHeader a, HasParams a, MonadRandom m, AsError e, MonadError e m
-     , ProtectionIndicator p
+     , ProtectionSupport p
      )
   => B.ByteString -> a p -> JWK -> m (Signature p a)
 mkSignature p h k =
@@ -585,7 +609,7 @@
       , VerificationKeyStore m (h p) s k
       , Cons s s Word8 Word8, AsEmpty s
       , Foldable t
-      , ProtectionIndicator p
+      , ProtectionSupport p
       )
   => k      -- ^ key or key store
   -> JWS t p h  -- ^ JWS
@@ -609,7 +633,7 @@
       , VerificationKeyStore m (h p) s k
       , Cons s s Word8 Word8, AsEmpty s
       , Foldable t
-      , ProtectionIndicator p
+      , ProtectionSupport p
       )
   => a        -- ^ validation settings
   -> k        -- ^ key or key store
@@ -618,20 +642,24 @@
 verifyJWS = verifyJWSWithPayload pure
 {-# INLINE verifyJWS #-}
 
+-- | Verify a JWS, with explicit payload decoding.  This variant
+-- enables the key store to use information in the payload to locate
+-- verification key(s).
+--
 verifyJWSWithPayload
   ::  ( HasAlgorithms a, HasValidationPolicy a, AsError e, MonadError e m
       , HasJWSHeader h, HasParams h
       , VerificationKeyStore m (h p) payload k
       , Cons s s Word8 Word8, AsEmpty s
       , Foldable t
-      , ProtectionIndicator p
+      , ProtectionSupport p
       )
   => (s -> m payload)  -- ^ payload decoder
   -> a                 -- ^ validation settings
   -> k                 -- ^ key or key store
   -> JWS t p h         -- ^ JWS
   -> m payload
-verifyJWSWithPayload dec conf k (JWS p@(Types.Base64Octets p') sigs) =
+verifyJWSWithPayload dec conf k jws@(JWS p sigs) =
   let
     algs :: S.Set Alg
     algs = conf ^. algorithms
@@ -645,17 +673,19 @@
 
     validate payload sig = do
       keys <- getVerificationKeys (view header sig) payload k
-      if null keys
-        then throwing_ _NoUsableKeys
-        else pure $ any ((== Right True) . verifySig p sig) keys
+      case (keys, view (header . alg . param) sig) of
+        -- special case for alg "none"
+        ([], None)  -> pure $ verifySig p sig undefined == Right True
+        ([], _)     -> throwing_ _NoUsableKeys
+        _           -> pure $ any ((== Right True) . verifySig p sig) keys
   in do
-    payload <- (dec . view recons) p'
+    payload <- unsafeGetPayload dec jws
     results <- traverse (validate payload) $ filter shouldValidateSig $ toList sigs
     payload <$ applyPolicy policy results
 {-# INLINE verifyJWSWithPayload #-}
 
 verifySig
-  :: (HasJWSHeader a, HasParams a, ProtectionIndicator p)
+  :: (HasJWSHeader a, HasParams a, ProtectionSupport p)
   => Types.Base64Octets
   -> Signature p a
   -> JWK
diff --git a/src/Crypto/JOSE/TH.hs b/src/Crypto/JOSE/TH.hs
--- a/src/Crypto/JOSE/TH.hs
+++ b/src/Crypto/JOSE/TH.hs
@@ -12,7 +12,6 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE TemplateHaskell #-}
 
 {-|
@@ -100,11 +99,7 @@
   let
     derive = map mkName ["Eq", "Ord", "Show"]
   in
-#if ! MIN_VERSION_template_haskell(2,12,0)
-    dataD (cxt []) (mkName s) [] Nothing (map conQ vs) (mapM conT derive)
-#else
     dataD (cxt []) (mkName s) [] Nothing (map conQ vs) [return (DerivClause Nothing (map ConT derive))]
-#endif
   , instanceD (cxt []) (aesonInstance s ''FromJSON) [parseJSONFun vs]
   , instanceD (cxt []) (aesonInstance s ''ToJSON) [toJSONFun vs]
   ]
diff --git a/src/Crypto/JOSE/Types/Internal.hs b/src/Crypto/JOSE/Types/Internal.hs
--- a/src/Crypto/JOSE/Types/Internal.hs
+++ b/src/Crypto/JOSE/Types/Internal.hs
@@ -12,9 +12,7 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE NoMonomorphismRestriction #-}
-{-# LANGUAGE OverloadedStrings #-}
 
 {-|
 
diff --git a/src/Crypto/JWT.hs b/src/Crypto/JWT.hs
--- a/src/Crypto/JWT.hs
+++ b/src/Crypto/JWT.hs
@@ -12,9 +12,6 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TemplateHaskell #-}
 {-# LANGUAGE UndecidableInstances #-}
@@ -43,8 +40,9 @@
   -- * API
   -- ** Creating a JWT
     SignedJWT
-  , signClaims
+  , SignedJWTWithHeader
   , signJWT
+  , signClaims
 
   -- ** Validating a JWT and extracting claims
   , defaultJWTValidationSettings
@@ -62,6 +60,10 @@
   , verifyClaimsAt
   , verifyJWTAt
 
+  -- ** Extracting claims without verification
+  , unsafeGetJWTPayload
+  , unsafeGetJWTClaimsSet
+
   -- ** Claims Set
   , ClaimsSet
   , emptyClaimsSet
@@ -133,7 +135,7 @@
 doJwtSign :: 'JWK' -> 'ClaimsSet' -> IO (Either 'JWTError' 'SignedJWT')
 doJwtSign jwk claims = 'runJOSE' $ do
   alg \<- 'bestJWSAlg' jwk
-  'signClaims' jwk ('newJWSHeader' ((), alg)) claims
+  'signClaims' jwk ('newJWSHeaderProtected' alg) claims
 
 doJwtVerify :: 'JWK' -> 'SignedJWT' -> IO (Either 'JWTError' 'ClaimsSet')
 doJwtVerify jwk jwt = 'runJOSE' $ do
@@ -151,8 +153,8 @@
   let
     k' = 'fromOctets' k      -- turn raw secret into symmetric JWK
     audCheck = const True  -- should be a proper audience check
-  jwt <- 'decodeCompact' s    -- decode JWT
-  'verifyClaims' ('defaultJWTValidationSettings' audCheck) k' jwt
+  jwt <- 'decodeCompact' s   -- decode JWT
+  'verifyClaims' ('defaultJWTValidationSettings' audCheck) k' (jwt :: 'SignedJWT')
 @
 
 -}
@@ -219,7 +221,7 @@
 -- contains a @:@ but does not parse as a 'URI'.  Use 'stringOrUri'
 -- directly in this situation.
 --
-data StringOrURI = Arbitrary T.Text | OrURI URI deriving (Eq, Show)
+data StringOrURI = Arbitrary T.Text | OrURI URI deriving (Eq, Show, Ord)
 
 -- | Non-total.  A string with a @':'@ in it MUST parse as a URI
 instance Data.String.IsString StringOrURI where
@@ -435,11 +437,7 @@
 
 filterUnregistered :: M.Map T.Text Value -> M.Map T.Text Value
 filterUnregistered m =
-#if MIN_VERSION_containers(0,5,8)
   m `M.withoutKeys` registeredClaims
-#else
-  m `M.difference` M.fromSet (const ()) registeredClaims
-#endif
 
 toKeyMap :: M.Map T.Text Value -> KeyMap.KeyMap Value
 toKeyMap = KeyMap.fromMap . M.mapKeysMonotonic Key.fromText
@@ -617,31 +615,51 @@
     unless (view issuerPredicate conf iss) (throwing_ _JWTNotInIssuer) )
   . preview (claimIss . _Just)
 
--- | A digitally signed or MACed JWT
+-- | A digitally signed or MAC'd JWT, with the JWS header type fixed
+-- at 'JWSHeader'.
 --
-type SignedJWT = CompactJWS JWSHeader
+type SignedJWT = SignedJWTWithHeader JWSHeader
 
+-- | A digitally signed or MAC'd JWT, with caller-specified JWS
+-- header type.  For information about defining custom header types
+-- see /Defining additional header parameters/ in "Crypto.JOSE.JWS".
+--
+type SignedJWTWithHeader h = CompactJWS h
 
 newtype WrappedUTCTime = WrappedUTCTime { getUTCTime :: UTCTime }
 
-#if MIN_VERSION_monad_time(0,4,0)
 -- | @'monotonicTime' = pure 0@.  /jose/ doesn't use this so we fake it
-#endif
 instance Monad m => MonadTime (ReaderT WrappedUTCTime m) where
   currentTime = asks getUTCTime
-#if MIN_VERSION_monad_time(0,4,0)
   monotonicTime = pure 0
-#endif
 
+-- | Get the JWT payload __without verifying it__.  Do not use this
+-- function unless you have a compelling reason.
+--
+-- Most applications should use 'verifyJWT' or one of its variants
+-- to verify the JWT and access the claims.
+--
+-- See also 'unsafeGetJWTClaimsSet' which is the same as this
+-- function with the payload type specialised to 'ClaimsSet'.
+--
+unsafeGetJWTPayload
+  :: ( FromJSON payload, AsJWTError e, MonadError e m )
+  => SignedJWT -> m payload
+unsafeGetJWTPayload = unsafeGetPayload f
+  where
+  f = either (throwing _JWTClaimsSetDecodeError) pure . eitherDecode
 
+-- | Variant of 'unsafeGetJWTPayload' specialised to 'ClaimsSet'
+unsafeGetJWTClaimsSet
+  :: ( AsJWTError e, MonadError e m )
+  => SignedJWT -> m ClaimsSet
+unsafeGetJWTClaimsSet = unsafeGetJWTPayload
+
+
 -- | Cryptographically verify a JWS JWT, then validate the
 -- Claims Set, returning it if valid.  The claims are validated
 -- at the current system time.
 --
--- This is the only way to get at the claims of a JWS JWT,
--- enforcing that the claims are cryptographically and
--- semantically valid before the application can use them.
---
 -- This function is abstracted over any payload type with 'HasClaimsSet' and
 -- 'FromJSON' instances.  The 'verifyClaims' variant uses 'ClaimsSet' as the
 -- payload type.
@@ -656,13 +674,18 @@
     , HasIssuerPredicate a
     , HasCheckIssuedAt a
     , HasValidationSettings a
+    , HasJWSHeader h, HasParams h
     , AsError e, AsJWTError e, MonadError e m
-    , VerificationKeyStore m (JWSHeader ()) payload k
+    , VerificationKeyStore m (h RequiredProtection) payload k
     , HasClaimsSet payload, FromJSON payload
     )
   => a
+  -- ^ Validation settings
   -> k
-  -> SignedJWT
+  -- ^ Key store
+  -> SignedJWTWithHeader h
+  -- ^ JWT.  Simple use cases may find the 'SignedJWT' type synonym useful for
+  -- fixing the type of @h@.
   -> m payload
 verifyJWT conf k jws =
   -- It is important, for security reasons, that the signature get
@@ -679,12 +702,17 @@
     , HasIssuerPredicate a
     , HasCheckIssuedAt a
     , HasValidationSettings a
+    , HasJWSHeader h, HasParams h
     , AsError e, AsJWTError e, MonadError e m
-    , VerificationKeyStore m (JWSHeader ()) ClaimsSet k
+    , VerificationKeyStore m (h RequiredProtection) ClaimsSet k
     )
   => a
+  -- ^ Validation settings
   -> k
-  -> SignedJWT
+  -- ^ Key store
+  -> SignedJWTWithHeader h
+  -- ^ JWT.  Simple use cases may find the 'SignedJWT' type synonym useful for
+  -- fixing the type of @h@.
   -> m ClaimsSet
 verifyClaims = verifyJWT
 
@@ -698,14 +726,20 @@
     , HasIssuerPredicate a
     , HasCheckIssuedAt a
     , HasValidationSettings a
+    , HasJWSHeader h, HasParams h
     , AsError e, AsJWTError e, MonadError e m
-    , VerificationKeyStore (ReaderT WrappedUTCTime m) (JWSHeader ()) payload k
+    , VerificationKeyStore (ReaderT WrappedUTCTime m) (h RequiredProtection) payload k
     , HasClaimsSet payload, FromJSON payload
     )
   => a
+  -- ^ Validation settings
   -> k
+  -- ^ Key store
   -> UTCTime
-  -> SignedJWT
+  -- ^ Validation time
+  -> SignedJWTWithHeader h
+  -- ^ JWT.  Simple use cases may find the 'SignedJWT' type synonym useful for
+  -- fixing the type of @h@.
   -> m payload
 verifyJWTAt a k t jwt = runReaderT (verifyJWT a k jwt) (WrappedUTCTime t)
 
@@ -718,13 +752,19 @@
     , HasIssuerPredicate a
     , HasCheckIssuedAt a
     , HasValidationSettings a
+    , HasJWSHeader h, HasParams h
     , AsError e, AsJWTError e, MonadError e m
-    , VerificationKeyStore (ReaderT WrappedUTCTime m) (JWSHeader ()) ClaimsSet k
+    , VerificationKeyStore (ReaderT WrappedUTCTime m) (h RequiredProtection) ClaimsSet k
     )
   => a
+  -- ^ Validation settings
   -> k
+  -- ^ Key store
   -> UTCTime
-  -> SignedJWT
+  -- ^ Validation time
+  -> SignedJWTWithHeader h
+  -- ^ JWT.  Simple use cases may find the 'SignedJWT' type synonym useful for
+  -- fixing the type of @h@.
   -> m ClaimsSet
 verifyClaimsAt = verifyJWTAt
 
@@ -738,11 +778,17 @@
 --
 signJWT
   :: ( MonadRandom m, MonadError e m, AsError e
+     , HasJWSHeader h, HasParams h
      , ToJSON payload )
   => JWK
-  -> JWSHeader ()
+  -- ^ Signing key
+  -> h RequiredProtection
+  -- ^ JWS Header.  Commonly this will be 'JWSHeader'.  If your application
+  -- uses additional header fields, see /Defining additional header parameters/
+  -- in "Crypto.JOSE.JWS".
   -> payload
-  -> m SignedJWT
+  -- ^ The payload ('ClaimsSet' or a subtype).
+  -> m (SignedJWTWithHeader h)
 signJWT k h c = signJWS (encode c) (Identity (h, k))
 
 -- | Create a JWS JWT.  Specialisation of 'signJWT' with payload type fixed
@@ -752,9 +798,15 @@
 -- ("Issued At") Claim.  The payload is encoded as-is.
 --
 signClaims
-  :: (MonadRandom m, MonadError e m, AsError e)
+  :: ( MonadRandom m, MonadError e m, AsError e
+     , HasJWSHeader h, HasParams h )
   => JWK
-  -> JWSHeader ()
+  -- ^ Signing key
+  -> h RequiredProtection
+  -- ^ JWS Header.  Commonly this will be 'JWSHeader'.  If your application
+  -- uses additional header fields, see /Defining additional header parameters/
+  -- in "Crypto.JOSE.JWS".
   -> ClaimsSet
-  -> m SignedJWT
+  -- ^ Payload
+  -> m (SignedJWTWithHeader h)
 signClaims = signJWT
diff --git a/test/Examples.hs b/test/Examples.hs
--- a/test/Examples.hs
+++ b/test/Examples.hs
@@ -1,5 +1,4 @@
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE LambdaCase #-}
 
 -- | Miscellaneous end-to-end examples.
 module Examples
@@ -42,7 +41,7 @@
           (do
             es256jwk <- errorOrJWK
             jwt <- decodeCompact es256token
-            verifyClaimsAt valSettings es256jwk now jwt) `shouldBe`
+            verifyClaimsAt valSettings es256jwk now (jwt :: SignedJWT)) `shouldBe`
           Right expectedClaims
 
       _ -> pure ()
diff --git a/test/JWS.hs b/test/JWS.hs
--- a/test/JWS.hs
+++ b/test/JWS.hs
@@ -26,6 +26,7 @@
 import Data.Aeson
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Base64.URL as B64U
+import qualified Data.Set as S
 import Test.Hspec
 
 import Crypto.JOSE.Compact
@@ -108,7 +109,7 @@
       -- protected header: {"kid":""}
       s = "{\"protected\":\"eyJraWQiOiIifQ\",\"header\":{\"alg\":\"none\",\"kid\":\"\"},\"signature\":\"\"}"
     in
-      (eitherDecode s :: Either String (Signature Protection JWSHeader))
+      (eitherDecode s :: Either String (Signature OptionalProtection JWSHeader))
         `shouldSatisfy` is _Left
 
   it "rejects reserved crit parameters" $
@@ -116,7 +117,7 @@
       -- protected header: {"crit":["kid"],"kid":""}
       s = "{\"protected\":\"eyJjcml0IjpbImtpZCJdLCJraWQiOiIifQ\",\"header\":{\"alg\":\"none\"},\"signature\":\"\"}"
     in
-      (eitherDecode s :: Either String (Signature Protection JWSHeader))
+      (eitherDecode s :: Either String (Signature OptionalProtection JWSHeader))
         `shouldSatisfy` is _Left
 
   it "rejects unknown crit parameters" $
@@ -124,7 +125,7 @@
       -- protected header: {"crit":["foo"],"foo":""}
       s = "{\"protected\":\"eyJjcml0IjpbImZvbyJdLCJmb28iOiIifQ\",\"header\":{\"alg\":\"none\"},\"signature\":\"\"}"
     in
-      (eitherDecode s :: Either String (Signature Protection JWSHeader))
+      (eitherDecode s :: Either String (Signature OptionalProtection JWSHeader))
         `shouldSatisfy` is _Left
 
   it "accepts known crit parameter in protected header" $
@@ -132,7 +133,7 @@
       -- protected header: {"crit":["foo"],"foo":""}
       s = "{\"protected\":\"eyJjcml0IjpbImZvbyJdLCJmb28iOiIifQ\",\"header\":{\"alg\":\"none\"},\"signature\":\"\"}"
     in
-      (eitherDecode s :: Either String (Signature Protection JWSHeader'))
+      (eitherDecode s :: Either String (Signature OptionalProtection JWSHeader'))
         `shouldSatisfy` is _Right
 
   it "accepts known crit parameter in unprotected header" $
@@ -140,7 +141,7 @@
       -- protected header: {"crit":["foo"]}
       s = "{\"protected\":\"eyJjcml0IjpbImZvbyJdfQ\",\"header\":{\"alg\":\"none\",\"foo\":\"\"},\"signature\":\"\"}"
     in
-      (eitherDecode s :: Either String (Signature Protection JWSHeader'))
+      (eitherDecode s :: Either String (Signature OptionalProtection JWSHeader'))
         `shouldSatisfy` is _Right
 
   it "rejects known crit parameter that does not appear in JOSE header" $
@@ -148,14 +149,14 @@
       -- protected header: {"crit":["foo"]}
       s = "{\"protected\":\"eyJjcml0IjpbImZvbyJdfQ\",\"header\":{\"alg\":\"none\"},\"signature\":\"\"}"
     in
-      (eitherDecode s :: Either String (Signature Protection JWSHeader'))
+      (eitherDecode s :: Either String (Signature OptionalProtection JWSHeader'))
         `shouldSatisfy` is _Left
 
   it "rejects unprotected crit parameters" $
     let
       s = "{\"header\":{\"alg\":\"none\",\"crit\":[\"foo\"],\"foo\":\"\"},\"signature\":\"\"}"
     in
-      (eitherDecode s :: Either String (Signature Protection JWSHeader'))
+      (eitherDecode s :: Either String (Signature OptionalProtection JWSHeader'))
         `shouldSatisfy` is _Left
 
   it "rejects empty crit parameters" $
@@ -163,7 +164,7 @@
       -- protected header: {"crit":[]}
       s = "{\"protected\":\"eyJjcml0IjpbXX0\",\"header\":{\"alg\":\"none\"},\"signature\":\"\"}"
     in
-      (eitherDecode s :: Either String (Signature Protection JWSHeader'))
+      (eitherDecode s :: Either String (Signature OptionalProtection JWSHeader'))
         `shouldSatisfy` is _Left
 
   it "parses required protected header when present in protected header" $
@@ -172,28 +173,28 @@
       s = "{\"protected\":\"eyJjcml0IjpbIm5vbmNlIl0sIm5vbmNlIjoiYm05dVkyVSJ9\""
           <> ",\"header\":{\"alg\":\"none\"},\"signature\":\"\"}"
     in
-      (eitherDecode s :: Either String (Signature Protection ACMEHeader))
+      (eitherDecode s :: Either String (Signature OptionalProtection ACMEHeader))
         `shouldSatisfy` is _Right
 
   it "rejects required protected header when present in unprotected header" $
     let
       s = "{\"header\":{\"alg\":\"none\"},\"nonce\":\"bm9uY2U\",\"signature\":\"\"}"
     in
-      (eitherDecode s :: Either String (Signature Protection ACMEHeader))
+      (eitherDecode s :: Either String (Signature OptionalProtection ACMEHeader))
         `shouldSatisfy` is _Left
 
-  it "accepts unprotected \"alg\" param with 'Protection' protection indicator" $
+  it "accepts unprotected \"alg\" param with 'OptionalProtection' protection indicator" $
     let
       s = "{\"header\":{\"alg\":\"none\"},\"signature\":\"\"}"
     in
-      (eitherDecode s :: Either String (Signature Protection JWSHeader))
+      (eitherDecode s :: Either String (Signature OptionalProtection JWSHeader))
         `shouldSatisfy` is _Right
 
-  it "rejects unprotected \"alg\" param with '()' protection indicator" $
+  it "rejects unprotected \"alg\" param with 'RequiredProtection' protection indicator" $
     let
       s = "{\"header\":{\"alg\":\"none\"},\"signature\":\"\"}"
     in
-      (eitherDecode s :: Either String (Signature () JWSHeader))
+      (eitherDecode s :: Either String (Signature RequiredProtection JWSHeader))
         `shouldSatisfy` is _Left
 
 
@@ -234,8 +235,8 @@
     compactJWS = signingInput' <> ".dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
     jws = decodeCompact compactJWS :: Either Error (CompactJWS JWSHeader)
     alg_ = JWA.JWS.HS256
-    h = newJWSHeader ((), alg_)
-        & typ .~ Just (HeaderParam () "JWT")
+    h = newJWSHeaderProtected alg_
+        & typ .~ Just (newHeaderParamProtected "JWT")
     mac = view recons
       [116, 24, 223, 180, 151, 153, 224, 37, 79, 250, 96, 125, 216, 173,
       187, 186, 22, 212, 37, 77, 105, 214, 191, 240, 91, 88, 5, 88, 83,
@@ -280,7 +281,7 @@
 
   it "prohibits signing with 1024-bit key" $
     fst (withDRG drg (runJOSE $
-      signJWS signingInput' (Identity (newJWSHeader ((), JWA.JWS.RS256), jwkRSA1024))))
+      signJWS signingInput' (Identity (newJWSHeaderProtected JWA.JWS.RS256, jwkRSA1024))))
         `shouldBe` (Left KeySizeTooSmall :: Either Error (CompactJWS JWSHeader))
 
   where
@@ -361,15 +362,20 @@
   it "decodes the correct JWS" $
     decodeCompact exampleJWS `shouldBe` jws
 
+  it "can be verified with an empty key set" $
+    (jws >>= verifyJWS conf (JWKSet []) :: Either Error BS.ByteString)
+      `shouldSatisfy` is _Right
+
   where
     jws = fst $ withDRG drg $ runJOSE $
-      signJWS examplePayloadBytes (Identity (newJWSHeader ((), JWA.JWS.None), undefined))
+      signJWS examplePayloadBytes (Identity (newJWSHeaderProtected JWA.JWS.None, undefined))
       :: Either Error (CompactJWS JWSHeader)
     exampleJWS = "eyJhbGciOiJub25lIn0\
       \.\
       \eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt\
       \cGxlLmNvbS9pc19yb290Ijp0cnVlfQ\
       \."
+    conf = set algorithms (S.singleton None) defaultValidationSettings
 
 
 appendixA6Spec :: Spec
diff --git a/test/JWT.hs b/test/JWT.hs
--- a/test/JWT.hs
+++ b/test/JWT.hs
@@ -12,7 +12,6 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE OverloadedStrings #-}
 
 module JWT
@@ -100,7 +99,7 @@
         valConf = defaultJWTValidationSettings (const True)
       k <- genJWK $ RSAGenParam 256
       res <- runJOSE $ do
-        token <- signClaims k (newJWSHeader ((), RS512)) claims
+        token <- signClaims k (newJWSHeaderProtected RS512) claims
         token' <- decodeCompact . encodeCompact $ token
         liftIO $ token' `shouldBe` token
         claims' <- verifyClaims valConf k token'
@@ -113,7 +112,7 @@
         now = utcTime "2010-01-01 00:00:00"
       k <- genJWK $ RSAGenParam 256
       res <- runJOSE $ do
-        token <- signJWT k (newJWSHeader ((), RS512)) super
+        token <- signJWT k (newJWSHeaderProtected RS512) super
         token' <- decodeCompact . encodeCompact $ token
         liftIO $ token' `shouldBe` token
         claims <- runReaderT (verifyJWT valConf k token') now
@@ -339,9 +338,9 @@
            192,205,154,245,103,208,128,163]
         now = utcTime "2010-01-01 00:00:00"
         settings = defaultJWTValidationSettings (const True)
-      runReaderT (decodeCompact exampleJWT >>= verifyClaims settings k) now
+      runReaderT (decodeCompact @SignedJWT exampleJWT >>= verifyClaims settings k) now
         `shouldBe` (Right exampleClaimsSet :: Either JWTError ClaimsSet)
-      runReaderT (decodeCompact exampleJWT >>= verifyJWT settings k) now
+      runReaderT (decodeCompact @SignedJWT exampleJWT >>= verifyJWT settings k) now
         `shouldBe` (Right super :: Either JWTError Super)
 
   describe "RFC 7519 §6.1.  Example Unsecured JWT" $ do
@@ -352,7 +351,7 @@
         <> "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt"
         <> "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
         <> "."
-      jwt = decodeCompact exampleJWT
+      jwt = decodeCompact @SignedJWT exampleJWT
       k = fromJust $ decode "{\"kty\":\"oct\",\"k\":\"\"}" :: JWK
 
     describe "when the current time is prior to the Expiration Time" $
@@ -367,7 +366,7 @@
 
     describe "when signature is invalid and token is expired" $
       it "fails on sig validation (claim validation not reached)" $ do
-        let jwt' = decodeCompact (exampleJWT <> "badsig")
+        let jwt' = decodeCompact @SignedJWT (exampleJWT <> "badsig")
         (runReaderT (jwt' >>= verifyClaims conf k) (utcTime "2012-01-01 00:00:00")
           :: Either JWTError ClaimsSet)
           `shouldSatisfy` is (_Left . _JWSInvalidSignature)
diff --git a/test/Perf.hs b/test/Perf.hs
--- a/test/Perf.hs
+++ b/test/Perf.hs
@@ -1,6 +1,4 @@
 {-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
 
 {-
 
@@ -17,7 +15,7 @@
 import Control.Lens ((^?), _Just)
 import Control.Monad.Except (ExceptT, runExceptT)
 import Crypto.JOSE.JWK.Store (VerificationKeyStore (getVerificationKeys))
-import Crypto.JWT (CompactJWS, HasX5c (x5c), JWSHeader, JWTError, decodeCompact, fromX509Certificate, param, verifyJWS')
+import Crypto.JWT (CompactJWS, HasX5c (x5c), JWSHeader, JWTError, RequiredProtection, decodeCompact, fromX509Certificate, param, verifyJWS')
 import qualified Data.ByteString as B
 import qualified Data.ByteString.Lazy as L
 import Data.List.NonEmpty (NonEmpty ((:|)))
@@ -26,7 +24,7 @@
 
 data Store = Store
 
-instance VerificationKeyStore (ExceptT JWTError IO) (JWSHeader ()) B.ByteString Store where
+instance VerificationKeyStore (ExceptT JWTError IO) (JWSHeader RequiredProtection) B.ByteString Store where
   getVerificationKeys header _ _ = do
     let Just (x :| _) = header ^? x5c . _Just . param
     res <- fromX509Certificate x
diff --git a/test/Properties.hs b/test/Properties.hs
--- a/test/Properties.hs
+++ b/test/Properties.hs
@@ -21,7 +21,6 @@
   ( properties
   ) where
 
-import Control.Applicative (liftA2)
 import Control.Monad.IO.Class
 
 import Control.Lens ((&), set, view)
