diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,146 @@
+## 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
+  fork of *cryptonite*, which was no longer maintained.  With this
+  change, the minimum supported version of GHC increased to 8.8.
+  There are no other notable changes in this release.
+
+- The `v0.10` series is the last release series to support
+  *cryptonite*.  It will continue to receive important bug fixes
+  until the end of 2024.
+
+
+## Version 0.10 (2022-09-01)
+
+- Introduce `newtype JOSE e m a` which behaves like `ExceptT e m a`
+  but also has `instance (MonadRandom m) => MonadRandom (JOSE e m)`.
+  The orphan `MonadRandom` instances were removed. ([#91][])
+
+- Parameterise `JWT` over the claims data type.  This is a
+  cleaner mechanism to support applications that use additional
+  claims beyond those registered by RFC 7519.  `unregisteredClaims`
+  and `addClaim` are deprecated and will be removed in a future
+  release. ([#39][])
+
+- Add Ed448 and X448 support. ([#74][])
+
+- Add secp256k1 curve support (RFC 8812).
+
+- Added `checkJWK :: (MonadError e m, AsError e) => JWK -> m ()`.
+  This action performs some key usability checks.  In particular
+  it identifies too-small symmetric keys.  ([#46][])
+
+- Removed `QuickCheck` instances.  *jose* no longer depends on
+  `QuickCheck`.  ([#106][])
+
+- Removed orphan `ToJSON` and `FromJSON` instances for `URI`.
+
+- Fail signature verification when curve does not match algorithm.
+  This is an additional defence against curve substitution attacks.
+
+- Improved error reporting when constructing a JWK from an X.509
+  certificate with ECDSA key.
+
+- Make compatible with `mtl == 2.3.*` ([#107][])
+
+- Make compatible with `monad-time == 0.4`
+
+[#39]: https://github.com/frasertweedale/hs-jose/issues/39
+[#46]: https://github.com/frasertweedale/hs-jose/issues/46
+[#74]: https://github.com/frasertweedale/hs-jose/issues/74
+[#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
+
+See Git commit history
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,15 +1,16 @@
-# jose - Javascript Object Signing and Encryption & JWT (JSON Web Token)
+# jose - JSON Object Signing and Encryption & JWT (JSON Web Token)
 
-*jose* is a Haskell implementation of [Javascript Object Signing and
-Encryption](https://datatracker.ietf.org/wg/jose/) and [JSON Web
-Token](https://tools.ietf.org/html/rfc7519).
+*jose* is a Haskell implementation of [JSON Object Signing and
+Encryption (JOSE)](https://datatracker.ietf.org/wg/jose/) and [JSON
+Web Token (JWT)](https://tools.ietf.org/html/rfc7519).
 
 The JSON Web Signature (JWS; RFC 7515) implementation is complete.
 JSON Web Encryption (JWE; RFC 7516) is not yet implemented.
 
-**EdDSA** signatures (RFC 8037) are supported (Ed25519 only).
+**EdDSA** signatures (RFC 8037) and secp256k1 signatures (RFC 8812)
+are supported.
 
-JWK Thumbprint (RFC 7638) is supported (requires *aeson* >= 0.10).
+JWK Thumbprint (RFC 7638) is supported.
 
 [Contributions](#contributing) are welcome.
 
diff --git a/example/JWS.hs b/example/JWS.hs
--- a/example/JWS.hs
+++ b/example/JWS.hs
@@ -1,8 +1,26 @@
-module JWS where
+-- Copyright (C) 2017-2022  Fraser Tweedale
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--      http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
 
+{-# OPTIONS_GHC -fno-warn-incomplete-patterns #-}
+
+module JWS
+  ( doJwsSign
+  , doJwsVerify
+  ) where
+
 import System.Exit (exitFailure)
 
-import Control.Monad.Except (runExceptT)
 import Data.Aeson (decode, encode)
 import qualified Data.ByteString.Lazy as L
 
@@ -17,11 +35,11 @@
 --
 doJwsSign :: [String] -> IO ()
 doJwsSign [jwkFilename, payloadFilename] = do
-  Just jwk <- decode <$> L.readFile jwkFilename
+  Just k <- decode <$> L.readFile jwkFilename
   payload <- L.readFile payloadFilename
-  result <- runExceptT $ do
-    h <- makeJWSHeader jwk
-    signJWS payload [(h :: JWSHeader Protection, jwk)]
+  result <- runJOSE $ do
+    h <- makeJWSHeader k
+    signJWS payload [(h :: JWSHeader OptionalProtection, k)]
   case result of
     Left e -> print (e :: Error) >> exitFailure
     Right jws -> L.putStr (encode jws)
@@ -36,9 +54,9 @@
 --
 doJwsVerify :: [String] -> IO ()
 doJwsVerify [jwkFilename, jwsFilename] = do
-  Just jwk <- decode <$> L.readFile jwkFilename
+  Just k <- decode <$> L.readFile jwkFilename
   Just jws <- decode <$> L.readFile jwsFilename
-  result <- runExceptT $ verifyJWS' (jwk :: JWK) (jws :: GeneralJWS JWSHeader)
+  result <- runJOSE $ verifyJWS' (k :: JWK) (jws :: GeneralJWS JWSHeader)
   case result of
     Left e -> print (e :: Error) >> exitFailure
     Right s -> L.putStr s
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(..)
@@ -9,7 +5,6 @@
 
 import Control.Exception (IOException, handle)
 import Data.Maybe (catMaybes)
-import Data.Semigroup ((<>))
 
 import Control.Monad.Trans (MonadIO(..))
 import Control.Lens (_Just, preview)
diff --git a/example/Main.hs b/example/Main.hs
--- a/example/Main.hs
+++ b/example/Main.hs
@@ -1,8 +1,6 @@
 {-# OPTIONS_GHC -fno-warn-incomplete-patterns #-}
-{-# LANGUAGE FlexibleContexts #-}
 
 import Data.Maybe (fromJust)
-import Data.Semigroup ((<>))
 import System.Environment (getArgs)
 import System.Exit (die, exitFailure)
 
@@ -11,7 +9,6 @@
 import Data.Text.Strict.Lens (utf8)
 import System.Posix.Files (getFileStatus, isDirectory)
 
-import Control.Monad.Except (runExceptT)
 import Control.Lens (preview, re, review, set, view)
 
 import Crypto.JWT
@@ -54,7 +51,7 @@
 doJwtSign [jwkFilename, claimsFilename] = do
   Just k <- decode <$> L.readFile jwkFilename
   Just claims <- decode <$> L.readFile claimsFilename
-  result <- runExceptT $ makeJWSHeader k >>= \h -> signClaims k h claims
+  result <- runJOSE $ makeJWSHeader k >>= \h -> signClaims k h claims
   case result of
     Left e -> print (e :: Error) >> exitFailure
     Right jwt -> L.putStr (encodeCompact jwt)
@@ -77,7 +74,7 @@
   let
     aud' = fromJust $ preview stringOrUri aud
     conf = defaultJWTValidationSettings (== aud')
-    go k = runExceptT (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,17 +1,16 @@
 cabal-version:       2.2
 name:                jose
-version:             0.8.5.1
+version:             0.13
 synopsis:
-  Javascript Object Signing and Encryption (JOSE)
-  and JSON Web Token (JWT) library
+  JSON Object Signing and Encryption (JOSE) and JSON Web Token (JWT) library
 description:
   .
-  An implementation of the Javascript Object Signing and Encryption
-  (JOSE) and JSON Web Token (JWT; RFC 7519) formats.
+  Implementation of JSON Object Signing and Encryption
+  (JOSE) and JSON Web Token (JWT; RFC 7519).
   .
   The JSON Web Signature (JWS; RFC 7515) implementation is complete.
   .
-  EdDSA signatures (RFC 8037) are supported (Ed25519 only).
+  EdDSA signatures (RFC 8037) and secp256k1 (RFC 8812) are supported.
   .
   JWK Thumbprint (RFC 7638) is supported.
   .
@@ -25,34 +24,50 @@
 license:             Apache-2.0
 license-file:        LICENSE
 extra-source-files:
-  README.md
   test/data/fido.jwt
+extra-doc-files:
+  CHANGELOG.md
+  README.md
 author:              Fraser Tweedale
 maintainer:          frase@frase.id.au
-copyright:           Copyright (C) 2013-2018  Fraser Tweedale
+copyright:           Copyright (C) 2013-2025  Fraser Tweedale
 category:            Cryptography
 build-type:          Simple
 tested-with:
-  GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.4, GHC==8.6.5, GHC==8.8.4, GHC==8.10.4, GHC==9.0.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
-  ghc-options:    -Wall
+  default-language: GHC2021
+  default-extensions: LambdaCase
+  ghc-options:
+    -Wall
+    -Widentities
+    -Wincomplete-record-updates
+    -Wincomplete-uni-patterns
+    -Werror=missing-methods
+    -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.8 && < 5
-    , aeson >= 0.11.1.0 && < 2.0
-    , bytestring == 0.10.*
+    base >= 4.18 && < 5
+    , bytestring >= 0.10 && < 0.13
     , lens >= 4.16
-    , mtl >= 2
-    , text >= 1.1
-  if impl(ghc < 8.0)
-    build-depends:
-      semigroups >= 0.15
+    , mtl >= 2.2.1
 
 library
   import: common
@@ -77,28 +92,21 @@
   other-modules:
     Crypto.JOSE.TH
     Crypto.JOSE.Types.Internal
-    Crypto.JOSE.Types.Orphans
+    Crypto.JOSE.Types.URI
 
   build-depends:
-    attoparsec
+    , aeson >= 2.0.1.0 && < 3
     , base64-bytestring >= 1.2.1.0 && < 1.3
     , concise >= 0.1
-    , containers >= 0.5
-    , cryptonite >= 0.7
-    , memory >= 0.7
-    , monad-time >= 0.1
-    , template-haskell >= 2.4
-    , safe >= 0.3
-    , unordered-containers == 0.2.*
+    , 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
-    , QuickCheck >= 2.9
-    , quickcheck-instances
-    , x509 >= 1.4
-    , vector
-
-  if impl(ghc<8)
-    build-depends: fail
+    , crypton-x509 >= 1.7.6
 
   hs-source-dirs: src
 
@@ -121,30 +129,23 @@
     Types
 
   build-depends:
-    attoparsec
+    , aeson
     , base64-bytestring
     , containers
-    , cryptonite
-    , memory
-    , monad-time
-    , template-haskell
-    , safe
-    , unordered-containers
+    , crypton
     , time
     , network-uri
-    , vector
-    , x509
+    , crypton-x509
     , pem
 
     , concise
     , jose
 
     , tasty
+    , tasty-hedgehog >= 1.2
     , tasty-hspec >= 1.0
-    , tasty-quickcheck
+    , hedgehog
     , hspec
-    , QuickCheck >= 2.9
-    , quickcheck-instances
 
 test-suite perf
   import: common
@@ -165,5 +166,7 @@
     JWS
 
   build-depends:
-    unix
+    aeson
+    , text
+    , unix
     , jose
diff --git a/src/Crypto/JOSE.hs b/src/Crypto/JOSE.hs
--- a/src/Crypto/JOSE.hs
+++ b/src/Crypto/JOSE.hs
@@ -19,7 +19,8 @@
 -}
 module Crypto.JOSE
   (
-    module Crypto.JOSE.Compact
+    vulnerableToHashFlood
+  , module Crypto.JOSE.Compact
   , module Crypto.JOSE.Error
   , module Crypto.JOSE.JWK
   , module Crypto.JOSE.JWK.Store
@@ -31,6 +32,22 @@
 import Crypto.JOSE.JWK
 import Crypto.JOSE.JWK.Store
 import Crypto.JOSE.JWS
-import Crypto.JOSE.Types (base64url)
 
+import qualified Data.Aeson.KeyMap as KeyMap
+
 {-# ANN module ("HLint: ignore Use import/export shortcut" :: String) #-}
+
+-- | /aeson/ supports multiple map implementations.  The
+-- implementation using @Data.HashMap@ from *unordered-containers*
+-- is vulnerable to hash-flooding DoS attacks.  If your program
+-- processes JOSE objects from untrusted sources, you can check this
+-- value to find out if the *aeson* build uses a secure map
+-- implementation, or not.
+--
+vulnerableToHashFlood :: Bool
+vulnerableToHashFlood = case KeyMap.coercionToMap of
+  -- Don't check that the map implementation is NOT Data.HashMap, in
+  -- case some other insecure implementation emerges.  Instead,
+  -- check that the implementation IS known to be secure.
+  Just _  -> False
+  Nothing -> True
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;
@@ -26,7 +24,8 @@
   , aesKeyUnwrap
   ) where
 
-import Control.Monad.State
+import Control.Monad (join)
+import Control.Monad.State (StateT, execStateT, get, lift, put)
 import Crypto.Cipher.Types
 import Data.Bits (xor)
 import Data.ByteArray as BA hiding (replicate, xor)
diff --git a/src/Crypto/JOSE/Compact.hs b/src/Crypto/JOSE/Compact.hs
--- a/src/Crypto/JOSE/Compact.hs
+++ b/src/Crypto/JOSE/Compact.hs
@@ -21,7 +21,12 @@
 functions for working with such data.
 
 -}
-module Crypto.JOSE.Compact where
+module Crypto.JOSE.Compact
+  ( FromCompact(..)
+  , decodeCompact
+  , ToCompact(..)
+  , encodeCompact
+  ) where
 
 import Control.Monad.Except (MonadError)
 import qualified Data.ByteString.Lazy as L
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
@@ -1,4 +1,4 @@
--- Copyright (C) 2014  Fraser Tweedale
+-- Copyright (C) 2014-2022  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,19 +12,22 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE TemplateHaskell #-}
-{-# LANGUAGE UndecidableInstances #-}
-{-# OPTIONS_GHC -fno-warn-orphans #-}
 
 {-|
 
-JOSE error types.
+JOSE error types and helpers.
 
 -}
 module Crypto.JOSE.Error
   (
-    Error(..)
+  -- * Running JOSE computations
+    runJOSE
+  , unwrapJOSE
+  , JOSE(..)
+
+  -- * Base error type and class
+  , Error(..)
   , AsError(..)
 
   -- * JOSE compact serialisation errors
@@ -33,12 +36,13 @@
   , CompactDecodeError(..)
   , _CompactInvalidNumberOfParts
   , _CompactInvalidText
+
   ) where
 
-import Data.Semigroup ((<>))
 import Numeric.Natural
 
-import Control.Monad.Trans (MonadTrans(..))
+import Control.Monad.Except (MonadError(..), ExceptT, runExceptT)
+import Control.Monad.Trans (MonadIO(liftIO), MonadTrans(lift))
 import qualified Crypto.PubKey.RSA as RSA
 import Crypto.Error (CryptoError)
 import Crypto.Random (MonadRandom(..))
@@ -118,10 +122,39 @@
 makeClassyPrisms ''Error
 
 
-instance (
-    MonadRandom m
-  , MonadTrans t
-  , Functor (t m)
-  , Monad (t m)
-  ) => MonadRandom (t m) where
+newtype JOSE e m a = JOSE (ExceptT e m a)
+
+-- | Run the 'JOSE' computation.  Result is an @Either e a@
+-- where @e@ is the error type (typically 'Error' or 'Crypto.JWT.JWTError')
+runJOSE :: JOSE e m a -> m (Either e a)
+runJOSE = runExceptT . (\(JOSE m) -> m)
+
+-- | Get the inner 'ExceptT' value of the 'JOSE' computation.
+-- Typically 'runJOSE' would be preferred, unless you specifically
+-- need an 'ExceptT' value.
+unwrapJOSE :: JOSE e m a -> ExceptT e m a
+unwrapJOSE (JOSE m) = m
+
+
+instance (Functor m) => Functor (JOSE e m) where
+  fmap f (JOSE ma) = JOSE (fmap f ma)
+
+instance (Monad m) => Applicative (JOSE e m) where
+  pure = JOSE . pure
+  JOSE mf <*> JOSE ma = JOSE (mf <*> ma)
+
+instance (Monad m) => Monad (JOSE e m) where
+  JOSE ma >>= f = JOSE (ma >>= unwrapJOSE . f)
+
+instance MonadTrans (JOSE e) where
+  lift = JOSE . lift
+
+instance (Monad m) => MonadError e (JOSE e m) where
+  throwError = JOSE . throwError
+  catchError (JOSE m) handle = JOSE (catchError m (unwrapJOSE . handle))
+
+instance (MonadIO m) => MonadIO (JOSE e m) where
+  liftIO = JOSE . liftIO
+
+instance (MonadRandom m) => MonadRandom (JOSE e m) where
     getRandomBytes = lift . getRandomBytes
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
@@ -36,6 +42,7 @@
   , headerRequired
   , headerRequiredProtected
   , headerOptional
+  , headerOptional'
   , headerOptionalProtected
 
   -- * Parsing headers
@@ -63,30 +70,30 @@
 
 
 import qualified Control.Monad.Fail as Fail
+import Data.Kind (Type)
 import Data.List.NonEmpty (NonEmpty)
-import Data.Monoid ((<>))
 import Data.Proxy (Proxy(..))
 
 import Control.Lens (Lens', Getter, review, to)
 import Data.Aeson (FromJSON(..), Object, Value, encode, object)
 import Data.Aeson.Types (Pair, Parser)
+import qualified Data.Aeson.Key as Key
+import qualified Data.Aeson.KeyMap as M
 import qualified Data.ByteString.Lazy as L
-import qualified Data.HashMap.Strict as M
 import qualified Data.Text as T
 
 import qualified Crypto.JOSE.JWA.JWS as JWA.JWS
 import Crypto.JOSE.JWK (JWK)
-import Crypto.JOSE.Types.Orphans ()
 import Crypto.JOSE.Types.Internal (base64url)
 import qualified Crypto.JOSE.Types as Types
 
 
 -- | A thing with parameters.
 --
-class HasParams (a :: * -> *) where
+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.
@@ -94,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)
@@ -104,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
@@ -121,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
@@ -129,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
 
@@ -149,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
 
 
@@ -177,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.
@@ -223,19 +259,34 @@
 -- the protected or the unprotected header.
 --
 headerOptional
-  :: (FromJSON a, ProtectionIndicator p)
+  :: (FromJSON a, ProtectionSupport p)
   => T.Text
   -> Maybe Object
   -> Maybe Object
   -> Parser (Maybe (HeaderParam p a))
-headerOptional k hp hu = case (hp >>= M.lookup k, hu >>= M.lookup k) of
-  (Just _, Just _)    -> fail $ "duplicate header " ++ show k
-  (Just v, Nothing)   -> Just . HeaderParam getProtected <$> parseJSON v
+headerOptional = headerOptional' parseJSON
+
+-- | Parse an optional parameter that may be carried in either
+-- the protected or the unprotected header.  Like 'headerOptional',
+-- but with an explicit argument for the parser.
+--
+headerOptional'
+  :: (ProtectionSupport p)
+  => (Value -> Parser a)
+  -> T.Text
+  -> Maybe Object
+  -> Maybe Object
+  -> Parser (Maybe (HeaderParam p a))
+headerOptional' parser kText hp hu = case (hp >>= M.lookup k, hu >>= M.lookup k) of
+  (Just _, Just _)    -> fail $ "duplicate header " ++ show kText
+  (Just v, Nothing)   -> Just . HeaderParam getProtected <$> parser v
   (Nothing, Just v)   -> maybe
     (fail "unprotected header not supported")
-    (\p -> Just . HeaderParam p <$> parseJSON v)
+    (\p -> Just . HeaderParam p <$> parser v)
     getUnprotected
   (Nothing, Nothing)  -> pure Nothing
+  where
+    k = Key.fromText kText
 
 -- | Parse an optional parameter that, if present, MUST be carried
 -- in the protected header.
@@ -246,29 +297,33 @@
   -> Maybe Object
   -> Maybe Object
   -> Parser (Maybe a)
-headerOptionalProtected k hp hu = case (hp >>= M.lookup k, hu >>= M.lookup k) of
-  (Just _, Just _)    -> fail $ "duplicate header " ++ show k
-  (_, Just _) -> fail $ "header must be protected: " ++ show k
+headerOptionalProtected kText hp hu = case (hp >>= M.lookup k, hu >>= M.lookup k) of
+  (Just _, Just _)    -> fail $ "duplicate header " ++ show kText
+  (_, Just _) -> fail $ "header must be protected: " ++ show kText
   (Just v, _) -> Just <$> parseJSON v
   _           -> pure Nothing
+  where
+    k = Key.fromText kText
 
 -- | Parse a required parameter that may be carried in either
 -- the protected or the unprotected header.
 --
 headerRequired
-  :: (FromJSON a, ProtectionIndicator p)
+  :: (FromJSON a, ProtectionSupport p)
   => T.Text
   -> Maybe Object
   -> Maybe Object
   -> Parser (HeaderParam p a)
-headerRequired k hp hu = case (hp >>= M.lookup k, hu >>= M.lookup k) of
-  (Just _, Just _)    -> fail $ "duplicate header " ++ show k
+headerRequired kText hp hu = case (hp >>= M.lookup k, hu >>= M.lookup k) of
+  (Just _, Just _)    -> fail $ "duplicate header " ++ show kText
   (Just v, Nothing)   -> HeaderParam getProtected <$> parseJSON v
   (Nothing, Just v)   -> maybe
     (fail "unprotected header not supported")
     (\p -> HeaderParam p <$> parseJSON v)
     getUnprotected
   (Nothing, Nothing)  -> fail $ "missing required header " ++ show k
+  where
+    k = Key.fromText kText
 
 -- | Parse a required parameter that MUST be carried
 -- in the protected header.
@@ -279,11 +334,13 @@
   -> Maybe Object
   -> Maybe Object
   -> Parser a
-headerRequiredProtected k hp hu = case (hp >>= M.lookup k, hu >>= M.lookup k) of
-  (Just _, Just _)    -> fail $ "duplicate header " ++ show k
-  (_, Just _) -> fail $ "header must be protected: " <> show k
+headerRequiredProtected kText hp hu = case (hp >>= M.lookup k, hu >>= M.lookup k) of
+  (Just _, Just _)    -> fail $ "duplicate header " ++ show kText
+  (_, Just _) -> fail $ "header must be protected: " <> show kText
   (Just v, _) -> parseJSON v
-  _           -> fail $ "missing required protected header: " <> show k
+  _           -> fail $ "missing required protected header: " <> show kText
+  where
+    k = Key.fromText kText
 
 
 critObjectParser
@@ -292,7 +349,7 @@
 critObjectParser reserved exts o s
   | s `elem` reserved         = Fail.fail "crit key is reserved"
   | s `notElem` exts          = Fail.fail "crit key is not understood"
-  | not (s `M.member` o)      = Fail.fail "crit key is not present in headers"
+  | not (Key.fromText s `M.member` o) = Fail.fail "crit key is not present in headers"
   | otherwise                 = pure s
 
 -- | Parse a "crit" header param
diff --git a/src/Crypto/JOSE/JWA/JWE.hs b/src/Crypto/JOSE/JWA/JWE.hs
--- a/src/Crypto/JOSE/JWA/JWE.hs
+++ b/src/Crypto/JOSE/JWA/JWE.hs
@@ -20,18 +20,23 @@
 JSON Web Encryption data types specified under JSON Web Algorithms.
 
 -}
-module Crypto.JOSE.JWA.JWE where
+module Crypto.JOSE.JWA.JWE
+  ( Enc(..)
+  , AlgWithParams(..)
+  , AESGCMParameters(AESGCMParameters)
+  , ECDHParameters(ECDHParameters)
+  , PBES2Parameters(PBES2Parameters)
+  ) where
 
 import Data.Maybe (catMaybes)
 
-import qualified Data.HashMap.Strict as M
-
 import Crypto.JOSE.JWK
 import Crypto.JOSE.TH
 import Crypto.JOSE.Types
-import Crypto.JOSE.Types.Internal (objectPairs)
+import Crypto.JOSE.Types.Internal (insertToObject)
 
 import Data.Aeson
+import qualified Data.Aeson.KeyMap as M
 
 
 -- | RFC 7518 §4.  Cryptographic Algorithms for Key Management
@@ -84,7 +89,7 @@
 algObject s = object [("alg", s)]
 
 algWithParamsObject :: ToJSON a => a -> Value -> Value
-algWithParamsObject a s = object $ ("alg", s) : objectPairs (toJSON a)
+algWithParamsObject a s = insertToObject "alg" s (toJSON a)
 
 instance ToJSON AlgWithParams where
   toJSON RSA1_5       = algObject "RSA1_5"
diff --git a/src/Crypto/JOSE/JWA/JWE/Alg.hs b/src/Crypto/JOSE/JWA/JWE/Alg.hs
--- a/src/Crypto/JOSE/JWA/JWE/Alg.hs
+++ b/src/Crypto/JOSE/JWA/JWE/Alg.hs
@@ -20,7 +20,9 @@
 JSON Web Encryption algorithms.
 
 -}
-module Crypto.JOSE.JWA.JWE.Alg where
+module Crypto.JOSE.JWA.JWE.Alg
+  ( Alg(..)
+  ) where
 
 import qualified Crypto.JOSE.TH
 
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,14 +12,10 @@
 -- 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 #-}
-{-# LANGUAGE TupleSections #-}
-{-# LANGUAGE TypeFamilies #-}
 
 {-|
 
@@ -32,12 +28,14 @@
 
   -- * Parameters for Elliptic Curve Keys
   , Crv(..)
-  , ECKeyParameters
+  , ECKeyParameters(ECKeyParameters)
   , ecCrv, ecX, ecY, ecD
   , curve
   , point
   , ecPrivateKey
   , ecParametersFromX509
+  , ecParametersFromX509Priv
+  , genEC
 
   -- * Parameters for RSA Keys
   , RSAPrivateKeyOthElem(..)
@@ -59,6 +57,7 @@
   -- * Parameters for CFRG EC keys (RFC 8037)
   , OKPKeyParameters(..)
   , OKPCrv(..)
+  , genOKP
 
   -- * Key generation
   , KeyMaterialGenParam(..)
@@ -72,13 +71,11 @@
   , module Crypto.Random
   ) where
 
-import Control.Applicative
 import Control.Monad (guard)
 import Control.Monad.Except (MonadError)
 import Data.Bifunctor
 import Data.Foldable (toList)
 import Data.Maybe (isJust)
-import Data.Monoid ((<>))
 
 import Control.Lens hiding ((.=), elements)
 import Control.Monad.Error.Lens (throwing, throwing_)
@@ -93,32 +90,29 @@
 import qualified Crypto.PubKey.RSA.PSS as PSS
 import qualified Crypto.PubKey.ECC.Types as ECC
 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 Crypto.Random
 import Data.Aeson
+import qualified Data.Aeson.KeyMap as M
 import qualified Data.ByteArray as BA
 import qualified Data.ByteString as B
-import qualified Data.HashMap.Strict as M
 import Data.List.NonEmpty (NonEmpty)
 import qualified Data.Text as T
 import Data.X509 as X509
 import Data.X509.EC as X509.EC
-import Test.QuickCheck (Arbitrary(..), arbitrarySizedNatural, elements, oneof, vectorOf)
 
 import Crypto.JOSE.Error
 import qualified Crypto.JOSE.JWA.JWS as JWA.JWS
 import qualified Crypto.JOSE.TH
 import qualified Crypto.JOSE.Types as Types
 import qualified Crypto.JOSE.Types.Internal as Types
-import Crypto.JOSE.Types.Orphans ()
 
 
 -- | \"crv\" (Curve) Parameter
 --
-$(Crypto.JOSE.TH.deriveJOSEType "Crv" ["P-256", "P-384", "P-521"])
-
-instance Arbitrary Crv where
-  arbitrary = elements [P_256, P_384, P_521]
+$(Crypto.JOSE.TH.deriveJOSEType "Crv" ["P-256", "P-384", "P-521", "secp256k1"])
 
 
 -- | \"oth\" (Other Primes Info) Parameter
@@ -139,10 +133,7 @@
 instance ToJSON RSAPrivateKeyOthElem where
   toJSON (RSAPrivateKeyOthElem r d t) = object ["r" .= r, "d" .= d, "t" .= t]
 
-instance Arbitrary RSAPrivateKeyOthElem where
-  arbitrary = RSAPrivateKeyOthElem <$> arbitrary <*> arbitrary <*> arbitrary
 
-
 -- | Optional parameters for RSA private keys
 --
 data RSAPrivateKeyOptionalParameters = RSAPrivateKeyOptionalParameters {
@@ -173,16 +164,7 @@
     , "qi" .= rsaQi
     ] ++ maybe [] ((:[]) . ("oth" .=)) rsaOth
 
-instance Arbitrary RSAPrivateKeyOptionalParameters where
-  arbitrary = RSAPrivateKeyOptionalParameters
-    <$> arbitrary
-    <*> arbitrary
-    <*> arbitrary
-    <*> arbitrary
-    <*> arbitrary
-    <*> arbitrary
 
-
 -- | RSA private key parameters
 --
 data RSAPrivateKeyParameters = RSAPrivateKeyParameters
@@ -200,15 +182,17 @@
       else pure Nothing)
 
 instance ToJSON RSAPrivateKeyParameters where
-  toJSON RSAPrivateKeyParameters {..} = object $
-    ("d" .= rsaD) : maybe [] (Types.objectPairs . toJSON) rsaOptionalParameters
-
-instance Arbitrary RSAPrivateKeyParameters where
-  arbitrary = RSAPrivateKeyParameters <$> arbitrary <*> arbitrary
+  toJSON RSAPrivateKeyParameters {..} =
+    Types.insertToObject "d" rsaD
+      $ 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
@@ -248,16 +232,6 @@
     , "y" .= view ecY k
     ] <> fmap ("d" .=) (toList (view ecD k))
 
-instance Arbitrary ECKeyParameters where
-  arbitrary = do
-    drg <- drgNewTest <$> arbitrary
-    crv <- arbitrary
-    let (params, _) = withDRG drg (genEC crv)
-    includePrivate <- arbitrary
-    if includePrivate
-      then pure params
-      else pure params { _ecD = Nothing }
-
 genEC :: MonadRandom m => Crv -> m ECKeyParameters
 genEC crv = do
   let i = Types.SizedBase64Integer (ecCoordBytes crv)
@@ -307,11 +281,13 @@
   (\case
     P_256 -> ECC.SEC_p256r1
     P_384 -> ECC.SEC_p384r1
-    P_521 -> ECC.SEC_p521r1)
+    P_521 -> ECC.SEC_p521r1
+    Secp256k1 -> ECC.SEC_p256k1)
   (\case
     ECC.SEC_p256r1 -> Just P_256
     ECC.SEC_p384r1 -> Just P_384
     ECC.SEC_p521r1 -> Just P_521
+    ECC.SEC_p256k1 -> Just Secp256k1
     _              -> Nothing)
 
 point :: ECKeyParameters -> ECC.Point
@@ -323,25 +299,58 @@
 ecCoordBytes P_256 = 32
 ecCoordBytes P_384 = 48
 ecCoordBytes P_521 = 66
+ecCoordBytes Secp256k1 = 32
 
 ecPrivateKey :: (MonadError e m, AsError e) => ECKeyParameters -> m Integer
 ecPrivateKey (ECKeyParameters _ _ _ (Just (Types.SizedBase64Integer _ d))) = pure d
 ecPrivateKey _ = throwing _KeyMismatch "Not an EC private key"
 
-ecParametersFromX509 :: X509.PubKeyEC -> Maybe ECKeyParameters
-ecParametersFromX509 pubKeyEC = do
-  ecCurve <- X509.EC.ecPubKeyCurve pubKeyEC
-  curveName <- X509.EC.ecPubKeyCurveName pubKeyEC
-  crv <- preview fromCurveName curveName
-  pt <- X509.EC.unserializePoint ecCurve (X509.pubkeyEC_pub pubKeyEC)
+-- | See also 'Crypto.JOSE.JWK.fromX509PubKey' and
+-- 'Crypto.JOSE.JWK.fromX509Certificate'.
+--
+ecParametersFromX509 :: (MonadError e m, AsError e) => X509.PubKeyEC -> m ECKeyParameters
+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    -> Nothing
+    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
   , _rsaE :: Types.Base64Integer
@@ -361,17 +370,13 @@
         else pure Nothing
 
 instance ToJSON RSAKeyParameters where
-  toJSON RSAKeyParameters {..} = object $
-      ("kty" .= ("RSA" :: T.Text))
-    : ("n" .= _rsaN)
-    : ("e" .= _rsaE)
-    : maybe [] (Types.objectPairs . toJSON) _rsaPrivateKeyParameters
-
-instance Arbitrary RSAKeyParameters where
-  arbitrary = RSAKeyParameters
-    <$> arbitrary
-    <*> arbitrary
-    <*> arbitrary
+  toJSON RSAKeyParameters {..} =
+    Types.insertManyToObject
+      [ "kty" .= ("RSA" :: T.Text)
+      , "n" .= _rsaN
+      , "e" .= _rsaE
+      ]
+      $ maybe (Object mempty) toJSON _rsaPrivateKeyParameters
 
 genRSA :: MonadRandom m => Int -> m RSAKeyParameters
 genRSA size = toRSAKeyParameters . snd <$> RSA.generate size 65537
@@ -472,9 +477,6 @@
     , "k" .= (view octK k :: Types.Base64Octets)
     ]
 
-instance Arbitrary OctKeyParameters where
-  arbitrary = OctKeyParameters <$> arbitrary
-
 signOct
   :: forall h e m. (HashAlgorithm h, MonadError e m, AsError e)
   => h
@@ -491,13 +493,17 @@
 --
 data OKPKeyParameters
   = Ed25519Key Ed25519.PublicKey (Maybe Ed25519.SecretKey)
+  | Ed448Key Ed448.PublicKey (Maybe Ed448.SecretKey)
   | X25519Key Curve25519.PublicKey (Maybe Curve25519.SecretKey)
+  | X448Key Curve448.PublicKey (Maybe Curve448.SecretKey)
   deriving (Eq)
 
 instance Show OKPKeyParameters where
   show = \case
       Ed25519Key pk sk  -> "Ed25519 " <> showKeys pk sk
+      Ed448Key pk sk  -> "Ed448 " <> showKeys pk sk
       X25519Key pk sk   -> "X25519 " <> showKeys pk sk
+      X448Key pk sk   -> "X448 " <> showKeys pk sk
     where
       showKeys pk sk = show pk <> " " <> show (("SECRET" :: String) <$ sk)
 
@@ -508,8 +514,8 @@
     case (crv :: T.Text) of
       "Ed25519" -> parseOKPKey Ed25519Key Ed25519.publicKey Ed25519.secretKey o
       "X25519"  -> parseOKPKey X25519Key Curve25519.publicKey Curve25519.secretKey o
-      "Ed448"   -> fail "Ed448 keys not implemented"
-      "X448"    -> fail "X448 not implemented"
+      "Ed448"   -> parseOKPKey Ed448Key Ed448.publicKey Ed448.secretKey o
+      "X448"    -> parseOKPKey X448Key Curve448.publicKey Curve448.secretKey o
       _         -> fail "unrecognised OKP key subtype"
     where
       bs (Types.Base64Octets k) = k
@@ -522,39 +528,22 @@
   toJSON x = object $
     "kty" .= ("OKP" :: T.Text) : case x of
       Ed25519Key pk sk -> "crv" .= ("Ed25519" :: T.Text) : params pk sk
+      Ed448Key pk sk -> "crv" .= ("Ed448" :: T.Text) : params pk sk
       X25519Key pk sk  -> "crv" .= ("X25519" :: T.Text) : params pk sk
+      X448Key pk sk  -> "crv" .= ("X448" :: T.Text) : params pk sk
     where
       b64 = Types.Base64Octets . BA.convert
       params pk sk = "x" .= b64 pk : (("d" .=) . b64 <$> toList sk)
 
-instance Arbitrary OKPKeyParameters where
-  arbitrary = oneof
-    [ Ed25519Key
-        <$> keyOfLen 32 Ed25519.publicKey
-        <*> oneof [pure Nothing, Just <$> keyOfLen 32 Ed25519.secretKey]
-    , X25519Key
-        <$> keyOfLen 32 Curve25519.publicKey
-        <*> oneof [pure Nothing, Just <$> keyOfLen 32 Curve25519.secretKey]
-    ]
-    where
-      bsOfLen n = B.pack <$> vectorOf n arbitrary
-      keyOfLen n con = onCryptoFailure (error . show) id . con <$> bsOfLen n
-
-data OKPCrv = Ed25519 | X25519
+data OKPCrv = Ed25519 | Ed448 | X25519 | X448
   deriving (Eq, Show)
 
-instance Arbitrary OKPCrv where
-  arbitrary = elements [Ed25519, X25519]
-
 genOKP :: MonadRandom m => OKPCrv -> m OKPKeyParameters
 genOKP = \case
-  Ed25519 -> go 32 Ed25519Key Ed25519.secretKey Ed25519.toPublic
-  X25519 -> go 32 X25519Key Curve25519.secretKey Curve25519.toPublic
-  where
-    go len con skCon toPub = do
-      (bs :: B.ByteString) <- getRandomBytes len
-      let sk = onCryptoFailure (error . show) id (skCon bs)
-      pure $ con (toPub sk) (Just sk)
+  Ed25519 -> Ed25519.generateSecretKey >>= \k -> pure (Ed25519Key (Ed25519.toPublic k) (Just k))
+  Ed448 -> Ed448.generateSecretKey >>= \k -> pure (Ed448Key (Ed448.toPublic k) (Just k))
+  X25519 -> Curve25519.generateSecretKey >>= \k -> pure (X25519Key (Curve25519.toPublic k) (Just k))
+  X448 -> Curve448.generateSecretKey >>= \k -> pure (X448Key (Curve448.toPublic k) (Just k))
 
 signEdDSA
   :: (MonadError e m, AsError e)
@@ -562,8 +551,11 @@
   -> B.ByteString
   -> m B.ByteString
 signEdDSA (Ed25519Key pk (Just sk)) m = pure . BA.convert $ Ed25519.sign sk pk m
-signEdDSA (Ed25519Key _ Nothing) _ = throwing _KeyMismatch "not a private key"
-signEdDSA _ _ = throwing _KeyMismatch "not an EdDSA key"
+signEdDSA (Ed25519Key _   Nothing)  _ = throwing _KeyMismatch "not a private key"
+signEdDSA (Ed448Key pk (Just sk))   m = pure . BA.convert $ Ed448.sign sk pk m
+signEdDSA (Ed448Key _   Nothing)    _ = throwing _KeyMismatch "not a private key"
+signEdDSA (X25519Key _ _) _ = throwing _KeyMismatch "not an EdDSA key"
+signEdDSA (X448Key _ _)   _ = throwing _KeyMismatch "not an EdDSA key"
 
 verifyEdDSA
   :: (BA.ByteArrayAccess msg, BA.ByteArrayAccess sig, MonadError e m, AsError e)
@@ -573,7 +565,13 @@
     (throwing _CryptoError)
     (pure . Ed25519.verify pk m)
     (Ed25519.signature s)
-verifyEdDSA _ _ _ = throwing _AlgorithmMismatch "not an EdDSA key"
+verifyEdDSA (Ed448Key pk _) m s =
+  onCryptoFailure
+    (throwing _CryptoError)
+    (pure . Ed448.verify pk m)
+    (Ed448.signature s)
+verifyEdDSA (X25519Key _ _) _ _ = throwing _AlgorithmMismatch "not an EdDSA key"
+verifyEdDSA (X448Key _ _)   _ _ = throwing _AlgorithmMismatch "not an EdDSA key"
 
 
 -- | Key material sum type.
@@ -602,10 +600,10 @@
       Just s      -> fail $ "unsupported \"kty\": " <> show s
 
 instance ToJSON KeyMaterial where
-  toJSON (ECKeyMaterial p)  = object $ Types.objectPairs (toJSON p)
-  toJSON (RSAKeyMaterial p) = object $ Types.objectPairs (toJSON p)
-  toJSON (OctKeyMaterial p) = object $ Types.objectPairs (toJSON p)
-  toJSON (OKPKeyMaterial p) = object $ Types.objectPairs (toJSON p)
+  toJSON (ECKeyMaterial p)  = toJSON p
+  toJSON (RSAKeyMaterial p) = toJSON p
+  toJSON (OctKeyMaterial p) = toJSON p
+  toJSON (OKPKeyMaterial p) = toJSON p
 
 -- | Keygen parameters.
 --
@@ -620,14 +618,6 @@
   -- ^ Generate an EdDSA or Edwards ECDH key with specified curve.
   deriving (Eq, Show)
 
-instance Arbitrary KeyMaterialGenParam where
-  arbitrary = oneof
-    [ ECGenParam <$> arbitrary
-    , RSAGenParam <$> elements ((`div` 8) <$> [2048, 3072, 4096])
-    , OctGenParam <$> liftA2 (+) arbitrarySizedNatural (elements [32, 48, 64])
-    , OKPGenParam <$> arbitrary
-    ]
-
 genKeyMaterial :: MonadRandom m => KeyMaterialGenParam -> m KeyMaterial
 genKeyMaterial (ECGenParam crv) = ECKeyMaterial <$> genEC crv
 genKeyMaterial (RSAGenParam size) = RSAKeyMaterial <$> genRSA size
@@ -645,6 +635,7 @@
 sign JWA.JWS.ES256 (ECKeyMaterial k@ECKeyParameters{ _ecCrv = P_256 }) = signEC SHA256 k
 sign JWA.JWS.ES384 (ECKeyMaterial k@ECKeyParameters{ _ecCrv = P_384 }) = signEC SHA384 k
 sign JWA.JWS.ES512 (ECKeyMaterial k@ECKeyParameters{ _ecCrv = P_521 }) = signEC SHA512 k
+sign JWA.JWS.ES256K (ECKeyMaterial k@ECKeyParameters{ _ecCrv = Secp256k1 }) = signEC SHA256 k
 sign JWA.JWS.RS256 (RSAKeyMaterial k) = signPKCS15 SHA256 k
 sign JWA.JWS.RS384 (RSAKeyMaterial k) = signPKCS15 SHA384 k
 sign JWA.JWS.RS512 (RSAKeyMaterial k) = signPKCS15 SHA512 k
@@ -666,9 +657,10 @@
   -> B.ByteString
   -> m Bool
 verify JWA.JWS.None _ = \_ s -> pure $ s == ""
-verify JWA.JWS.ES256 (ECKeyMaterial k) = fmap pure . verifyEC SHA256 k
-verify JWA.JWS.ES384 (ECKeyMaterial k) = fmap pure . verifyEC SHA384 k
-verify JWA.JWS.ES512 (ECKeyMaterial k) = fmap pure . verifyEC SHA512 k
+verify JWA.JWS.ES256 (ECKeyMaterial k@ECKeyParameters{ _ecCrv = P_256 }) = fmap pure . verifyEC SHA256 k
+verify JWA.JWS.ES384 (ECKeyMaterial k@ECKeyParameters{ _ecCrv = P_384 }) = fmap pure . verifyEC SHA384 k
+verify JWA.JWS.ES512 (ECKeyMaterial k@ECKeyParameters{ _ecCrv = P_521 }) = fmap pure . verifyEC SHA512 k
+verify JWA.JWS.ES256K (ECKeyMaterial k@ECKeyParameters{ _ecCrv = Secp256k1 }) = fmap pure . verifyEC SHA256 k
 verify JWA.JWS.RS256 (RSAKeyMaterial k) = fmap pure . verifyPKCS15 SHA256 k
 verify JWA.JWS.RS384 (RSAKeyMaterial k) = fmap pure . verifyPKCS15 SHA384 k
 verify JWA.JWS.RS512 (RSAKeyMaterial k) = fmap pure . verifyPKCS15 SHA512 k
@@ -682,15 +674,7 @@
 verify h k = \_ _ -> throwing _AlgorithmMismatch
   (show h <> " cannot be used with " <> showKeyType k <> " key")
 
-instance Arbitrary KeyMaterial where
-  arbitrary = oneof
-    [ ECKeyMaterial <$> arbitrary
-    , RSAKeyMaterial <$> arbitrary
-    , OctKeyMaterial <$> arbitrary
-    , OKPKeyMaterial <$> arbitrary
-    ]
 
-
 -- | Keys that may have have public material
 --
 class AsPublicKey k where
@@ -706,8 +690,10 @@
 
 instance AsPublicKey OKPKeyParameters where
   asPublicKey = to $ \case
-    Ed25519Key pk _ -> Just (Ed25519Key pk Nothing)
-    X25519Key pk _  -> Just (X25519Key pk Nothing)
+    Ed25519Key  pk _  -> Just (Ed25519Key pk Nothing)
+    Ed448Key    pk _  -> Just (Ed448Key pk Nothing)
+    X25519Key   pk _  -> Just (X25519Key pk Nothing)
+    X448Key     pk _  -> Just (X448Key pk Nothing)
 
 instance AsPublicKey KeyMaterial where
   asPublicKey = to $ \case
diff --git a/src/Crypto/JOSE/JWA/JWS.hs b/src/Crypto/JOSE/JWA/JWS.hs
--- a/src/Crypto/JOSE/JWA/JWS.hs
+++ b/src/Crypto/JOSE/JWA/JWS.hs
@@ -20,7 +20,9 @@
 JSON Web Signature algorithms.
 
 -}
-module Crypto.JOSE.JWA.JWS where
+module Crypto.JOSE.JWA.JWS
+  ( Alg(..)
+  ) where
 
 import qualified Crypto.JOSE.TH
 
@@ -37,6 +39,7 @@
   , "ES256" -- ECDSA P curve and SHA ; RECOMMENDED+
   , "ES384" -- ECDSA P curve and SHA ; OPTIONAL
   , "ES512" -- ECDSA P curve and SHA ; OPTIONAL
+  , "ES256K" -- ECDSA using secp256k1 curve and SHA-256
   , "PS256" -- RSASSA-PSS SHA ; OPTIONAL
   , "PS384" -- RSASSA-PSS SHA ; OPTIONAL
   , "PS512" -- RSSSSA-PSS SHA ; OPTIONAL
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
   (
@@ -27,9 +24,8 @@
 import Control.Applicative ((<|>))
 import Data.Bifunctor (bimap)
 import Data.Maybe (catMaybes, fromMaybe)
-import Data.Monoid ((<>))
 
-import Control.Lens (view)
+import Control.Lens (view, views)
 import Data.Aeson
 import Data.Aeson.Types
 import qualified Data.ByteArray as BA
@@ -53,6 +49,7 @@
 import Crypto.JOSE.JWA.JWE
 import Crypto.JOSE.JWK
 import qualified Crypto.JOSE.Types as Types
+import Crypto.JOSE.Types.URI
 import qualified Crypto.JOSE.Types.Internal as Types
 
 
@@ -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
@@ -92,10 +89,10 @@
     <$> parseJSON (Object (fromMaybe mempty hp <> fromMaybe mempty hu))
     <*> headerRequired "enc" hp hu
     <*> headerOptionalProtected "zip" hp hu
-    <*> headerOptional "jku" hp hu
+    <*> headerOptional' uriFromJSON "jku" hp hu
     <*> headerOptional "jwk" hp hu
     <*> headerOptional "kid" hp hu
-    <*> headerOptional "x5u" hp hu
+    <*> headerOptional' uriFromJSON "x5u" hp hu
     <*> (fmap . fmap . fmap . fmap)
           (\(Types.Base64X509 cert) -> cert) (headerOptional "x5c" hp hu)
     <*> headerOptional "x5t" hp hu
@@ -110,10 +107,10 @@
       [ undefined -- TODO
       , Just (view isProtected enc,      "enc" .= view param enc)
       , fmap (\p -> (True, "zip" .= p)) zip'
-      , fmap (\p -> (view isProtected p, "jku" .= view param p)) jku
+      , fmap (\p -> (view isProtected p, "jku" .= views param uriToJSON p)) jku
       , fmap (\p -> (view isProtected p, "jwk" .= view param p)) jwk
       , fmap (\p -> (view isProtected p, "kid" .= view param p)) kid
-      , fmap (\p -> (view isProtected p, "x5u" .= view param p)) x5u
+      , fmap (\p -> (view isProtected p, "x5u" .= views param uriToJSON p)) x5u
       , fmap (\p -> (view isProtected p, "x5c" .= fmap Types.Base64X509 (view param p))) x5c
       , fmap (\p -> (view isProtected p, "x5t" .= view param p)) x5t
       , fmap (\p -> (view isProtected p, "x5t#S256" .= view param p)) x5tS256
@@ -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
@@ -261,12 +258,14 @@
     CryptoFailed _ -> return $ Left AlgorithmNotImplemented -- FIXME
     CryptoPassed (e :: e) -> do
       iv <- getRandomBytes 16
-      let Just iv' = makeIV iv
-      let m' = pad (PKCS7 $ blockSize e) m
-      let c = cbcEncrypt e iv' m'
-      let hmacInput = B.concat [aad, iv, c, aadLen]
-      let tag = B.take kLen $ BA.pack $ BA.unpack (hmac mKey hmacInput :: HMAC h)
-      return $ Right (iv, c, tag)
+      case makeIV iv of
+        Nothing -> pure $ Left (CryptoError CryptoError_IvSizeInvalid)
+        Just iv' -> do
+          let m' = pad (PKCS7 $ blockSize e) m
+          let c = cbcEncrypt e iv' m'
+          let hmacInput = B.concat [aad, iv, c, aadLen]
+          let tag = BA.convert $ BA.takeView (hmac mKey hmacInput :: HMAC h) kLen
+          pure $ Right (iv, c, tag)
 
 _gcmEnc
   :: forall e m. (BlockCipher e, MonadRandom m)
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,11 @@
 -- 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 #-}
 
 {-|
 
@@ -32,10 +31,10 @@
 --
 doGen :: IO JWK
 doGen = do
-  jwk <- 'genJWK' (RSAGenParam (4096 \`div` 8))
+  jwk <- 'genJWK' ('RSAGenParam' (4096 \`div` 8))
   let
     h = view 'thumbprint' jwk :: Digest SHA256
-    kid = view (re ('base64url' . 'digest') . utf8) h
+    kid = view (re ('Types.base64url' . 'digest') . utf8) h
   pure $ set 'jwkKid' (Just kid) jwk
 @
 
@@ -68,8 +67,11 @@
   -- * Converting from other key formats
   , fromKeyMaterial
   , fromRSA
+  , fromRSAPublic
   , fromOctets
   , fromX509Certificate
+  , fromX509PubKey
+  , fromX509PrivKey
 
   -- * JWK Thumbprint
   , thumbprint
@@ -81,6 +83,8 @@
   , JWKSet(..)
 
   -- Miscellaneous
+  , checkJWK
+  , negotiateJWSAlg
   , bestJWSAlg
 
   , module Crypto.JOSE.JWA.JWK
@@ -89,17 +93,22 @@
 import Control.Applicative
 import Control.Monad ((>=>))
 import Data.Function (on)
+import Data.List (find)
 import Data.Maybe (catMaybes)
-import Data.Monoid ((<>))
 import Data.Word (Word8)
 
 import Control.Lens hiding ((.=))
 import Control.Lens.Cons.Extras (recons)
-import Control.Monad.Except (MonadError)
+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')
 import qualified Data.ByteArray as BA
 import qualified Data.ByteString as B
 import qualified Data.ByteString.Lazy as L
@@ -108,14 +117,13 @@
 import qualified Data.Text as T
 import qualified Data.X509 as X509
 
-import Test.QuickCheck
-
 import Crypto.JOSE.Error
 import qualified Crypto.JOSE.JWA.JWE.Alg as JWA.JWE
 import Crypto.JOSE.JWA.JWK
 import qualified Crypto.JOSE.JWA.JWS as JWA.JWS
 import qualified Crypto.JOSE.TH
 import qualified Crypto.JOSE.Types as Types
+import Crypto.JOSE.Types.URI
 import qualified Crypto.JOSE.Types.Internal as Types
 
 
@@ -197,7 +205,7 @@
     <*> o .:? "key_ops"
     <*> o .:? "alg"
     <*> o .:? "kid"
-    <*> o .:? "x5u"
+    <*> explicitParseFieldMaybe' uriFromJSON o "x5u"
     <*> ((fmap . fmap) (\(Types.Base64X509 cert) -> cert) <$> o .:? "x5c")
     <*> o .:? "x5t"
     <*> o .:? "x5t#S256"
@@ -209,35 +217,24 @@
       | otherwise = pure k
 
 instance ToJSON JWK where
-  toJSON JWK{..} = object $ catMaybes
-    [ fmap ("alg" .=) _jwkAlg
-    , fmap ("use" .=) _jwkUse
-    , fmap ("key_ops" .=) _jwkKeyOps
-    , fmap ("kid" .=) _jwkKid
-    , fmap ("x5u" .=) _jwkX5u
-    , fmap (("x5c" .=) . fmap Types.Base64X509) _jwkX5cRaw
-    , fmap ("x5t" .=) _jwkX5t
-    , fmap ("x5t#S256" .=) _jwkX5tS256
-    ]
-    ++ Types.objectPairs (toJSON _jwkMaterial)
+  toJSON JWK{..} = Types.insertManyToObject kvs (toJSON _jwkMaterial)
+    where
+      kvs = catMaybes
+        [ fmap ("alg" .=) _jwkAlg
+        , fmap ("use" .=) _jwkUse
+        , fmap ("key_ops" .=) _jwkKeyOps
+        , fmap ("kid" .=) _jwkKid
+        , fmap ("x5u" .=) (uriToJSON <$> _jwkX5u)
+        , fmap (("x5c" .=) . fmap Types.Base64X509) _jwkX5cRaw
+        , fmap ("x5t" .=) _jwkX5t
+        , fmap ("x5t#S256" .=) _jwkX5tS256
+        ]
 
 -- | Generate a JWK.  Apart from key parameters, no other parameters are set.
 --
 genJWK :: MonadRandom m => KeyMaterialGenParam -> m JWK
 genJWK p = fromKeyMaterial <$> genKeyMaterial p
 
-instance Arbitrary JWK where
-  arbitrary = JWK
-    <$> arbitrary
-    <*> pure Nothing
-    <*> pure Nothing
-    <*> pure Nothing
-    <*> arbitrary
-    <*> pure Nothing
-    <*> pure Nothing
-    <*> arbitrary
-    <*> arbitrary
-
 fromKeyMaterial :: KeyMaterial -> JWK
 fromKeyMaterial k = JWK k z z z z z z z z where z = Nothing
 
@@ -252,12 +249,7 @@
 fromRSAPublic :: RSA.PublicKey -> JWK
 fromRSAPublic = fromKeyMaterial . RSAKeyMaterial . toRSAPublicKeyParameters
 
--- | Convert an EC public key into a JWK
---
-fromECPublic :: X509.PubKeyEC -> Maybe JWK
-fromECPublic = fmap (fromKeyMaterial . ECKeyMaterial) . ecParametersFromX509
 
-
 -- | Convert octet string into a JWK
 --
 fromOctets :: Cons s s Word8 Word8 => s -> JWK
@@ -267,28 +259,61 @@
 {-# 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.
 --
--- Only RSA keys are supported.  Other key types will throw
--- 'KeyMismatch'.
+-- 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.
 --
 fromX509Certificate
   :: (AsError e, MonadError e m)
   => X509.SignedCertificate -> m JWK
-fromX509Certificate =
-  maybe (throwing _KeyMismatch "X.509 key type not supported") pure
-  . fromX509CertificateMaybe
-
-fromX509CertificateMaybe :: X509.SignedCertificate -> Maybe JWK
-fromX509CertificateMaybe cert = do
-  k <- case (X509.certPubKey . X509.signedObject . X509.getSigned) cert of
-    X509.PubKeyRSA k -> pure (fromRSAPublic k)
-    X509.PubKeyEC k -> fromECPublic k
-    _ -> Nothing
+fromX509Certificate cert = do
+  k <- fromX509PubKey . X509.certPubKey . X509.signedObject $ X509.getSigned cert
   pure $ k & set jwkX5cRaw (Just (pure cert))
 
+fromX509CertificateMaybe :: X509.SignedCertificate -> Maybe JWK
+fromX509CertificateMaybe = f . runExcept . fromX509Certificate
+  where
+    f :: Either Error JWK -> Maybe JWK
+    f (Left _) = Nothing
+    f (Right jwk) = Just jwk
 
 
 instance AsPublicKey JWK where
@@ -297,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")
@@ -306,32 +331,95 @@
   toJSON (JWKSet ks) = object ["keys" .= toJSON ks]
 
 
+-- | Sanity-check a JWK.
+--
+-- Return an appropriate error if the key is size is too small to be
+-- used with any JOSE algorithm, or for other problems that mean the
+-- key cannot be used.
+--
+checkJWK :: (MonadError e m, AsError e) => JWK -> m ()
+checkJWK jwk = case view jwkMaterial jwk of
+  RSAKeyMaterial (view rsaN -> Types.Base64Integer n)
+    | n >= 2 ^ (2040 :: Integer) -> pure ()
+    | otherwise -> throwing _KeySizeTooSmall ()
+  OctKeyMaterial (view octK -> Types.Base64Octets k)
+    | B.length k >= 256 `div` 8 -> pure ()
+    | otherwise -> throwing _KeySizeTooSmall ()
+  _ -> pure ()
+
+
 -- | 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
-  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
-  OKPKeyMaterial (Ed25519Key _ _) -> pure JWA.JWS.EdDSA
-  OKPKeyMaterial _ -> throwing _KeyMismatch "Cannot sign with OKP ECDH key"
+    | 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
+    (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
@@ -361,7 +449,9 @@
     OctKeyMaterial (OctKeyParameters k') ->
       "k" .= k' <> "kty" .= ("oct" :: T.Text)
     OKPKeyMaterial (Ed25519Key pk _) -> okpSeries "Ed25519" pk
+    OKPKeyMaterial (Ed448Key pk _) -> okpSeries "Ed448" pk
     OKPKeyMaterial (X25519Key pk _) -> okpSeries "X25519" pk
+    OKPKeyMaterial (X448Key pk _) -> okpSeries "X448" pk
   where
     b64 = Types.Base64Octets . BA.convert
     okpSeries crv pk =
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
@@ -31,21 +28,44 @@
 -- | A KeyDB is just a filesystem directory
 newtype KeyDB = KeyDB FilePath
 
-instance (MonadIO m, HasKid h)
-    => VerificationKeyStore m (h p) ClaimsSet KeyDB where
-  getVerificationKeys h claims (KeyDB dir) = liftIO $
+instance (MonadIO m, t'Crypto.JOSE.Header.HasKid' h)
+    => VerificationKeyStore m (h p) t'Crypto.JWT.ClaimsSet' KeyDB where
+  'getVerificationKeys' h claims (KeyDB dir) = liftIO $
     fmap catMaybes . traverse findKey $ catMaybes
-      [ preview (kid . _Just . param) h
-      , preview (claimIss . _Just . string) claims]
+      [ preview ('Crypto.JOSE.Header.kid' . _Just . 'Crypto.JOSE.Header.param') h
+      , preview ('Crypto.JWT.claimIss' . _Just . 'Crypto.JWT.string') claims]
     where
     findKey :: T.Text -> IO (Maybe JWK)
     findKey s =
       let path = dir <> "/" <> T.unpack s <> ".jwk"
       in handle
-        (\(_ :: IOException) -> pure Nothing)
-        (decode <$> L.readFile path)
+        (\\(_ :: IOException) -> pure Nothing)
+        (decode \<$> L.readFile path)
 @
 
+The next example shows how to retrieve public keys from a JWK Set
+(@\/.well-known\/jwks.json@) resource.  For production use, it would
+be a good idea to cache the HTTP response.  Thanks to Steve Mao for
+this example.
+
+@
+-- | URI of JWK Set
+newtype JWKsURI = JWKsURI String
+
+instance (MonadIO m, t'Crypto.JOSE.Header.HasKid' h)
+    => 'VerificationKeyStore' m (h p) t'Crypto.JWT.ClaimsSet' JWKsURI where
+  'getVerificationKeys' h claims (JWKsURI url) = liftIO $
+    maybe [] (:[]) . join
+      \<$> traverse findKey (preview ('Crypto.JOSE.Header.kid' . _Just . 'Crypto.JOSE.Header.param') h)
+    where
+    findKey :: T.Text -> IO (Maybe JWK)
+    findKey kid' =
+      handle (\\(_ :: SomeException) -> pure Nothing) $ do
+        request \<- setRequestCheckStatus \<$> parseRequest url
+        response \<- getResponseBody \<$> httpJSON request
+        keys \<- getVerificationKeys h claims response
+        pure $ find (\\j -> view 'Crypto.JOSE.JWK.jwkKid' j == Just kid') keys
+@
 -}
 module Crypto.JOSE.JWK.Store
   (
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
@@ -20,23 +20,23 @@
 <https://tools.ietf.org/html/rfc7515 RFC 7515>.
 
 @
+import Crypto.JOSE
+
 doJwsSign :: 'JWK' -> L.ByteString -> IO (Either 'Error' ('GeneralJWS' 'JWSHeader'))
-doJwsSign jwk payload = runExceptT $ do
+doJwsSign jwk payload = 'runJOSE' $ do
   alg \<- 'bestJWSAlg' jwk
   'signJWS' payload [('newJWSHeader' ('Protected', alg), jwk)]
 
 doJwsVerify :: 'JWK' -> 'GeneralJWS' 'JWSHeader' -> IO (Either 'Error' ())
-doJwsVerify jwk jws = runExceptT $ 'verifyJWS'' jwk jws
+doJwsVerify jwk jws = 'runJOSE' $
+  'verifyJWS'' jwk jws
 @
 
 -}
 
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE UndecidableInstances #-}
 {-# LANGUAGE MonoLocalBinds #-}
-{-# LANGUAGE ScopedTypeVariables #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
 module Crypto.JOSE.JWS
@@ -52,6 +52,7 @@
 
   -- * JWS creation
   , newJWSHeader
+  , newJWSHeaderProtected
   , makeJWSHeader
   , signJWS
 
@@ -68,6 +69,9 @@
   , HasAlgorithms(..)
   , HasValidationPolicy(..)
 
+  -- * Access payload without verification
+  , unsafeGetPayload
+
   -- * Signature data
   , signatures
   , Signature
@@ -86,20 +90,19 @@
   ) where
 
 import Control.Applicative ((<|>))
+import Control.Monad (unless)
 import Data.Foldable (toList)
 import Data.Maybe (catMaybes, fromMaybe)
-import Data.Monoid ((<>))
 import Data.List.NonEmpty (NonEmpty)
-import Data.Traversable (traverse)
 import Data.Word (Word8)
 
 import Control.Lens hiding ((.=))
 import Control.Lens.Cons.Extras (recons)
 import Control.Monad.Error.Lens (throwing, throwing_)
-import Control.Monad.Except (MonadError, unless)
+import Control.Monad.Except (MonadError)
 import Data.Aeson
+import qualified Data.Aeson.KeyMap as M
 import qualified Data.ByteString as B
-import qualified Data.HashMap.Strict as M
 import qualified Data.Set as S
 import qualified Data.Text as T
 import qualified Data.Text.Encoding as T
@@ -111,6 +114,7 @@
 import Crypto.JOSE.JWK.Store
 import Crypto.JOSE.Header
 import qualified Crypto.JOSE.Types as Types
+import Crypto.JOSE.Types.URI
 import qualified Crypto.JOSE.Types.Internal as Types
 
 {- $extending
@@ -155,6 +159,7 @@
 - 'headerRequired'
 - 'headerRequiredProtected'
 - 'headerOptional'
+- 'headerOptional''
 - 'headerOptionalProtected'
 
 -}
@@ -234,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.
@@ -250,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
@@ -297,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
@@ -314,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
@@ -330,10 +340,10 @@
 instance HasParams JWSHeader where
   parseParamsFor proxy hp hu = JWSHeader
     <$> headerRequired "alg" hp hu
-    <*> headerOptional "jku" hp hu
+    <*> headerOptional' uriFromJSON "jku" hp hu
     <*> headerOptional "jwk" hp hu
     <*> headerOptional "kid" hp hu
-    <*> headerOptional "x5u" hp hu
+    <*> headerOptional' uriFromJSON "x5u" hp hu
     <*> (fmap . fmap . fmap . fmap)
           (\(Types.Base64X509 cert) -> cert) (headerOptional "x5c" hp hu)
     <*> headerOptional "x5t" hp hu
@@ -346,10 +356,10 @@
   params h =
     catMaybes
       [ Just (view (alg . isProtected) h, "alg" .= view (alg . param) h)
-      , fmap (\p -> (view isProtected p, "jku" .= view param p)) (view jku h)
+      , fmap (\p -> (view isProtected p, "jku" .= views param uriToJSON p)) (view jku h)
       , fmap (\p -> (view isProtected p, "jwk" .= view param p)) (view jwk h)
       , fmap (\p -> (view isProtected p, "kid" .= view param p)) (view kid h)
-      , fmap (\p -> (view isProtected p, "x5u" .= view param p)) (view x5u h)
+      , fmap (\p -> (view isProtected p, "x5u" .= views param uriToJSON p)) (view x5u h)
       , fmap (\p -> (view isProtected p, "x5c" .= fmap Types.Base64X509 (view param p))) (view x5c h)
       , fmap (\p -> (view isProtected p, "x5t" .= view param p)) (view x5t h)
       , fmap (\p -> (view isProtected p, "x5t#S256" .= view param p)) (view x5tS256 h)
@@ -359,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
@@ -376,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'
@@ -408,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
-  toJSON (JWS p [s]) = object $ "payload" .= p : Types.objectPairs (toJSON s)
+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
-  toJSON (JWS p (Identity s)) = object $ "payload" .= p : Types.objectPairs (toJSON s)
+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
@@ -445,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
@@ -455,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
@@ -484,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
@@ -496,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 =
@@ -568,6 +596,7 @@
     , ES256, ES384, ES512
     , PS256, PS384, PS512
     , EdDSA
+    , ES256K
     ] )
   AllValidated
 
@@ -580,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
@@ -604,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
@@ -613,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
@@ -640,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 #-}
 
 {-|
@@ -44,7 +43,7 @@
 conize = mkName . capitalize . sanitize
 
 guardPred :: String -> ExpQ
-guardPred s = [e| $(varE $ mkName "s") == s |]
+guardPred s = [e| $(varE $ mkName "s") == $(lift s) |]
 
 guardExp :: String -> ExpQ
 guardExp s = [e| pure $(conE $ conize s) |]
@@ -58,7 +57,7 @@
 -- | Expression for an end guard.  Arg describes type it was expecting.
 --
 endGuardExp :: String -> ExpQ
-endGuardExp s = [e| fail ("unrecognised value; expected: " ++ s) |]
+endGuardExp s = [e| fail ("unrecognised value; expected: " ++ $(lift s)) |]
 
 -- | Build a catch-all guard that fails.  String describes what is expected.
 --
@@ -76,7 +75,7 @@
 
 
 toJSONClause :: String -> ClauseQ
-toJSONClause s = clause [conP (conize s) []] (normalB [| s |]) []
+toJSONClause s = clause [conP (conize s) []] (normalB [| $(lift s) |]) []
 
 toJSONFun :: [String] -> DecQ
 toJSONFun vs = funD 'toJSON (map toJSONClause vs)
@@ -100,13 +99,7 @@
   let
     derive = map mkName ["Eq", "Ord", "Show"]
   in
-#if ! MIN_VERSION_template_haskell(2,11,0)
-    dataD (cxt []) (mkName s) [] (map conQ vs) derive
-#elif ! 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.hs b/src/Crypto/JOSE/Types.hs
--- a/src/Crypto/JOSE/Types.hs
+++ b/src/Crypto/JOSE/Types.hs
@@ -26,7 +26,6 @@
   , _Base64Integer
   , SizedBase64Integer(..)
   , makeSizedBase64Integer
-  , genSizedBase64IntegerOf
   , checkSize
   , Base64Octets(..)
   , Base64SHA1(..)
@@ -37,20 +36,14 @@
   , base64url
   ) where
 
-import Data.Word (Word8)
-
 import Control.Lens
 import Data.Aeson
 import Data.Aeson.Types (Parser)
 import qualified Data.ByteString as B
 import Data.X509
 import Network.URI (URI)
-import Test.QuickCheck
-import Test.QuickCheck.Instances ()
 
-import Crypto.Number.Basic (log2)
 import Crypto.JOSE.Types.Internal
-import Crypto.JOSE.Types.Orphans ()
 
 
 -- | A base64url encoded octet sequence interpreted as an integer.
@@ -88,22 +81,6 @@
   toJSON (Base64Integer x) = encodeB64Url $ integerToBS x
 
 
-arbitraryBigInteger :: Gen Integer
-arbitraryBigInteger = do
-  size <- arbitrarySizedNatural  -- number of octets
-  go (size + 1) 0
-  where
-    go :: Integer -> Integer -> Gen Integer
-    go 0 n = pure n
-    go k n =
-      (arbitraryBoundedIntegral :: Gen Word8)
-      >>= go (k - 1) . (n * 256 +) . fromIntegral
-
-
-instance Arbitrary Base64Integer where
-  arbitrary = Base64Integer <$> arbitraryBigInteger
-
-
 -- | A base64url encoded octet sequence interpreted as an integer
 -- and where the number of octets carries explicit bit-length
 -- information.
@@ -114,21 +91,6 @@
 instance Eq SizedBase64Integer where
   SizedBase64Integer _ n == SizedBase64Integer _ m = n == m
 
-instance Arbitrary SizedBase64Integer where
-  arbitrary = do
-    x <- arbitraryBigInteger
-    l <- Test.QuickCheck.elements [0,1,2]  -- number of leading zero-bytes
-    pure $ SizedBase64Integer ((log2 x `div` 8) + 1 + l) x
-
-genByteStringOf :: Int -> Gen B.ByteString
-genByteStringOf n = B.pack <$> vectorOf n arbitrary
-
--- | Generate a 'SizedBase64Integer' of the given number of bytes
---
-genSizedBase64IntegerOf :: Int -> Gen SizedBase64Integer
-genSizedBase64IntegerOf n =
-  SizedBase64Integer n . bsToInteger <$> genByteStringOf n
-
 -- | Create a 'SizedBase64Integer'' from an 'Integer'.
 makeSizedBase64Integer :: Integer -> SizedBase64Integer
 makeSizedBase64Integer x = SizedBase64Integer (intBytes x) x
@@ -160,10 +122,7 @@
 instance ToJSON Base64Octets where
   toJSON (Base64Octets bytes) = encodeB64Url bytes
 
-instance Arbitrary Base64Octets where
-  arbitrary = Base64Octets <$> arbitrary
 
-
 -- | A base64url encoded SHA-1 digest.  Used for X.509 certificate
 -- thumbprints.
 --
@@ -179,10 +138,7 @@
 instance ToJSON Base64SHA1 where
   toJSON (Base64SHA1 bytes) = encodeB64Url bytes
 
-instance Arbitrary Base64SHA1 where
-  arbitrary = Base64SHA1 <$> genByteStringOf 20
 
-
 -- | A base64url encoded SHA-256 digest.  Used for X.509 certificate
 -- thumbprints.
 --
@@ -197,9 +153,6 @@
 
 instance ToJSON Base64SHA256 where
   toJSON (Base64SHA256 bytes) = encodeB64Url bytes
-
-instance Arbitrary Base64SHA256 where
-  arbitrary = Base64SHA256 <$> genByteStringOf 32
 
 
 -- | A base64 encoded X.509 certificate.
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 #-}
 
 {-|
 
@@ -23,7 +21,8 @@
 -}
 module Crypto.JOSE.Types.Internal
   (
-    objectPairs
+    insertToObject
+  , insertManyToObject
   , encodeB64
   , parseB64
   , encodeB64Url
@@ -36,7 +35,6 @@
   ) where
 
 import Data.Bifunctor (first)
-import Data.Monoid ((<>))
 import Data.Tuple (swap)
 import Data.Word (Word8)
 
@@ -44,19 +42,28 @@
 import Control.Lens.Cons.Extras
 import Crypto.Number.Basic (log2)
 import Data.Aeson.Types
+import qualified Data.Aeson.KeyMap as M
 import qualified Data.ByteString as B
 import qualified Data.ByteString.Base64 as B64
 import qualified Data.ByteString.Base64.URL as B64U
-import qualified Data.HashMap.Strict as M
 import qualified Data.Text as T
 import qualified Data.Text.Encoding as E
 
--- | Convert a JSON object into a list of pairs or the empty list
--- if the JSON value is not an object.
+-- | Insert the given key and value into the given @Value@, which
+-- is expected to be an @Object@.  If the value is not an @Object@,
+-- this is a no-op.
 --
-objectPairs :: Value -> [Pair]
-objectPairs (Object o) = M.toList o
-objectPairs _ = []
+insertToObject :: ToJSON v => Key -> v -> Value -> Value
+insertToObject k v (Object o) = Object $ M.insert k (toJSON v) o
+insertToObject _ _ v          = v
+
+-- | Insert several key/value pairs to the given @Value@, which
+-- is expected to be an @Object@.  If the value is not an @Object@,
+-- this is a no-op.
+--
+insertManyToObject :: [Pair] -> Value -> Value
+insertManyToObject kvs (Object o) = Object $ foldr (uncurry M.insert) o kvs
+insertManyToObject _ v            = v
 
 -- | Produce a parser of base64 encoded text from a bytestring parser.
 --
diff --git a/src/Crypto/JOSE/Types/Orphans.hs b/src/Crypto/JOSE/Types/Orphans.hs
deleted file mode 100644
--- a/src/Crypto/JOSE/Types/Orphans.hs
+++ /dev/null
@@ -1,29 +0,0 @@
--- Copyright (C) 2014, 2015, 2016  Fraser Tweedale
---
--- Licensed under the Apache License, Version 2.0 (the "License");
--- you may not use this file except in compliance with the License.
--- You may obtain a copy of the License at
---
---      http://www.apache.org/licenses/LICENSE-2.0
---
--- Unless required by applicable law or agreed to in writing, software
--- distributed under the License is distributed on an "AS IS" BASIS,
--- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--- See the License for the specific language governing permissions and
--- limitations under the License.
-
-{-# OPTIONS_GHC -fno-warn-orphans #-}
-
-module Crypto.JOSE.Types.Orphans where
-
-import Data.Aeson
-import qualified Data.Text as T
-import Network.URI (URI, parseURI)
-
-
-instance FromJSON URI where
-  parseJSON = withText "URI" $
-    maybe (fail "not a URI") return . parseURI . T.unpack
-
-instance ToJSON URI where
-  toJSON = String . T.pack . show
diff --git a/src/Crypto/JOSE/Types/URI.hs b/src/Crypto/JOSE/Types/URI.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/JOSE/Types/URI.hs
@@ -0,0 +1,29 @@
+-- Copyright (C) 2014-2022  Fraser Tweedale
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--      http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+module Crypto.JOSE.Types.URI
+  ( uriFromJSON
+  , uriToJSON
+  ) where
+
+import Data.Aeson
+import Data.Aeson.Types (Parser)
+import qualified Data.Text as T
+import Network.URI (URI, parseURI)
+
+uriFromJSON :: Value -> Parser URI
+uriFromJSON = withText "URI" $ maybe (fail "not a URI") pure . parseURI . T.unpack
+
+uriToJSON :: URI -> Value
+uriToJSON = String . T.pack . show
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 #-}
@@ -30,51 +27,27 @@
 JWTs use the JWS /compact serialisation/.
 See "Crypto.JOSE.Compact" for details.
 
-@
-mkClaims :: IO 'ClaimsSet'
-mkClaims = do
-  t <- 'currentTime'
-  pure $ 'emptyClaimsSet'
-    & 'claimIss' ?~ "alice"
-    & 'claimAud' ?~ 'Audience' ["bob"]
-    & 'claimIat' ?~ 'NumericDate' t
-
-doJwtSign :: 'JWK' -> 'ClaimsSet' -> IO (Either 'JWTError' 'SignedJWT')
-doJwtSign jwk claims = runExceptT $ do
-  alg \<- 'bestJWSAlg' jwk
-  'signClaims' jwk ('newJWSHeader' ((), alg)) claims
-
-doJwtVerify :: 'JWK' -> 'SignedJWT' -> IO (Either 'JWTError' 'ClaimsSet')
-doJwtVerify jwk jwt = runExceptT $ do
-  let config = 'defaultJWTValidationSettings' (== "bob")
-  'verifyClaims' config jwk jwt
-@
-
-Some JWT libraries have a function that takes two strings: the
-"secret" (a symmetric key) and the raw JWT.  The following function
-achieves the same:
-
-@
-verify :: L.ByteString -> L.ByteString -> IO (Either 'JWTError' 'ClaimsSet')
-verify k s = runExceptT $ do
-  let
-    k' = 'fromOctets' k      -- turn raw secret into symmetric JWK
-    audCheck = const True  -- should be a proper audience check
-  s' <- 'decodeCompact' s    -- decode JWT
-  'verifyClaims' ('defaultJWTValidationSettings' audCheck) k' s'
-@
-
 -}
 module Crypto.JWT
   (
-  -- * Creating a JWT
-    signClaims
-  , SignedJWT
+  -- * Overview / HOWTO
+  -- ** Basic usage
+  -- $basic
 
-  -- * Validating a JWT and extracting claims
+  -- ** Supporting additional claims via subtypes #subtypes#
+  -- $subtypes
+
+  -- * API
+  -- ** Creating a JWT
+    SignedJWT
+  , SignedJWTWithHeader
+  , signJWT
+  , signClaims
+
+  -- ** Validating a JWT and extracting claims
   , defaultJWTValidationSettings
   , verifyClaims
-  , verifyClaimsAt
+  , verifyJWT
   , HasAllowedSkew(..)
   , HasAudiencePredicate(..)
   , HasIssuerPredicate(..)
@@ -82,25 +55,29 @@
   , JWTValidationSettings
   , HasJWTValidationSettings(..)
 
-  -- * Claims Set
+  -- *** Specifying the verification time
+  , WrappedUTCTime(..)
+  , verifyClaimsAt
+  , verifyJWTAt
+
+  -- ** Extracting claims without verification
+  , unsafeGetJWTPayload
+  , unsafeGetJWTClaimsSet
+
+  -- ** Claims Set
   , ClaimsSet
-  , claimAud
-  , claimExp
-  , claimIat
-  , claimIss
-  , claimJti
-  , claimNbf
-  , claimSub
-  , unregisteredClaims
-  , addClaim
   , emptyClaimsSet
+  , HasClaimsSet(..)
   , validateClaimsSet
+  -- *** Unregistered claims (__deprecated__)
+  , addClaim
+  , unregisteredClaims
 
-  -- * JWT errors
+  -- ** JWT errors
   , JWTError(..)
   , AsJWTError(..)
 
-  -- * Miscellaneous
+  -- ** Miscellaneous types
   , Audience(..)
   , StringOrURI
   , stringOrUri
@@ -108,6 +85,7 @@
   , uri
   , NumericDate(..)
 
+  -- ** Re-exports
   , module Crypto.JOSE
 
   ) where
@@ -115,9 +93,6 @@
 import Control.Applicative
 import Control.Monad
 import Control.Monad.Time (MonadTime(..))
-#if ! MIN_VERSION_monad_time(0,2,0)
-import Control.Monad.Time.Instances ()
-#endif
 import Data.Foldable (traverse_)
 import Data.Functor.Identity
 import Data.Maybe
@@ -132,7 +107,10 @@
 import Control.Monad.Except (MonadError)
 import Control.Monad.Reader (ReaderT, asks, runReaderT)
 import Data.Aeson
-import qualified Data.HashMap.Strict as M
+import qualified Data.Aeson.Key as Key
+import qualified Data.Aeson.KeyMap as KeyMap
+import qualified Data.Map as M
+import qualified Data.Set as S
 import qualified Data.Text as T
 import Data.Time (NominalDiffTime, UTCTime, addUTCTime)
 import Data.Time.Clock.POSIX (posixSecondsToUTCTime, utcTimeToPOSIXSeconds)
@@ -141,7 +119,81 @@
 import Crypto.JOSE
 import Crypto.JOSE.Types
 
+{- $basic
 
+@
+import Crypto.JWT
+
+mkClaims :: IO 'ClaimsSet'
+mkClaims = do
+  t <- 'currentTime'
+  pure $ 'emptyClaimsSet'
+    & 'claimIss' ?~ "alice"
+    & 'claimAud' ?~ 'Audience' ["bob"]
+    & 'claimIat' ?~ 'NumericDate' t
+
+doJwtSign :: 'JWK' -> 'ClaimsSet' -> IO (Either 'JWTError' 'SignedJWT')
+doJwtSign jwk claims = 'runJOSE' $ do
+  alg \<- 'bestJWSAlg' jwk
+  'signClaims' jwk ('newJWSHeaderProtected' alg) claims
+
+doJwtVerify :: 'JWK' -> 'SignedJWT' -> IO (Either 'JWTError' 'ClaimsSet')
+doJwtVerify jwk jwt = 'runJOSE' $ do
+  let config = 'defaultJWTValidationSettings' (== "bob")
+  'verifyClaims' config jwk jwt
+@
+
+Some JWT libraries have a function that takes two strings: the
+"secret" (a symmetric key) and the raw JWT.  The following function
+achieves the same:
+
+@
+verify :: L.ByteString -> L.ByteString -> IO (Either 'JWTError' 'ClaimsSet')
+verify k s = 'runJOSE' $ do
+  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 :: 'SignedJWT')
+@
+
+-}
+
+{- $subtypes
+
+For applications that use __additional claims__, define a data type that wraps
+'ClaimsSet' and includes fields for the additional claims.  You will also need
+to define 'FromJSON' if verifying JWTs, and 'ToJSON' if producing JWTs.  The
+following example is taken from
+<https://datatracker.ietf.org/doc/html/rfc7519#section-3.1 RFC 7519 §3.1>.
+
+@
+import qualified Data.Aeson.KeyMap as M
+
+data Super = Super { jwtClaims :: 'ClaimsSet', isRoot :: Bool }
+
+instance 'HasClaimsSet' Super where
+  'claimsSet' f s = fmap (\\a' -> s { jwtClaims = a' }) (f (jwtClaims s))
+
+instance FromJSON Super where
+  parseJSON = withObject \"Super\" $ \\o -> Super
+    \<$\> parseJSON (Object o)
+    \<*\> o .: "http://example.com/is_root"
+
+instance ToJSON Super where
+  toJSON s =
+    ins "http://example.com/is_root" (isRoot s) (toJSON (jwtClaims s))
+    where
+      ins k v (Object o) = Object $ M.insert k (toJSON v) o
+      ins _ _ a = a
+@
+
+__Use 'signJWT' and 'verifyJWT' when using custom payload types__ (instead of
+'signClaims' and 'verifyClaims' which are specialised to 'ClaimsSet').
+
+-}
+
+
 data JWTError
   = JWSError Error
   -- ^ A JOSE error occurred while processing the JWT
@@ -169,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
@@ -240,9 +292,13 @@
 
 
 -- | The JWT Claims Set represents a JSON object whose members are
--- the registered claims defined by RFC 7519.  Unrecognised
--- claims are gathered into the 'unregisteredClaims' map.
+-- the registered claims defined by RFC 7519.  To construct a
+-- @ClaimsSet@ use 'emptyClaimsSet' then use the lenses defined in
+-- 'HasClaimsSet' to set relevant claims.
 --
+-- For applications that use additional claims beyond those defined
+-- by RFC 7519, define a [subtype](#g:subtypes) and instance 'HasClaimsSet'.
+--
 data ClaimsSet = ClaimsSet
   { _claimIss :: Maybe StringOrURI
   , _claimSub :: Maybe StringOrURI
@@ -251,92 +307,144 @@
   , _claimNbf :: Maybe NumericDate
   , _claimIat :: Maybe NumericDate
   , _claimJti :: Maybe T.Text
-  , _unregisteredClaims :: M.HashMap T.Text Value
+  , _unregisteredClaims :: M.Map T.Text Value
   }
   deriving (Eq, Show)
 
--- | The issuer claim identifies the principal that issued the
--- JWT.  The processing of this claim is generally application
--- specific.
-claimIss :: Lens' ClaimsSet (Maybe StringOrURI)
-claimIss f h@ClaimsSet{ _claimIss = a} =
-  fmap (\a' -> h { _claimIss = a' }) (f a)
+class HasClaimsSet a where
+  claimsSet :: Lens' a ClaimsSet
 
--- | The subject claim identifies the principal that is the
--- subject of the JWT.  The Claims in a JWT are normally
--- statements about the subject.  The subject value MAY be scoped
--- to be locally unique in the context of the issuer or MAY be
--- globally unique.  The processing of this claim is generally
--- application specific.
-claimSub :: Lens' ClaimsSet (Maybe StringOrURI)
-claimSub f h@ClaimsSet{ _claimSub = a} =
-  fmap (\a' -> h { _claimSub = a' }) (f a)
+  -- | The issuer claim identifies the principal that issued the
+  -- JWT.  The processing of this claim is generally application
+  -- specific.
+  claimIss :: Lens' a (Maybe StringOrURI)
+  {-# INLINE claimIss #-}
 
--- | The audience claim identifies the recipients that the JWT is
--- intended for.  Each principal intended to process the JWT MUST
--- identify itself with a value in the audience claim.  If the
--- principal processing the claim does not identify itself with a
--- value in the /aud/ claim when this claim is present, then the
--- JWT MUST be rejected.
-claimAud :: Lens' ClaimsSet (Maybe Audience)
-claimAud f h@ClaimsSet{ _claimAud = a} =
-  fmap (\a' -> h { _claimAud = a' }) (f a)
+  -- | The subject claim identifies the principal that is the
+  -- subject of the JWT.  The Claims in a JWT are normally
+  -- statements about the subject.  The subject value MAY be scoped
+  -- to be locally unique in the context of the issuer or MAY be
+  -- globally unique.  The processing of this claim is generally
+  -- application specific.
+  claimSub :: Lens' a (Maybe StringOrURI)
+  {-# INLINE claimSub #-}
 
--- | The expiration time claim identifies the expiration time on
--- or after which the JWT MUST NOT be accepted for processing.
--- The processing of /exp/ claim requires that the current
--- date\/time MUST be before expiration date\/time listed in the
--- /exp/ claim.  Implementers MAY provide for some small leeway,
--- usually no more than a few minutes, to account for clock skew.
-claimExp :: Lens' ClaimsSet (Maybe NumericDate)
-claimExp f h@ClaimsSet{ _claimExp = a} =
-  fmap (\a' -> h { _claimExp = a' }) (f a)
+  -- | The audience claim identifies the recipients that the JWT is
+  -- intended for.  Each principal intended to process the JWT MUST
+  -- identify itself with a value in the audience claim.  If the
+  -- principal processing the claim does not identify itself with a
+  -- value in the /aud/ claim when this claim is present, then the
+  -- JWT MUST be rejected.
+  claimAud :: Lens' a (Maybe Audience)
+  {-# INLINE claimAud #-}
 
--- | The not before claim identifies the time before which the JWT
--- MUST NOT be accepted for processing.  The processing of the
--- /nbf/ claim requires that the current date\/time MUST be after
--- or equal to the not-before date\/time listed in the /nbf/
--- claim.  Implementers MAY provide for some small leeway, usually
--- no more than a few minutes, to account for clock skew.
-claimNbf :: Lens' ClaimsSet (Maybe NumericDate)
-claimNbf f h@ClaimsSet{ _claimNbf = a} =
-  fmap (\a' -> h { _claimNbf = a' }) (f a)
+  -- | The expiration time claim identifies the expiration time on
+  -- or after which the JWT MUST NOT be accepted for processing.
+  -- The processing of /exp/ claim requires that the current
+  -- date\/time MUST be before expiration date\/time listed in the
+  -- /exp/ claim.  Implementers MAY provide for some small leeway,
+  -- usually no more than a few minutes, to account for clock skew.
+  claimExp :: Lens' a (Maybe NumericDate)
+  {-# INLINE claimExp #-}
 
--- | The issued at claim identifies the time at which the JWT was
--- issued.  This claim can be used to determine the age of the
--- JWT.
-claimIat :: Lens' ClaimsSet (Maybe NumericDate)
-claimIat f h@ClaimsSet{ _claimIat = a} =
-  fmap (\a' -> h { _claimIat = a' }) (f a)
+  -- | The not before claim identifies the time before which the JWT
+  -- MUST NOT be accepted for processing.  The processing of the
+  -- /nbf/ claim requires that the current date\/time MUST be after
+  -- or equal to the not-before date\/time listed in the /nbf/
+  -- claim.  Implementers MAY provide for some small leeway, usually
+  -- no more than a few minutes, to account for clock skew.
+  claimNbf :: Lens' a (Maybe NumericDate)
+  {-# INLINE claimNbf #-}
 
--- | The JWT ID claim provides a unique identifier for the JWT.
--- The identifier value MUST be assigned in a manner that ensures
--- that there is a negligible probability that the same value will
--- be accidentally assigned to a different data object.  The /jti/
--- claim can be used to prevent the JWT from being replayed.  The
--- /jti/ value is a case-sensitive string.
-claimJti :: Lens' ClaimsSet (Maybe T.Text)
-claimJti f h@ClaimsSet{ _claimJti = a} =
-  fmap (\a' -> h { _claimJti = a' }) (f a)
+  -- | The issued at claim identifies the time at which the JWT was
+  -- issued.  This claim can be used to determine the age of the
+  -- JWT.
+  claimIat :: Lens' a (Maybe NumericDate)
+  {-# INLINE claimIat #-}
 
+  -- | The JWT ID claim provides a unique identifier for the JWT.
+  -- The identifier value MUST be assigned in a manner that ensures
+  -- that there is a negligible probability that the same value will
+  -- be accidentally assigned to a different data object.  The /jti/
+  -- claim can be used to prevent the JWT from being replayed.  The
+  -- /jti/ value is a case-sensitive string.
+  claimJti :: Lens' a (Maybe T.Text)
+  {-# INLINE claimJti #-}
+
+  claimAud = claimsSet . claimAud
+  claimExp = claimsSet . claimExp
+  claimIat = claimsSet . claimIat
+  claimIss = claimsSet . claimIss
+  claimJti = claimsSet . claimJti
+  claimNbf = claimsSet . claimNbf
+  claimSub = claimsSet . claimSub
+
+instance HasClaimsSet ClaimsSet where
+  claimsSet = id
+
+  claimIss f h@ClaimsSet{ _claimIss = a} = fmap (\a' -> h { _claimIss = a' }) (f a)
+  {-# INLINE claimIss #-}
+
+  claimSub f h@ClaimsSet{ _claimSub = a} = fmap (\a' -> h { _claimSub = a' }) (f a)
+  {-# INLINE claimSub #-}
+
+  claimAud f h@ClaimsSet{ _claimAud = a} = fmap (\a' -> h { _claimAud = a' }) (f a)
+  {-# INLINE claimAud #-}
+
+  claimExp f h@ClaimsSet{ _claimExp = a} = fmap (\a' -> h { _claimExp = a' }) (f a)
+  {-# INLINE claimExp #-}
+
+  claimNbf f h@ClaimsSet{ _claimNbf = a} = fmap (\a' -> h { _claimNbf = a' }) (f a)
+  {-# INLINE claimNbf #-}
+
+  claimIat f h@ClaimsSet{ _claimIat = a} = fmap (\a' -> h { _claimIat = a' }) (f a)
+  {-# INLINE claimIat #-}
+
+  claimJti f h@ClaimsSet{ _claimJti = a} = fmap (\a' -> h { _claimJti = a' }) (f a)
+  {-# INLINE claimJti #-}
+
 -- | Claim Names can be defined at will by those using JWTs.
-unregisteredClaims :: Lens' ClaimsSet (M.HashMap T.Text Value)
+-- Use this lens to access a map non-RFC 7519 claims in the
+-- Claims Set object.
+unregisteredClaims :: Lens' ClaimsSet (M.Map T.Text Value)
 unregisteredClaims f h@ClaimsSet{ _unregisteredClaims = a} =
   fmap (\a' -> h { _unregisteredClaims = a' }) (f a)
-
+{-# INLINE unregisteredClaims #-}
+{-# DEPRECATED unregisteredClaims "use a [subtype](#g:subtypes) to define additional claims" #-}
 
 -- | Return an empty claims set.
 --
 emptyClaimsSet :: ClaimsSet
 emptyClaimsSet = ClaimsSet n n n n n n n M.empty where n = Nothing
 
+-- | Add a __non-RFC 7519__ claim.  Use the lenses from the
+-- 'HasClaimsSet' class for setting registered claims.
+--
 addClaim :: T.Text -> Value -> ClaimsSet -> ClaimsSet
 addClaim k v = over unregisteredClaims (M.insert k v)
+{-# DEPRECATED addClaim "use a [subtype](#g:subtypes) to define additional claims" #-}
 
-filterUnregistered :: M.HashMap T.Text Value -> M.HashMap T.Text Value
-filterUnregistered = M.filterWithKey (\k _ -> k `notElem` registered) where
-  registered = ["iss", "sub", "aud", "exp", "nbf", "iat", "jti"]
+registeredClaims :: S.Set T.Text
+registeredClaims = S.fromDistinctAscList
+  [ "aud"
+  , "exp"
+  , "iat"
+  , "iss"
+  , "jti"
+  , "nbf"
+  , "sub"
+  ]
 
+filterUnregistered :: M.Map T.Text Value -> M.Map T.Text Value
+filterUnregistered m =
+  m `M.withoutKeys` registeredClaims
+
+toKeyMap :: M.Map T.Text Value -> KeyMap.KeyMap Value
+toKeyMap = KeyMap.fromMap . M.mapKeysMonotonic Key.fromText
+
+fromKeyMap :: KeyMap.KeyMap Value -> M.Map T.Text Value
+fromKeyMap = M.mapKeysMonotonic Key.toText . KeyMap.toMap
+
 instance FromJSON ClaimsSet where
   parseJSON = withObject "JWT Claims Set" (\o -> ClaimsSet
     <$> o .:? "iss"
@@ -346,18 +454,22 @@
     <*> o .:? "nbf"
     <*> o .:? "iat"
     <*> o .:? "jti"
-    <*> pure (filterUnregistered o))
+    <*> pure (filterUnregistered . fromKeyMap $ o)
+    )
 
 instance ToJSON ClaimsSet where
-  toJSON (ClaimsSet iss sub aud exp' nbf iat jti o) = object $ catMaybes [
-    fmap ("iss" .=) iss
-    , fmap ("sub" .=) sub
-    , fmap ("aud" .=) aud
-    , fmap ("exp" .=) exp'
-    , fmap ("nbf" .=) nbf
-    , fmap ("iat" .=) iat
-    , fmap ("jti" .=) jti
-    ] ++ M.toList (filterUnregistered o)
+  toJSON (ClaimsSet iss sub aud exp' nbf iat jti o) = Object $
+    ( KeyMap.fromMap . M.fromDistinctAscList $ catMaybes
+      [ fmap ("aud" .=) aud
+      , fmap ("exp" .=) exp'
+      , fmap ("iat" .=) iat
+      , fmap ("iss" .=) iss
+      , fmap ("jti" .=) jti
+      , fmap ("nbf" .=) nbf
+      , fmap ("sub" .=) sub
+      ]
+    )
+    <> toKeyMap (filterUnregistered o)
 
 
 data JWTValidationSettings = JWTValidationSettings
@@ -422,9 +534,9 @@
 
 -- | Validate the claims made by a ClaimsSet.
 --
--- These checks are performed by 'verifyClaims', which also
--- validates any signatures, so you shouldn't need to use this
--- function directly.
+-- __You should never need to use this function directly.__
+-- These checks are always performed by 'verifyClaims' and 'verifyJWT'.
+-- The function is exported mainly for testing purposes.
 --
 validateClaimsSet
   ::
@@ -503,54 +615,136 @@
     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 }
 
+-- | @'monotonicTime' = pure 0@.  /jose/ doesn't use this so we fake it
 instance Monad m => MonadTime (ReaderT WrappedUTCTime m) where
   currentTime = asks getUTCTime
+  monotonicTime = pure 0
 
+-- | 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.
+-- 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.
 --
 -- See also 'verifyClaimsAt' which allows you to explicitly specify
--- the time.
+-- the time of validation (against which time-related claims will be
+-- validated).
 --
-verifyClaims
+verifyJWT
   ::
     ( MonadTime m, HasAllowedSkew a, HasAudiencePredicate a
     , 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) payload k
+    , HasClaimsSet payload, FromJSON payload
     )
   => a
+  -- ^ Validation settings
   -> k
-  -> SignedJWT
-  -> m ClaimsSet
-verifyClaims conf k jws =
+  -- ^ 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
   -- verified before the claims.
-  verifyJWSWithPayload f conf k jws >>= validateClaimsSet conf
+  verifyJWSWithPayload f conf k jws >>= claimsSet (validateClaimsSet conf)
   where
     f = either (throwing _JWTClaimsSetDecodeError) pure . eitherDecode
 
+-- | Variant of 'verifyJWT' that uses 'ClaimsSet' as the payload type.
+--
+verifyClaims
+  ::
+    ( MonadTime m, HasAllowedSkew a, HasAudiencePredicate a
+    , HasIssuerPredicate a
+    , HasCheckIssuedAt a
+    , HasValidationSettings a
+    , HasJWSHeader h, HasParams h
+    , AsError e, AsJWTError e, MonadError e m
+    , VerificationKeyStore m (h RequiredProtection) ClaimsSet k
+    )
+  => a
+  -- ^ Validation settings
+  -> k
+  -- ^ 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
 
--- | Cryptographically verify a JWS JWT, then validate the
--- Claims Set, returning it if valid.
+-- | Variant of 'verifyJWT' where the validation time is provided by
+-- caller.  If you process many tokens per second
+-- this lets you avoid unnecessary repeat system calls.
 --
--- This is the same as 'verifyClaims' except that the time is
--- explicitly provided.  If you process many requests per second
--- this will allow you to avoid unnecessary repeat system calls.
+verifyJWTAt
+  ::
+    ( HasAllowedSkew a, HasAudiencePredicate a
+    , HasIssuerPredicate a
+    , HasCheckIssuedAt a
+    , HasValidationSettings a
+    , HasJWSHeader h, HasParams h
+    , AsError e, AsJWTError e, MonadError e m
+    , VerificationKeyStore (ReaderT WrappedUTCTime m) (h RequiredProtection) payload k
+    , HasClaimsSet payload, FromJSON payload
+    )
+  => a
+  -- ^ Validation settings
+  -> k
+  -- ^ Key store
+  -> UTCTime
+  -- ^ 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)
+
+-- | Variant of 'verifyJWT' that uses 'ClaimsSet' as the payload type and
+-- where validation time is provided by caller.
 --
 verifyClaimsAt
   ::
@@ -558,22 +752,61 @@
     , 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 a k t jwt = runReaderT (verifyClaims a k jwt) (WrappedUTCTime t)
+verifyClaimsAt = verifyJWTAt
 
--- | Create a JWS JWT
+
+-- | Create a JWS JWT.  The payload can be any type with a 'ToJSON'
+-- instance.  See also 'signClaims' which uses 'ClaimsSet' as the
+-- payload type.
 --
+-- __Does not set any fields in the Claims Set__, such as @"iat"@
+-- ("Issued At") Claim.  The payload is encoded as-is.
+--
+signJWT
+  :: ( MonadRandom m, MonadError e m, AsError e
+     , HasJWSHeader h, HasParams h
+     , ToJSON payload )
+  => JWK
+  -- ^ 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
+  -- ^ 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
+-- at 'ClaimsSet'.
+--
+-- __Does not set any fields in the Claims Set__, such as @"iat"@
+-- ("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
-signClaims k h c = signJWS (encode c) (Identity (h, k))
+  -- ^ Payload
+  -> m (SignedJWTWithHeader h)
+signClaims = signJWT
diff --git a/test/AESKW.hs b/test/AESKW.hs
--- a/test/AESKW.hs
+++ b/test/AESKW.hs
@@ -12,43 +12,51 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
-module AESKW where
+{-# LANGUAGE NoMonomorphismRestriction #-}
+{-# LANGUAGE OverloadedStrings #-}
 
-import Control.Applicative ((<$>), pure)
+module AESKW
+  ( aeskwProperties
+  ) where
 
 import qualified Data.ByteString as B
 import Crypto.Cipher.AES
 import Crypto.Cipher.Types
 import Crypto.Error
 
-import Test.QuickCheck.Monadic
+import Hedgehog
+import qualified Hedgehog.Gen as Gen
+import qualified Hedgehog.Range as Range
 import Test.Tasty
-import Test.Tasty.QuickCheck
+import Test.Tasty.Hedgehog
 
 import Crypto.JOSE.AESKW
 
 
+aeskwProperties :: TestTree
 aeskwProperties = testGroup "AESKW"
-  [ testProperty "AESKW round-trip" prop_roundTrip
+  [ let n = "AESKW round-trip" in testPropertyNamed n n prop_roundTrip
   ]
 
 prop_roundTrip :: Property
-prop_roundTrip = monadicIO $ do
-  cekLen <- (* 8) . (+ 2) <$> pick arbitrarySizedNatural
-  cek <- pick $ B.pack <$> vectorOf cekLen arbitrary
-  kekLen <- pick $ oneof $ pure <$> [16, 24, 32]
-  kek <- pick $ B.pack <$> vectorOf kekLen arbitrary
+prop_roundTrip = property $ do
+  cekLen <- forAll $ (* 8) . (+ 2) <$> Gen.integral (Range.linear 0 16)
+  cek <- forAll $ Gen.bytes (Range.singleton cekLen)
+  kekLen <- forAll $ Gen.element [16, 24, 32]
+  kek <- forAll $ Gen.bytes (Range.singleton kekLen)
   let
-    check :: BlockCipher128 cipher => CryptoFailable cipher -> Bool
-    check cipher' = case cipher' of
-      CryptoFailed _ -> False
-      CryptoPassed cipher ->
+    go cipher' = case cipher' of
+      CryptoFailed _ -> do
+        annotate "cipherInit failed"
+        failure
+      CryptoPassed cipher -> do
         let
           c = aesKeyWrap cipher cek :: B.ByteString
           cek' = aesKeyUnwrap cipher c
-        in
-          B.length c == cekLen + 8 && cek' == Just cek
+        B.length c === cekLen + 8
+        cek' === Just cek
   case kekLen of
-    16 -> assert $ check (cipherInit kek :: CryptoFailable AES128)
-    24 -> assert $ check (cipherInit kek :: CryptoFailable AES192)
-    32 -> assert $ check (cipherInit kek :: CryptoFailable AES256)
+    16 -> go (cipherInit kek :: CryptoFailable AES128)
+    24 -> go (cipherInit kek :: CryptoFailable AES192)
+    32 -> go (cipherInit kek :: CryptoFailable AES256)
+    _  -> annotate "the impossible happened" *> failure   -- can't happen
diff --git a/test/Examples.hs b/test/Examples.hs
--- a/test/Examples.hs
+++ b/test/Examples.hs
@@ -1,8 +1,10 @@
--- | Miscellaneous end-to-end examples.
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE LambdaCase #-}
-module Examples where
 
+-- | Miscellaneous end-to-end examples.
+module Examples
+  ( spec
+  ) where
+
 import Control.Lens (_Right, (&), (?~), (.~))
 import Control.Lens.Extras (is)
 import qualified Data.ByteString.Char8 as BC8
@@ -39,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/JWK.hs b/test/JWK.hs
--- a/test/JWK.hs
+++ b/test/JWK.hs
@@ -12,16 +12,16 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
+{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
 {-# LANGUAGE OverloadedStrings #-}
 
-module JWK where
-
-import Data.Monoid ((<>))
+module JWK
+  ( spec
+  ) where
 
 import Control.Lens (_Left, _Right, review, view)
 import Control.Lens.Extras (is)
 import Data.Aeson
-import qualified Data.ByteArray as BA
 import qualified Data.ByteString as B
 import qualified Data.ByteString.Lazy as L
 import Test.Hspec
@@ -298,8 +298,9 @@
       pk <- _A2_result
       pure $ view asPublicKey sk == Just pk
 
-rfc8037_A1_jwkJson = ""
-  <> "{\"kty\":\"OKP\",\"crv\":\"Ed25519\","
+rfc8037_A1_jwkJson :: L.ByteString
+rfc8037_A1_jwkJson =
+     "{\"kty\":\"OKP\",\"crv\":\"Ed25519\","
   <> "\"d\":\"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A\","
   <> "\"x\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\"}"
 
diff --git a/test/JWS.hs b/test/JWS.hs
--- a/test/JWS.hs
+++ b/test/JWS.hs
@@ -14,23 +14,22 @@
 
 {-# LANGUAGE OverloadedStrings #-}
 
-module JWS where
+module JWS
+  ( spec
+  ) where
 
 import Data.Maybe
-import Data.Monoid ((<>))
 
 import Control.Lens hiding ((.=))
 import Control.Lens.Extras (is)
 import Control.Lens.Cons.Extras (recons)
-import Control.Monad.Except (runExceptT)
 import Data.Aeson
 import qualified Data.ByteString as BS
-import qualified Data.ByteString.Lazy as L
 import qualified Data.ByteString.Base64.URL as B64U
+import qualified Data.Set as S
 import Test.Hspec
 
 import Crypto.JOSE.Compact
-import Crypto.JOSE.Error (Error)
 import Crypto.JOSE.JWA.JWK
 import Crypto.JOSE.JWK
 import Crypto.JOSE.JWS
@@ -110,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" $
@@ -118,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" $
@@ -126,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" $
@@ -134,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" $
@@ -142,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" $
@@ -150,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" $
@@ -165,37 +164,37 @@
       -- 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" $
     let
       -- protected header: {"crit":["nonce"],"nonce":"bm9uY2U"}
       s = "{\"protected\":\"eyJjcml0IjpbIm5vbmNlIl0sIm5vbmNlIjoiYm05dVkyVSJ9\""
-          <>",\"header\":{\"alg\":\"none\"},\"signature\":\"\"}"
+          <> ",\"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
 
 
@@ -205,10 +204,7 @@
   \ \"exp\":1300819380,\r\n\
   \ \"http://example.com/is_root\":true}"
 
-examplePayload :: Types.Base64Octets
-examplePayload = Types.Base64Octets examplePayloadBytes
 
-
 appendixA1Spec :: Spec
 appendixA1Spec = describe "RFC 7515 A.1.  Example JWS using HMAC SHA-256" $ do
   -- can't make aeson encode JSON to exact representation used in
@@ -223,12 +219,11 @@
     fmap encodeCompact jws `shouldBe` Right compactJWS
 
   it "computes the HMAC correctly" $
-    fst (withDRG drg $
-      runExceptT (sign alg (jwk ^. jwkMaterial) (signingInput' ^. recons)))
+    fst (withDRG drg $ runJOSE $ (sign alg_ (k ^. jwkMaterial) (signingInput' ^. recons)))
       `shouldBe` (Right mac :: Either Error BS.ByteString)
 
   it "validates the JWS correctly" $
-    (jws >>= verifyJWS defaultValidationSettings jwk)
+    (jws >>= verifyJWS defaultValidationSettings k)
     `shouldBe` Right examplePayloadBytes
 
   where
@@ -239,14 +234,14 @@
       \cGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
     compactJWS = signingInput' <> ".dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
     jws = decodeCompact compactJWS :: Either Error (CompactJWS JWSHeader)
-    alg = JWA.JWS.HS256
-    h = newJWSHeader ((), alg)
-        & typ .~ Just (HeaderParam () "JWT")
+    alg_ = JWA.JWS.HS256
+    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,
       132, 141, 121]
-    jwk = fromOctets
+    k = fromOctets
       [3,35,53,75,43,15,165,188,131,126,6,101,119,123,166,143,90,179,40,
        230,240,84,201,40,169,15,132,178,210,80,46,191,211,251,90,146,
        210,6,71,239,150,138,180,195,119,98,61,34,61,46,33,114,5,46,79,8,
@@ -277,16 +272,16 @@
 appendixA2Spec :: Spec
 appendixA2Spec = describe "RFC 7515 A.2. Example JWS using RSASSA-PKCS-v1_5 SHA-256" $ do
   it "computes the signature correctly" $
-    fst (withDRG drg $ runExceptT (sign JWA.JWS.RS256 (jwk ^. jwkMaterial) signingInput'))
+    fst (withDRG drg $ runJOSE (sign JWA.JWS.RS256 (k ^. jwkMaterial) signingInput'))
       `shouldBe` (Right sig :: Either Error BS.ByteString)
 
   it "validates the signature correctly" $
-    verify JWA.JWS.RS256 (jwk ^. jwkMaterial) signingInput' sig
+    verify JWA.JWS.RS256 (k ^. jwkMaterial) signingInput' sig
       `shouldBe` (Right True :: Either Error Bool)
 
   it "prohibits signing with 1024-bit key" $
-    fst (withDRG drg (runExceptT $
-      signJWS signingInput' (Identity (newJWSHeader ((), JWA.JWS.RS256), jwkRSA1024))))
+    fst (withDRG drg (runJOSE $
+      signJWS signingInput' (Identity (newJWSHeaderProtected JWA.JWS.RS256, jwkRSA1024))))
         `shouldBe` (Left KeySizeTooSmall :: Either Error (CompactJWS JWSHeader))
 
   where
@@ -295,7 +290,7 @@
       \.\
       \eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt\
       \cGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
-    jwk = fromJust $ decode "\
+    k = fromJust $ decode "\
       \{\"kty\":\"RSA\",\
       \ \"n\":\"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx\
             \HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs\
@@ -336,7 +331,7 @@
 appendixA3Spec :: Spec
 appendixA3Spec = describe "RFC 7515 A.3.  Example JWS using ECDSA P-256 SHA-256" $
   it "validates the signature correctly" $
-    verify JWA.JWS.ES256 (jwk ^. jwkMaterial) signingInput' sig
+    verify JWA.JWS.ES256 (k ^. jwkMaterial) signingInput' sig
     `shouldBe` (Right True :: Either Error Bool)
   where
     signingInput' = "\
@@ -344,7 +339,7 @@
       \.\
       \eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt\
       \cGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
-    jwk = fromJust $ decode "\
+    k = fromJust $ decode "\
       \{\"kty\":\"EC\",\
       \ \"crv\":\"P-256\",\
       \ \"x\":\"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU\",\
@@ -367,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 $ runExceptT $
-      signJWS examplePayloadBytes (Identity (newJWSHeader ((), JWA.JWS.None), undefined))
+    jws = fst $ withDRG drg $ runJOSE $
+      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
@@ -504,7 +504,7 @@
 cfrgSpec :: Spec
 cfrgSpec = describe "RFC 8037 signature/validation test vectors" $ do
   let
-    jwk = fromJust $ decode "\
+    k = fromJust $ decode "\
       \{\"kty\":\"OKP\",\"crv\":\"Ed25519\",\
       \\"d\":\"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A\",\
       \\"x\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\"}"
@@ -516,10 +516,10 @@
     sig = BS.pack sigOctets
     signingInput = "eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc"
   it "computes the correct signature" $
-    fst (withDRG drg $ runExceptT (sign JWA.JWS.EdDSA (view jwkMaterial jwk) signingInput))
+    fst (withDRG drg $ runJOSE (sign JWA.JWS.EdDSA (view jwkMaterial k) signingInput))
       `shouldBe` (Right sig :: Either Error BS.ByteString)
   it "validates signatures correctly" $
-    verify JWA.JWS.EdDSA (view jwkMaterial jwk) signingInput sig
+    verify JWA.JWS.EdDSA (view jwkMaterial k) signingInput sig
       `shouldBe` (Right True :: Either Error Bool)
 
 base64urlSpec :: Spec
diff --git a/test/JWT.hs b/test/JWT.hs
--- a/test/JWT.hs
+++ b/test/JWT.hs
@@ -12,29 +12,24 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE OverloadedStrings #-}
 
-module JWT where
+module JWT
+  ( spec
+  ) where
 
 import Data.Maybe
-import Data.Monoid ((<>))
 
+import qualified Data.ByteString.Lazy as L
 import Control.Lens
 import Control.Lens.Extras (is)
-import Control.Monad.Except (runExceptT)
 import Control.Monad.Trans (liftIO)
-import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT)
-import Control.Monad.State (execState)
-import Control.Monad.Time (MonadTime(..))
+import Control.Monad.Reader (runReaderT)
 import Data.Aeson hiding ((.=))
-import Data.Functor.Identity (runIdentity)
-import Data.HashMap.Strict (insert)
+import qualified Data.Aeson.KeyMap as M
 import qualified Data.Set as S
 import Data.Time
 import Network.URI (parseURI)
-import Safe (headMay)
 import Test.Hspec
 
 import Crypto.JWT
@@ -43,46 +38,90 @@
 intDate :: String -> Maybe NumericDate
 intDate = fmap NumericDate . parseTimeM True defaultTimeLocale "%F %T"
 
-utcTime :: String -> UTCTime
-utcTime = fromJust . parseTimeM True defaultTimeLocale "%F %T"
+utcTime :: String -> WrappedUTCTime
+utcTime = WrappedUTCTime . fromJust . parseTimeM True defaultTimeLocale "%F %T"
 
+--
+-- example extended JWT payload type
+--
+
+data Super = Super { jwtClaims :: ClaimsSet, isRoot :: Bool }
+  deriving (Eq, Show)
+
+instance HasClaimsSet Super where
+  claimsSet f s = fmap (\a' -> s { jwtClaims = a' }) (f (jwtClaims s))
+
+instance FromJSON Super where
+  parseJSON = withObject "Super" $ \o -> Super
+    <$> parseJSON (Object o)
+    <*> o .: "http://example.com/is_root"
+
+instance ToJSON Super where
+  toJSON s =
+    ins "http://example.com/is_root" (isRoot s) (toJSON (jwtClaims s))
+    where
+      ins k v (Object o) = Object $ M.insert k (toJSON v) o
+      ins _ _ a = a
+
+super :: Super
+super = Super
+  { jwtClaims = exampleClaimsSet
+  , isRoot = True
+  }
+
+claimsJSON :: L.ByteString
+claimsJSON =
+  "{\"iss\":\"joe\",\r\n"
+  <> "\"exp\":1300819380,\r\n"
+  <> "\"http://example.com/is_root\":true}"
+
 exampleClaimsSet :: ClaimsSet
 exampleClaimsSet = emptyClaimsSet
   & claimIss .~ preview stringOrUri ("joe" :: String)
   & claimExp .~ intDate "2011-03-22 18:43:00"
-  & over unregisteredClaims (insert "http://example.com/is_root" (Bool True))
   & addClaim "http://example.com/is_root" (Bool True)
 
-#if ! MIN_VERSION_monad_time(0,3,0)
-instance Monad m => MonadTime (ReaderT UTCTime m) where
-  currentTime = ask
-#endif
-
 spec :: Spec
 spec = do
   let conf = set algorithms (S.singleton None)
               (defaultJWTValidationSettings (const False))
+      headMay []    = Nothing
+      headMay (h:_) = Just h
 
   describe "JWT Claims Set" $ do
-    it "parses from JSON correctly" $
+    it "parses from JSON correctly" $ do
+      decode claimsJSON `shouldBe` Just exampleClaimsSet
+      decode claimsJSON `shouldBe` Just super
+
+    it "JWT round-trip (sign, serialise, decode, verify)" $ do
       let
-        claimsJSON =
-          "{\"iss\":\"joe\",\r\n"
-          <> "\"exp\":1300819380,\r\n"
-          <> "\"http://example.com/is_root\":true}"
-      in
-        decode claimsJSON `shouldBe` Just exampleClaimsSet
+        claims = emptyClaimsSet
+        valConf = defaultJWTValidationSettings (const True)
+      k <- genJWK $ RSAGenParam 256
+      res <- runJOSE $ do
+        token <- signClaims k (newJWSHeaderProtected RS512) claims
+        token' <- decodeCompact . encodeCompact $ token
+        liftIO $ token' `shouldBe` token
+        claims' <- verifyClaims valConf k token'
+        liftIO $ claims' `shouldBe` claims
+      either (error . show) return (res :: Either JWTError ()) :: IO ()
 
-    it "JWT compact round-trip" $ do
-      jwk <- genJWK $ RSAGenParam 256
-      res <- runExceptT $ do
-        token <- signClaims jwk (newJWSHeader ((), RS512)) emptyClaimsSet
+    it "JWT round-trip (sign, serialise, decode, verify) [extended payload type]" $ do
+      let
+        valConf = defaultJWTValidationSettings (const True)
+        now = utcTime "2010-01-01 00:00:00"
+      k <- genJWK $ RSAGenParam 256
+      res <- runJOSE $ do
+        token <- signJWT k (newJWSHeaderProtected RS512) super
         token' <- decodeCompact . encodeCompact $ token
         liftIO $ token' `shouldBe` token
+        claims <- runReaderT (verifyJWT valConf k token') now
+        liftIO $ claims `shouldBe` super
       either (error . show) return (res :: Either JWTError ()) :: IO ()
 
-    it "formats to a parsable and equal value" $
+    it "formats to a parsable and equal value" $ do
       decode (encode exampleClaimsSet) `shouldBe` Just exampleClaimsSet
+      decode (encode super) `shouldBe` Just super
 
     describe "with an Expiration Time claim" $ do
       describe "when the current time is prior to the Expiration Time" $ do
@@ -159,62 +198,64 @@
 
     describe "with a Not Before claim" $ do
       let
-        claimsSet = emptyClaimsSet & claimNbf .~ intDate "2016-07-05 17:37:22"
+        claims = emptyClaimsSet & claimNbf .~ intDate "2016-07-05 17:37:22"
       describe "when the current time is prior to the Not Before claim" $ do
         let now = utcTime "2016-07-05 17:37:20" -- 2s before nbf
         it "cannot be validated" $
-          runReaderT (validateClaimsSet conf claimsSet) now
+          runReaderT (validateClaimsSet conf claims) now
             `shouldBe` Left JWTNotYetValid
         it "cannot be validated if nonzero skew tolerance < delta" $
           let conf' = set allowedSkew 1 conf
-          in runReaderT (validateClaimsSet conf' claimsSet) now
+          in runReaderT (validateClaimsSet conf' claims) now
             `shouldBe` Left JWTNotYetValid
         it "can be validated if nonzero skew tolerance = delta" $
           let conf' = set allowedSkew 2 conf
-          in runReaderT (validateClaimsSet conf' claimsSet) now
-            `shouldBe` (Right claimsSet :: Either JWTError ClaimsSet)
+          in runReaderT (validateClaimsSet conf' claims) now
+            `shouldBe` (Right claims :: Either JWTError ClaimsSet)
         it "can be validated if nonzero skew tolerance > delta" $
           let conf' = set allowedSkew 3 conf
-          in runReaderT (validateClaimsSet conf' claimsSet) now
-            `shouldBe` (Right claimsSet :: Either JWTError ClaimsSet)
+          in runReaderT (validateClaimsSet conf' claims) now
+            `shouldBe` (Right claims :: Either JWTError ClaimsSet)
         it "can be validated if negative skew tolerance = -delta" $
           let conf' = set allowedSkew (-2) conf
-          in runReaderT (validateClaimsSet conf' claimsSet) now
-            `shouldBe` (Right claimsSet :: Either JWTError ClaimsSet)
+          in runReaderT (validateClaimsSet conf' claims) now
+            `shouldBe` (Right claims :: Either JWTError ClaimsSet)
 
       describe "when the current time is exactly equal to the Not Before claim" $
         it "can be validated" $
-          runReaderT (validateClaimsSet conf claimsSet) (utcTime "2016-07-05 17:37:22")
-            `shouldBe` (Right claimsSet :: Either JWTError ClaimsSet)
+          runReaderT (validateClaimsSet conf claims) (utcTime "2016-07-05 17:37:22")
+            `shouldBe` (Right claims :: Either JWTError ClaimsSet)
 
       describe "when the current time is after the Not Before claim" $
         it "can be validated" $
-          runReaderT (validateClaimsSet conf claimsSet) (utcTime "2017-01-01 00:00:00")
-            `shouldBe` (Right claimsSet :: Either JWTError ClaimsSet)
+          runReaderT (validateClaimsSet conf claims) (utcTime "2017-01-01 00:00:00")
+            `shouldBe` (Right claims :: Either JWTError ClaimsSet)
 
     describe "with Expiration Time and Not Before claims" $ do
       let
-        claimsSet = emptyClaimsSet & claimExp .~ intDate "2011-03-22 18:43:00"
-                                   & claimNbf .~ intDate "2011-03-20 17:37:22"
+        claims =
+          emptyClaimsSet
+            & claimExp .~ intDate "2011-03-22 18:43:00"
+            & claimNbf .~ intDate "2011-03-20 17:37:22"
       describe "when the current time is prior to the Not Before claim" $
         it "cannot be validated" $
-          runReaderT (validateClaimsSet conf claimsSet) (utcTime "2011-03-18 00:00:00")
+          runReaderT (validateClaimsSet conf claims) (utcTime "2011-03-18 00:00:00")
             `shouldBe` Left JWTNotYetValid
       describe "when the current time is exactly equal to the Not Before claim" $
         it "can be validated" $
-          runReaderT (validateClaimsSet conf claimsSet) (utcTime "2011-03-20 17:37:22")
-            `shouldBe` (Right claimsSet :: Either JWTError ClaimsSet)
+          runReaderT (validateClaimsSet conf claims) (utcTime "2011-03-20 17:37:22")
+            `shouldBe` (Right claims :: Either JWTError ClaimsSet)
       describe "when the current time is between the Not Before and Expiration Time claims" $
         it "can be validated" $
-          runReaderT (validateClaimsSet conf claimsSet) (utcTime "2011-03-21 18:00:00")
-            `shouldBe` (Right claimsSet :: Either JWTError ClaimsSet)
+          runReaderT (validateClaimsSet conf claims) (utcTime "2011-03-21 18:00:00")
+            `shouldBe` (Right claims :: Either JWTError ClaimsSet)
       describe "when the current time is exactly the Expiration Time" $
         it "cannot be validated" $
-          runReaderT (validateClaimsSet conf claimsSet) (utcTime "2011-03-22 18:43:00")
+          runReaderT (validateClaimsSet conf claims) (utcTime "2011-03-22 18:43:00")
             `shouldBe` Left JWTExpired
       describe "when the current time is after the Expiration Time claim" $
         it "cannot be validated" $
-          runReaderT (validateClaimsSet conf claimsSet) (utcTime "2011-03-24 00:00:00")
+          runReaderT (validateClaimsSet conf claims) (utcTime "2011-03-24 00:00:00")
             `shouldBe` Left JWTExpired
 
     describe "with an Audience claim" $ do
@@ -280,6 +321,28 @@
       decode "[1382245921]" `shouldBe` fmap (:[]) (intDate "2013-10-20 05:12:01")
       decode "[\"notnum\"]"       `shouldBe` (Nothing :: Maybe [NumericDate])
 
+  describe "RFC 7519 §3.1.  Example JWT" $
+    it "verifies JWT" $ do
+      let
+        exampleJWT =
+          "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9"
+          <> "."
+          <> "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt"
+          <> "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
+          <> "."
+          <> "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
+        k = fromOctets
+          [3,35,53,75,43,15,165,188,131,126,6,101,119,123,166,143,90,179,40,
+           230,240,84,201,40,169,15,132,178,210,80,46,191,211,251,90,146,
+           210,6,71,239,150,138,180,195,119,98,61,34,61,46,33,114,5,46,79,8,
+           192,205,154,245,103,208,128,163]
+        now = utcTime "2010-01-01 00:00:00"
+        settings = defaultJWTValidationSettings (const True)
+      runReaderT (decodeCompact @SignedJWT exampleJWT >>= verifyClaims settings k) now
+        `shouldBe` (Right exampleClaimsSet :: Either JWTError ClaimsSet)
+      runReaderT (decodeCompact @SignedJWT exampleJWT >>= verifyJWT settings k) now
+        `shouldBe` (Right super :: Either JWTError Super)
+
   describe "RFC 7519 §6.1.  Example Unsecured JWT" $ do
     let
       exampleJWT =
@@ -288,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" $
@@ -303,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,5 +1,4 @@
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
+{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
 
 {-
 
@@ -12,12 +11,11 @@
 Related: https://github.com/frasertweedale/hs-jose/pull/103
 
 -}
-module Main where
 
 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
@@ -12,71 +12,190 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
-{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE NoMonomorphismRestriction #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
 
-module Properties where
+module Properties
+  ( properties
+  ) where
 
-import Control.Applicative
-import Control.Monad.Except (runExceptT)
+import Control.Monad.IO.Class
 
-import Data.Aeson
+import Control.Lens ((&), set, view)
+import Crypto.Number.Basic (log2)
+import Crypto.Random
+import Data.Aeson (FromJSON, ToJSON, decode, encode)
 import qualified Data.ByteString as B
 
+import Hedgehog
+import qualified Hedgehog.Gen as Gen
+import qualified Hedgehog.Range as Range
 import Test.Tasty
-import Test.Tasty.QuickCheck
-import Test.QuickCheck.Monadic
-import Test.QuickCheck.Instances ()
+import Test.Tasty.Hedgehog
 
-import Crypto.JOSE.Error (Error(..))
 import Crypto.JOSE.Types
 import Crypto.JOSE.JWK
 import Crypto.JOSE.JWS
 
+
+instance (MonadIO m) => MonadRandom (PropertyT m) where
+  getRandomBytes = liftIO . getRandomBytes
+
+properties :: TestTree
 properties = testGroup "Properties"
-  [ testProperty "SizedBase64Integer round-trip"
-    (prop_roundTrip :: SizedBase64Integer -> Property)
-  , testProperty "JWK round-trip" (prop_roundTrip :: JWK -> Property)
-  , testProperty "RSA gen, sign and verify" prop_rsaSignAndVerify
-  , testProperty "gen, sign with best alg, verify" prop_bestJWSAlg
+  [ let n = "SizedBase64Integer round-trip" in testPropertyNamed n n (prop_roundTrip genSizedBase64Integer)
+  , let n = "JWK round-trip" in testPropertyNamed n n (prop_roundTrip genJWK')
+  , let n = "RSA gen, sign and verify" in testPropertyNamed n n prop_rsaSignAndVerify
+  , let n = "gen, sign with best alg, verify" in testPropertyNamed n n prop_bestJWSAlg
   ]
 
-prop_roundTrip :: (Eq a, Show a, ToJSON a, FromJSON a) => a -> Property
-prop_roundTrip a = decode (encode [a]) === Just [a]
+genBigInteger :: Gen Integer
+genBigInteger = Gen.integral $ Range.exponential 0 (2 ^ (4096 :: Integer))
 
-debugRoundTrip
-  :: (Show a, Arbitrary a, ToJSON a, FromJSON a)
-  => (a -> Bool)
-  -> Property
-debugRoundTrip f = monadicIO $ do
-  a :: a <- pick arbitrary
-  let encoded = encode [a]
-  monitor $ counterexample $
-    "JSON: \n" ++ show encoded ++ "\n\nDecoded: \n" ++ show (decode encoded :: Maybe [a])
-  assert $ f a
+genBase64Integer :: Gen Base64Integer
+genBase64Integer = Base64Integer <$> genBigInteger
 
-prop_rsaSignAndVerify :: B.ByteString -> Property
-prop_rsaSignAndVerify msg = monadicIO $ do
-  keylen <- pick $ elements ((`div` 8) <$> [2048, 3072, 4096])
-  k :: JWK <- run $ genJWK (RSAGenParam keylen)
-  alg <- pick $ elements [RS256, RS384, RS512, PS256, PS384, PS512]
-  monitor (collect alg)
-  wp (runExceptT (signJWS msg [(newJWSHeader (Protected, alg), k)]
-    >>= verifyJWS defaultValidationSettings k)) (checkSignVerifyResult msg)
+genSizedBase64Integer :: Gen SizedBase64Integer
+genSizedBase64Integer = do
+  x <- genBigInteger
+  l <- Gen.element [0, 1, 2]  -- number of leading zero-bytes
+  pure $ SizedBase64Integer ((log2 x `div` 8) + 1 + l) x
 
-prop_bestJWSAlg :: B.ByteString -> Property
-prop_bestJWSAlg msg = monadicIO $ do
-  genParam <- pick arbitrary
-  k <- run $ genJWK genParam
+
+prop_roundTrip :: (Eq a, Show a, ToJSON a, FromJSON a) => Gen a -> Property
+prop_roundTrip gen = property $
+  forAll gen >>= \a -> decode (encode [a]) === Just [a]
+
+prop_rsaSignAndVerify :: Property
+prop_rsaSignAndVerify = property $ do
+  msg <- forAll $ Gen.bytes (Range.linear 0 100)
+  keylen <- forAll $ Gen.element ((`div` 8) <$> [2048, 3072, 4096])
+  k <- evalIO $ genJWK (RSAGenParam keylen)
+  alg_ <- forAll $ Gen.element [RS256, RS384, RS512, PS256, PS384, PS512]
+  collect alg_
+  msg' <- evalExceptT $ unwrapJOSE
+    ( signJWS msg [(newJWSHeader (Protected, alg_), k)]
+      >>= verifyJWS defaultValidationSettings k
+      :: JOSE Error (PropertyT IO) B.ByteString
+    )
+  msg' === msg
+
+
+genCrv :: Gen Crv
+genCrv = Gen.element [P_256, P_384, P_521, Secp256k1]
+
+genOKPCrv :: Gen OKPCrv
+genOKPCrv = Gen.element [Ed25519, Ed448, X25519, X448]
+
+genKeyMaterialGenParam :: Gen KeyMaterialGenParam
+genKeyMaterialGenParam = Gen.choice
+  [ ECGenParam <$> genCrv
+  , RSAGenParam <$> Gen.element ((`div` 8) <$> [2048, 3072, 4096])
+  , OctGenParam <$> liftA2 (+) (Gen.integral (Range.exponential 0 64)) (Gen.element [32, 48, 64])
+  , OKPGenParam <$> genOKPCrv
+  ]
+
+prop_bestJWSAlg :: Property
+prop_bestJWSAlg = property $ do
+  msg <- forAll $ Gen.bytes (Range.linear 0 100)
+
+  genParam <- forAll $ genKeyMaterialGenParam
+  k <- evalIO $ genJWK genParam
+
   case bestJWSAlg k of
-    Left (KeyMismatch _) -> pre False  -- skip non-signing keys
+    Left (KeyMismatch _) -> discard   -- skip non-signing keys
     Left _ -> assert False
-    Right alg -> do
-      monitor (collect alg)
-      let
-        go = do
-          jws <- signJWS msg [(newJWSHeader (Protected, alg), k)]
-          verifyJWS defaultValidationSettings k jws
-      wp (runExceptT go) (checkSignVerifyResult msg)
+    Right alg_ -> do
+      collect alg_
+      msg' <- evalExceptT $ unwrapJOSE
+        ( signJWS msg [(newJWSHeader (Protected, alg_), k)]
+          >>= verifyJWS defaultValidationSettings k
+          :: JOSE Error (PropertyT IO) B.ByteString
+        )
+      msg' === msg
 
-checkSignVerifyResult :: Monad m => B.ByteString -> Either Error B.ByteString -> PropertyM m ()
-checkSignVerifyResult msg = assert . either (const False) (== msg)
+
+
+
+genRSAPrivateKeyOthElem :: Gen RSAPrivateKeyOthElem
+genRSAPrivateKeyOthElem =
+  RSAPrivateKeyOthElem <$> genBase64Integer <*> genBase64Integer <*> genBase64Integer
+
+genRSAPrivateKeyOptionalParameters :: Gen RSAPrivateKeyOptionalParameters
+genRSAPrivateKeyOptionalParameters =
+  RSAPrivateKeyOptionalParameters
+    <$> genBase64Integer
+    <*> genBase64Integer
+    <*> genBase64Integer
+    <*> genBase64Integer
+    <*> genBase64Integer
+    <*> Gen.maybe (Gen.nonEmpty (Range.linear 1 3) genRSAPrivateKeyOthElem)
+
+genRSAPrivateKeyParameters :: Gen RSAPrivateKeyParameters
+genRSAPrivateKeyParameters =
+  RSAPrivateKeyParameters
+    <$> genBase64Integer
+    <*> Gen.maybe (genRSAPrivateKeyOptionalParameters)
+
+genRSAKeyParameters :: Gen RSAKeyParameters
+genRSAKeyParameters =
+  RSAKeyParameters
+    <$> genBase64Integer
+    <*> genBase64Integer
+    <*> Gen.maybe (genRSAPrivateKeyParameters)
+
+genDRG :: Gen ChaChaDRG
+genDRG = do
+  let word64 = Gen.word64 Range.constantBounded
+  seed <- (,,,,) <$> word64 <*> word64 <*> word64 <*> word64 <*> word64
+  pure $ drgNewTest seed
+
+genECKeyParameters :: Gen ECKeyParameters
+genECKeyParameters = do
+    drg <- genDRG
+    crv <- genCrv
+    let (k, _) = withDRG drg (genEC crv)
+    includePrivate <- Gen.bool
+    pure $ if includePrivate
+      then k
+      else (let Just a = view asPublicKey k in a)
+
+genOctKeyParameters :: Gen OctKeyParameters
+genOctKeyParameters = OctKeyParameters . Base64Octets <$> Gen.bytes (Range.linear 16 128)
+
+genOKPKeyParameters :: Gen OKPKeyParameters
+genOKPKeyParameters = do
+  drg <- genDRG
+  crv <- genOKPCrv
+  let (k, _) = withDRG drg (genOKP crv)
+  includePrivate <- Gen.bool
+  pure $ if includePrivate
+    then k
+    else (let Just a = view asPublicKey k in a)
+
+genKeyMaterial' :: Gen KeyMaterial
+genKeyMaterial' = Gen.choice
+  [ ECKeyMaterial <$> genECKeyParameters
+  , RSAKeyMaterial <$> genRSAKeyParameters
+  , OctKeyMaterial <$> genOctKeyParameters
+  , OKPKeyMaterial <$> genOKPKeyParameters
+  ]
+
+genBase64SHA1 :: Gen Base64SHA1
+genBase64SHA1 = Base64SHA1 <$> Gen.bytes (Range.singleton 20)
+
+genBase64SHA256 :: Gen Base64SHA256
+genBase64SHA256 = Base64SHA256 <$> Gen.bytes (Range.singleton 32)
+
+genJWK' :: Gen JWK
+genJWK' = do
+  key <- genKeyMaterial'
+  kid_ <- Gen.text (Range.linear 8 16) Gen.hexit
+  x5t_ <- genBase64SHA1
+  x5tS256_ <- genBase64SHA256
+  pure $ fromKeyMaterial key
+    & set jwkKid (Just kid_)
+    & set jwkX5t (Just x5t_)
+    & set jwkX5tS256 (Just x5tS256_)
diff --git a/test/Test.hs b/test/Test.hs
--- a/test/Test.hs
+++ b/test/Test.hs
@@ -12,10 +12,12 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
+import Test.Hspec
 import Test.Tasty
 import Test.Tasty.Hspec
-import Test.Tasty.QuickCheck
 
+import Crypto.JOSE (vulnerableToHashFlood)
+
 import AESKW
 import Examples
 import JWK
@@ -32,9 +34,18 @@
 
 unitTestsIO :: IO TestTree
 unitTestsIO = do
-  types <- testSpec "Types" Types.spec
-  jwk <- testSpec "JWK" JWK.spec
-  jws <- testSpec "JWS" JWS.spec
-  jwt <- testSpec "JWT" JWT.spec
-  examples <- testSpec "Examples" Examples.spec
-  return $ testGroup "Unit tests" [types, jwk, jws, jwt, examples]
+  testGroup "Unit tests" <$> sequenceA specs
+  where
+    specs =
+      [ testSpec "Types" Types.spec
+      , testSpec "JWK" JWK.spec
+      , testSpec "JWS" JWS.spec
+      , testSpec "JWT" JWT.spec
+      , testSpec "Examples" Examples.spec
+      , testSpec "Security" securitySpec
+      ]
+
+securitySpec :: Spec
+securitySpec = describe "security characteristics" $
+  it "not vulnerable to hash-flood" $
+    vulnerableToHashFlood `shouldBe` False
diff --git a/test/Types.hs b/test/Types.hs
--- a/test/Types.hs
+++ b/test/Types.hs
@@ -14,12 +14,13 @@
 
 {-# LANGUAGE OverloadedStrings #-}
 
-module Types where
+module Types
+  ( spec
+  ) where
 
 import Data.Aeson
 import qualified Data.ByteString as BS
 import Data.List.NonEmpty
-import Network.URI (parseURI)
 import Test.Hspec
 
 import Crypto.JOSE.Types
@@ -27,7 +28,6 @@
 spec :: Spec
 spec = do
   base64OctetsSpec
-  uriSpec
   base64IntegerSpec
   sizedBase64IntegerSpec
   base64X509Spec
@@ -40,17 +40,6 @@
   where
     iv = BS.pack [3, 22, 60, 12, 43, 67, 104, 105, 108, 108, 105, 99, 111, 116, 104, 101]
     tag = BS.pack [246, 17, 244, 190, 4, 95, 98, 3, 231, 0, 115, 157, 242, 203, 100, 191]
-
-uriSpec :: Spec
-uriSpec = describe "URI typeclasses" $ do
-  it "gets parsed from JSON correctly" $ do
-    decode "[\"http://example.com\"]" `shouldBe`
-      fmap (fmap (:[])) parseURI "http://example.com"
-    decode "[\"foo\"]" `shouldBe` (Nothing :: Maybe [URI])
-
-  it "gets formatted to JSON correctly" $
-    fmap toJSON (Network.URI.parseURI "http://example.com")
-      `shouldBe` Just (String "http://example.com")
 
 base64IntegerSpec :: Spec
 base64IntegerSpec = describe "Base64Integer" $ do
