diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,120 +4,178 @@
 The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
 
-## 0.6.1
+## [1.0.0] - 2023-07-28
+
 ### Changed
+
+- Context data structure must be created and passed explicitly.
+- Field selectors are now short and duplicates are allowed.
+- Use DuplicateRecordFields and OverloadedRecordDot language extensions.
+
+## [0.6.1] - 2022-04-18
+
+### Changed
+
 - Bump version and LTS Haskell.
 - Update dependencies.
 
-## 0.6.0
+## [0.6.0] - 2021-08-27
+
 ### Changed
+
 - Remove all use of CPP.
 - Depend on base16 instead of the old base16-bytestring package.
 - Target ghc-9.0.1 compiler.
 
 ### Fixed
+
 - Do not crash on bytestrings that are backed by null C pointers.
 
-## 0.5.0
+## [0.5.0] - 2020-10-16
+
 ### Changed
+
 - Include version boundaries and other changes submitted by Emily Pillmore.
 
-## 0.4.0
+## [0.4.0] - 2020-07-23
+
 ### Changed
+
 - Remove fragile ForeignPtr implementation in favor of just storing ByteStrings.
 - Reuse memory instead of copying when possible.
 
-## 0.3.1
+## [0.3.1] - 2020-07-03
+
 ### Fixed
+
 - Use unsafe calls in FFI.
 
-## 0.3.0
+## [0.3.0] - 2020-06-13
+
 ### Fixed
+
 - Compiles with all flags now.
 
 ### Added
+
 - Script to compile with all flags.
 
 ### Removed
+
 - Remove ECDH support.
 - Remove Schnorr support.
 - Remove Recovery support.
 
-## 0.2.5
+## [0.2.5] - 2020-06-13
+
 ### Changed
+
 - Reuse context aggressively.
 - Generate context in a single thread.
 
 ### Fixed
+
 - Memory deallocation bug.
 
-## 0.2.4
+## [0.2.4] - 2020-06-12
+
 ### Changed
+
 - Update Cabal and package version.
 
-## 0.2.3
+## [0.2.3] - 2020-06-07
+
 ### Changed
+
 - Return meaningful error upon encountering weird ret status from upstream code.
 
 ### Added
+
 - Test parallel signature creation and verification.
 
-## 0.2.2
+## [0.2.2] - 2020-04-14
+
 ### Removed
+
 - Hide tweak negation behind a flag for compatibilidy with Debian 9.
 
 ### Fixed
+
 - Correct code that was not compiling with some flags enabled.
 
-## 0.2.1
+## [0.2.1] - 2020-04-10
+
 ### Changed
+
 - Do not depend on hardcoded DER signatures in tests.
 
-## 0.2.0
+## [0.2.0] - 2020-04-10
+
 ### Added
+
 - Support for ECDH APIs.
 - Support for Schnorr APIs.
 
 ### Removed
+
 - Enabling key recovery APIs need a flag.
 
-## 0.1.8
+## [0.1.8] - 2020-01-15
+
 ### Added
+
 - Add missing `NFData` instances for some types.
 
-## 0.1.7
+## [0.1.7] - 2020-01-15
+
 ### Added
+
 - Add `NFData` instances for all types.
 
-## 0.1.6
+## [0.1.6] - 2019-12-26
+
 ### Added
+
 - Use `pkgconfig` for C library dependency.
 
-## 0.1.5
+## [0.1.5] - 2019-09-19
+
 ### Added
+
 - Flag for ECDH bindings.
 
-## 0.1.4
+## [0.1.4] - 2018-10-25
+
 ### Changed
+
 - Constrain imports to avoid clashes with a QuickCheck function.
 
-## 0.1.3
+## [0.1.3] - 2018-10-13
+
 ### Added
+
 - Hashable instances for various types.
 
-## 0.1.2
+## [0.1.2] - 2018-09-10
+
 ### Changed
+
 - Separate dependencies between library and tests.
 - Remove `hspec` default to prevent problems with Nix.
 
 ### Removed
+
 - Dependency to `cryptohash` not needed.
 
-## 0.1.1
+## [0.1.1] - 2018-09-10
+
 ### Changed
+
 - Update changelog to reflect name and version change.
 - Update to LTS Haskell 12.9.
 
-## 0.1.0
+## [0.1.0] - 2018-09-10
+
 ### Changed
+
 - Name of package change from `secp256k1` to `secp256k1-haskell` to avoid Nix conflicts.
diff --git a/secp256k1-haskell.cabal b/secp256k1-haskell.cabal
--- a/secp256k1-haskell.cabal
+++ b/secp256k1-haskell.cabal
@@ -1,11 +1,11 @@
 cabal-version: 2.0
 
--- This file has been generated from package.yaml by hpack version 0.35.1.
+-- This file has been generated from package.yaml by hpack version 0.35.2.
 --
 -- see: https://github.com/sol/hpack
 
 name:           secp256k1-haskell
-version:        0.7.0
+version:        1.0.0
 synopsis:       Bindings for secp256k1
 description:    Sign and verify signatures using the secp256k1 library.
 category:       Crypto
@@ -41,7 +41,6 @@
     , base >=4.9 && <5
     , base16 >=1.0
     , bytestring >=0.10.8 && <0.12
-    , cereal >=0.5.4 && <0.6
     , deepseq >=1.4.2 && <1.5
     , entropy >=0.3.8 && <0.5
     , hashable >=1.2.6 && <1.5
@@ -67,7 +66,6 @@
     , base >=4.9 && <5
     , base16 >=1.0
     , bytestring >=0.10.8 && <0.12
-    , cereal >=0.5.4 && <0.6
     , deepseq >=1.4.2 && <1.5
     , entropy >=0.3.8 && <0.5
     , hashable >=1.2.6 && <1.5
diff --git a/src/Crypto/Secp256k1.hs b/src/Crypto/Secp256k1.hs
--- a/src/Crypto/Secp256k1.hs
+++ b/src/Crypto/Secp256k1.hs
@@ -1,8 +1,11 @@
 {-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DuplicateRecordFields #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE NoFieldSelectors #-}
 
 -- |
 -- Module      : Crypto.Secp256k1
@@ -13,24 +16,32 @@
 --
 -- Crytpographic functions from Bitcoin’s secp256k1 library.
 module Crypto.Secp256k1
-  ( -- * Messages
-    Msg,
+  ( -- * Context
+    Ctx (..),
+    withContext,
+    randomizeContext,
+    createContext,
+    cloneContext,
+    destroyContext,
+
+    -- * Messages
+    Msg (..),
     msg,
-    getMsg,
 
     -- * Secret Keys
-    SecKey,
+    SecKey (..),
     secKey,
-    getSecKey,
     derivePubKey,
 
     -- * Public Keys
-    PubKey,
+    PubKey (..),
+    pubKey,
     importPubKey,
     exportPubKey,
 
     -- * Signatures
-    Sig,
+    Sig (..),
+    sig,
     signMsg,
     verifySig,
     normalizeSig,
@@ -40,16 +51,14 @@
     exportSig,
 
     -- ** Compact
-    CompactSig,
-    getCompactSig,
+    CompactSig (..),
     compactSig,
     exportCompactSig,
     importCompactSig,
 
     -- * Addition & Multiplication
-    Tweak,
+    Tweak (..),
     tweak,
-    getTweak,
     tweakAddSecKey,
     tweakMulSecKey,
     tweakAddPubKey,
@@ -60,6 +69,7 @@
 where
 
 import Control.DeepSeq (NFData)
+import Control.Exception (bracket)
 import Control.Monad (replicateM, unless, (<=<))
 import Crypto.Secp256k1.Internal
 import Data.Base16.Types (assertBase16, extractBase16)
@@ -68,15 +78,12 @@
 import Data.ByteString.Base16 (decodeBase16, encodeBase16, isBase16)
 import Data.Hashable (Hashable (..))
 import Data.Maybe (fromJust, fromMaybe, isJust)
-import Data.Serialize
-  ( Serialize (..),
-    getByteString,
-    putByteString,
-  )
 import Data.String (IsString (..))
 import Data.String.Conversions (ConvertibleStrings, cs)
 import Foreign
-  ( alloca,
+  ( Bits (bitSize),
+    Ptr,
+    alloca,
     allocaArray,
     allocaBytes,
     free,
@@ -102,47 +109,25 @@
     readPrec,
   )
 
-newtype PubKey = PubKey {getPubKey :: ByteString}
-  deriving (Eq, Generic, NFData)
-
-newtype Msg = Msg {getMsg :: ByteString}
-  deriving (Eq, Generic, NFData)
-
-newtype Sig = Sig {getSig :: ByteString}
-  deriving (Eq, Generic, NFData)
-
-newtype SecKey = SecKey {getSecKey :: ByteString}
-  deriving (Eq, Generic, NFData)
-
-newtype Tweak = Tweak {getTweak :: ByteString}
-  deriving (Eq, Generic, NFData)
-
-newtype CompactSig = CompactSig {getCompactSig :: ByteString}
-  deriving (Eq, Generic, NFData)
+newtype Ctx = Ctx {get :: Ptr LCtx}
 
-instance Serialize PubKey where
-  put (PubKey bs) = putByteString bs
-  get = PubKey <$> getByteString 64
+newtype PubKey = PubKey {get :: ByteString}
+  deriving (Eq, Generic, Hashable, NFData)
 
-instance Serialize Msg where
-  put (Msg m) = putByteString m
-  get = Msg <$> getByteString 32
+newtype Msg = Msg {get :: ByteString}
+  deriving (Eq, Generic, Hashable, NFData)
 
-instance Serialize Sig where
-  put (Sig bs) = putByteString bs
-  get = Sig <$> getByteString 64
+newtype Sig = Sig {get :: ByteString}
+  deriving (Eq, Generic, Hashable, NFData)
 
-instance Serialize SecKey where
-  put (SecKey bs) = putByteString bs
-  get = SecKey <$> getByteString 32
+newtype SecKey = SecKey {get :: ByteString}
+  deriving (Eq, Generic, Hashable, NFData)
 
-instance Serialize Tweak where
-  put (Tweak bs) = putByteString bs
-  get = Tweak <$> getByteString 32
+newtype Tweak = Tweak {get :: ByteString}
+  deriving (Eq, Generic, Hashable, NFData)
 
-instance Serialize CompactSig where
-  put (CompactSig bs) = putByteString bs
-  get = CompactSig <$> getByteString 64
+newtype CompactSig = CompactSig {get :: ByteString}
+  deriving (Eq, Generic, Hashable, NFData)
 
 decodeHex :: (ConvertibleStrings a ByteString) => a -> Maybe ByteString
 decodeHex str =
@@ -151,71 +136,56 @@
     else Nothing
 
 instance Read PubKey where
-  readPrec = do
+  readPrec = parens $ do
     String str <- lexP
-    maybe pfail return $ importPubKey =<< decodeHex str
-
-instance Hashable PubKey where
-  i `hashWithSalt` k = i `hashWithSalt` exportPubKey True k
+    maybe pfail return $ pubKey =<< decodeHex str
 
 instance IsString PubKey where
-  fromString = fromMaybe e . (importPubKey <=< decodeHex)
+  fromString = fromMaybe e . (pubKey <=< decodeHex)
     where
       e = error "Could not decode public key from hex string"
 
 instance Show PubKey where
-  showsPrec _ = shows . extractBase16 . encodeBase16 . exportPubKey True
+  showsPrec _ = shows . extractBase16 . encodeBase16 . (.get)
 
 instance Read Msg where
   readPrec = parens $ do
     String str <- lexP
     maybe pfail return $ msg =<< decodeHex str
 
-instance Hashable Msg where
-  i `hashWithSalt` m = i `hashWithSalt` getMsg m
-
 instance IsString Msg where
   fromString = fromMaybe e . (msg <=< decodeHex)
     where
       e = error "Could not decode message from hex string"
 
-instance Show Msg where
-  showsPrec _ = shows . extractBase16 . encodeBase16 . getMsg
+instance Show Sig where
+  showsPrec _ = shows . extractBase16 . encodeBase16 . (.get)
 
 instance Read Sig where
   readPrec = parens $ do
     String str <- lexP
-    maybe pfail return $ importSig =<< decodeHex str
+    maybe pfail return $ sig =<< decodeHex str
 
 instance IsString Sig where
-  fromString = fromMaybe e . (importSig <=< decodeHex)
+  fromString = fromMaybe e . (sig <=< decodeHex)
     where
       e = error "Could not decode signature from hex string"
 
-instance Hashable Sig where
-  i `hashWithSalt` s = i `hashWithSalt` exportSig s
-
-instance Show Sig where
-  showsPrec _ = shows . extractBase16 . encodeBase16 . exportSig
+instance Show Msg where
+  showsPrec _ = shows . extractBase16 . encodeBase16 . (.get)
 
 instance Read SecKey where
   readPrec = parens $ do
     String str <- lexP
     maybe pfail return $ secKey =<< decodeHex str
 
-instance Hashable SecKey where
-  i `hashWithSalt` k = i `hashWithSalt` getSecKey k
-
 instance IsString SecKey where
   fromString = fromMaybe e . (secKey <=< decodeHex)
     where
       e = error "Colud not decode secret key from hex string"
 
 instance Show SecKey where
-  showsPrec _ = shows . extractBase16 . encodeBase16 . getSecKey
-
-instance Hashable Tweak where
-  i `hashWithSalt` t = i `hashWithSalt` getTweak t
+  showsPrec _ = shows . extractBase16 . encodeBase16 . (.get)
 
 instance Read Tweak where
   readPrec = parens $ do
@@ -228,8 +198,41 @@
       e = error "Could not decode tweak from hex string"
 
 instance Show Tweak where
-  showsPrec _ = shows . extractBase16 . encodeBase16 . getTweak
+  showsPrec _ = shows . extractBase16 . encodeBase16 . (.get)
 
+randomizeContext :: Ctx -> IO ()
+randomizeContext (Ctx ctx) = do
+  ret <- withRandomSeed $ contextRandomize ctx
+  unless (isSuccess ret) $ error "Could not randomize context"
+
+createContext :: IO Ctx
+createContext = Ctx <$> contextCreate signVerify
+
+cloneContext :: Ctx -> IO Ctx
+cloneContext = fmap Ctx . contextClone . (.get)
+
+destroyContext :: Ctx -> IO ()
+destroyContext = contextDestroy . (.get)
+
+withContext :: (Ctx -> IO a) -> IO a
+withContext = bracket create destroy
+  where
+    create = do
+      ctx <- createContext
+      randomizeContext ctx
+      return ctx
+    destroy = destroyContext
+
+sig :: ByteString -> Maybe Sig
+sig bs
+  | BS.length bs == 64 = Just (Sig bs)
+  | otherwise = Nothing
+
+pubKey :: ByteString -> Maybe PubKey
+pubKey bs
+  | BS.length bs == 64 = Just (PubKey bs)
+  | otherwise = Nothing
+
 -- | Import 32-byte 'ByteString' as 'Msg'.
 msg :: ByteString -> Maybe Msg
 msg bs
@@ -249,8 +252,8 @@
 
 -- | Convert signature to a normalized lower-S form. 'Nothing' indicates that it
 -- was already normal.
-normalizeSig :: Sig -> Maybe Sig
-normalizeSig (Sig sig) = unsafePerformIO $
+normalizeSig :: Ctx -> Sig -> Maybe Sig
+normalizeSig (Ctx ctx) (Sig sig) = unsafePerformIO $
   unsafeUseByteString sig $ \(sig_in, _) -> do
     sig_out <- mallocBytes 64
     ret <- ecdsaSignatureNormalize ctx sig_out sig_in
@@ -269,8 +272,8 @@
   | otherwise = Nothing
 
 -- | Import DER-encoded public key.
-importPubKey :: ByteString -> Maybe PubKey
-importPubKey bs
+importPubKey :: Ctx -> ByteString -> Maybe PubKey
+importPubKey (Ctx ctx) bs
   | BS.null bs = Nothing
   | otherwise = unsafePerformIO $
       unsafeUseByteString bs $ \(input, len) -> do
@@ -285,8 +288,8 @@
             return Nothing
 
 -- | Encode public key as DER. First argument 'True' for compressed output.
-exportPubKey :: Bool -> PubKey -> ByteString
-exportPubKey compress (PubKey in_bs) = unsafePerformIO $
+exportPubKey :: Ctx -> Bool -> PubKey -> ByteString
+exportPubKey (Ctx ctx) compress (PubKey in_bs) = unsafePerformIO $
   unsafeUseByteString in_bs $ \(in_ptr, _) ->
     alloca $ \len_ptr ->
       allocaBytes len $ \out_ptr -> do
@@ -299,8 +302,8 @@
     len = if compress then 33 else 65
     flags = if compress then compressed else uncompressed
 
-exportCompactSig :: Sig -> CompactSig
-exportCompactSig (Sig sig_bs) = unsafePerformIO $
+exportCompactSig :: Ctx -> Sig -> CompactSig
+exportCompactSig (Ctx ctx) (Sig sig_bs) = unsafePerformIO $
   unsafeUseByteString sig_bs $ \(sig_ptr, _) -> do
     out_ptr <- mallocBytes 64
     ret <- ecdsaSignatureSerializeCompact ctx out_ptr sig_ptr
@@ -310,8 +313,8 @@
     out_bs <- unsafePackByteString (out_ptr, 64)
     return $ CompactSig out_bs
 
-importCompactSig :: CompactSig -> Maybe Sig
-importCompactSig (CompactSig compact_sig) = unsafePerformIO $
+importCompactSig :: Ctx -> CompactSig -> Maybe Sig
+importCompactSig (Ctx ctx) (CompactSig compact_sig) = unsafePerformIO $
   unsafeUseByteString compact_sig $ \(compact_ptr, _) -> do
     out_sig <- mallocBytes 64
     ret <- ecdsaSignatureParseCompact ctx out_sig compact_ptr
@@ -324,8 +327,8 @@
         return Nothing
 
 -- | Import DER-encoded signature.
-importSig :: ByteString -> Maybe Sig
-importSig bs
+importSig :: Ctx -> ByteString -> Maybe Sig
+importSig (Ctx ctx) bs
   | BS.null bs = Nothing
   | otherwise = unsafePerformIO $
       unsafeUseByteString bs $ \(in_ptr, in_len) -> do
@@ -340,8 +343,8 @@
             return Nothing
 
 -- | Encode signature as strict DER.
-exportSig :: Sig -> ByteString
-exportSig (Sig in_sig) = unsafePerformIO $
+exportSig :: Ctx -> Sig -> ByteString
+exportSig (Ctx ctx) (Sig in_sig) = unsafePerformIO $
   unsafeUseByteString in_sig $ \(in_ptr, _) ->
     alloca $ \out_len ->
       allocaBytes 72 $ \out_ptr -> do
@@ -352,15 +355,15 @@
         packByteString (out_ptr, final_len)
 
 -- | Verify message signature. 'True' means that the signature is correct.
-verifySig :: PubKey -> Sig -> Msg -> Bool
-verifySig (PubKey pub_key) (Sig sig) (Msg m) = unsafePerformIO $
+verifySig :: Ctx -> PubKey -> Sig -> Msg -> Bool
+verifySig (Ctx ctx) (PubKey pub_key) (Sig sig) (Msg m) = unsafePerformIO $
   unsafeUseByteString pub_key $ \(pub_key_ptr, _) ->
     unsafeUseByteString sig $ \(sig_ptr, _) ->
       unsafeUseByteString m $ \(msg_ptr, _) ->
         isSuccess <$> ecdsaVerify ctx sig_ptr msg_ptr pub_key_ptr
 
-signMsg :: SecKey -> Msg -> Sig
-signMsg (SecKey sec_key) (Msg m) = unsafePerformIO $
+signMsg :: Ctx -> SecKey -> Msg -> Sig
+signMsg (Ctx ctx) (SecKey sec_key) (Msg m) = unsafePerformIO $
   unsafeUseByteString sec_key $ \(sec_key_ptr, _) ->
     unsafeUseByteString m $ \(msg_ptr, _) -> do
       sig_ptr <- mallocBytes 64
@@ -370,8 +373,8 @@
         error "could not sign message"
       Sig <$> unsafePackByteString (sig_ptr, 64)
 
-derivePubKey :: SecKey -> PubKey
-derivePubKey (SecKey sec_key) = unsafePerformIO $
+derivePubKey :: Ctx -> SecKey -> PubKey
+derivePubKey (Ctx ctx) (SecKey sec_key) = unsafePerformIO $
   unsafeUseByteString sec_key $ \(sec_key_ptr, _) -> do
     pub_key_ptr <- mallocBytes 64
     ret <- ecPubKeyCreate ctx pub_key_ptr sec_key_ptr
@@ -381,8 +384,8 @@
     PubKey <$> unsafePackByteString (pub_key_ptr, 64)
 
 -- | Add tweak to secret key.
-tweakAddSecKey :: SecKey -> Tweak -> Maybe SecKey
-tweakAddSecKey (SecKey sec_key) (Tweak t) = unsafePerformIO $
+tweakAddSecKey :: Ctx -> SecKey -> Tweak -> Maybe SecKey
+tweakAddSecKey (Ctx ctx) (SecKey sec_key) (Tweak t) = unsafePerformIO $
   unsafeUseByteString new_bs $ \(sec_key_ptr, _) ->
     unsafeUseByteString t $ \(tweak_ptr, _) -> do
       ret <- ecSecKeyTweakAdd ctx sec_key_ptr tweak_ptr
@@ -393,8 +396,8 @@
     new_bs = BS.copy sec_key
 
 -- | Multiply secret key by tweak.
-tweakMulSecKey :: SecKey -> Tweak -> Maybe SecKey
-tweakMulSecKey (SecKey sec_key) (Tweak t) = unsafePerformIO $
+tweakMulSecKey :: Ctx -> SecKey -> Tweak -> Maybe SecKey
+tweakMulSecKey (Ctx ctx) (SecKey sec_key) (Tweak t) = unsafePerformIO $
   unsafeUseByteString new_bs $ \(sec_key_ptr, _) ->
     unsafeUseByteString t $ \(tweak_ptr, _) -> do
       ret <- ecSecKeyTweakMul ctx sec_key_ptr tweak_ptr
@@ -405,8 +408,8 @@
     new_bs = BS.copy sec_key
 
 -- | Add tweak to public key. Tweak is multiplied first by G to obtain a point.
-tweakAddPubKey :: PubKey -> Tweak -> Maybe PubKey
-tweakAddPubKey (PubKey pub_key) (Tweak t) = unsafePerformIO $
+tweakAddPubKey :: Ctx -> PubKey -> Tweak -> Maybe PubKey
+tweakAddPubKey (Ctx ctx) (PubKey pub_key) (Tweak t) = unsafePerformIO $
   unsafeUseByteString new_bs $ \(pub_key_ptr, _) ->
     unsafeUseByteString t $ \(tweak_ptr, _) -> do
       ret <- ecPubKeyTweakAdd ctx pub_key_ptr tweak_ptr
@@ -418,8 +421,8 @@
 
 -- | Multiply public key by tweak. Tweak is multiplied first by G to obtain a
 -- point.
-tweakMulPubKey :: PubKey -> Tweak -> Maybe PubKey
-tweakMulPubKey (PubKey pub_key) (Tweak t) = unsafePerformIO $
+tweakMulPubKey :: Ctx -> PubKey -> Tweak -> Maybe PubKey
+tweakMulPubKey (Ctx ctx) (PubKey pub_key) (Tweak t) = unsafePerformIO $
   unsafeUseByteString new_bs $ \(pub_key_ptr, _) ->
     unsafeUseByteString t $ \(tweak_ptr, _) -> do
       ret <- ecPubKeyTweakMul ctx pub_key_ptr tweak_ptr
@@ -430,9 +433,9 @@
     new_bs = BS.copy pub_key
 
 -- | Add multiple public keys together.
-combinePubKeys :: [PubKey] -> Maybe PubKey
-combinePubKeys [] = Nothing
-combinePubKeys pubs = unsafePerformIO $
+combinePubKeys :: Ctx -> [PubKey] -> Maybe PubKey
+combinePubKeys _ [] = Nothing
+combinePubKeys (Ctx ctx) pubs = unsafePerformIO $
   pointers [] pubs $ \ps ->
     allocaArray (length ps) $ \a -> do
       out <- mallocBytes 64
@@ -451,8 +454,8 @@
       unsafeUseByteString pub_key $ \(p, _) ->
         pointers (p : ps) pub_keys f
 
-tweakNegate :: Tweak -> Maybe Tweak
-tweakNegate (Tweak t) = unsafePerformIO $
+tweakNegate :: Ctx -> Tweak -> Maybe Tweak
+tweakNegate (Ctx ctx) (Tweak t) = unsafePerformIO $
   unsafeUseByteString new $ \(out, _) -> do
     ret <- ecTweakNegate ctx out
     if isSuccess ret
@@ -474,6 +477,3 @@
       valid_bs = bs_gen `suchThat` isJust
       bs_gen = secKey . BS.pack <$> replicateM 32 arbitraryBoundedRandom
       gen_key = fromJust <$> valid_bs
-
-instance Arbitrary PubKey where
-  arbitrary = derivePubKey <$> arbitrary
diff --git a/src/Crypto/Secp256k1/Internal.hs b/src/Crypto/Secp256k1/Internal.hs
--- a/src/Crypto/Secp256k1/Internal.hs
+++ b/src/Crypto/Secp256k1/Internal.hs
@@ -1,46 +1,60 @@
-{-|
-Module      : Crypto.Secp256k1.Internal
-License     : UNLICENSE
-Maintainer  : Jean-Pierre Rupp <jprupp@protonmail.ch>
-Stability   : experimental
-Portability : POSIX
+{-# LANGUAGE ImportQualifiedPost #-}
 
-The API for this module may change at any time. This is an internal module only
-exposed for hacking and experimentation.
--}
+-- |
+-- Module      : Crypto.Secp256k1.Internal
+-- License     : UNLICENSE
+-- Maintainer  : Jean-Pierre Rupp <jprupp@protonmail.ch>
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- The API for this module may change at any time. This is an internal module only
+-- exposed for hacking and experimentation.
 module Crypto.Secp256k1.Internal where
 
-import           Data.ByteString        (ByteString)
-import qualified Data.ByteString        as BS
-import qualified Data.ByteString.Unsafe as BU
-import           Foreign                (FunPtr, Ptr, castPtr)
-import           Foreign.C              (CInt (..), CSize (..), CString, CUChar,
-                                         CUInt (..))
-import           System.IO.Unsafe       (unsafePerformIO)
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as BS
+import Data.ByteString.Unsafe qualified as BU
+import Foreign (FunPtr, Ptr, castPtr)
+import Foreign.C
+  ( CInt (..),
+    CSize (..),
+    CString,
+    CUChar,
+    CUInt (..),
+  )
+import GHC.Generics (Selector (selDecidedStrictness))
+import System.Entropy (getEntropy)
 
 data LCtx
+
 data PubKey64
+
 data Msg32
+
 data Sig64
-data Compact64
+
 data Seed32
+
+data Compact64
+
 data SecKey32
+
 data Tweak32
 
 type CtxFlags = CUInt
+
 type SerFlags = CUInt
+
 type Ret = CInt
 
 type NonceFun a =
-    Ptr CUChar ->
-    Ptr CUChar ->
-    Ptr CUChar ->
-    Ptr CUChar ->
-    Ptr a ->
-    CInt ->
-    IO CInt
-
-type Ctx = Ptr LCtx
+  Ptr CUChar ->
+  Ptr CUChar ->
+  Ptr CUChar ->
+  Ptr CUChar ->
+  Ptr a ->
+  CInt ->
+  IO CInt
 
 verify :: CtxFlags
 verify = 0x0101
@@ -64,207 +78,203 @@
 
 unsafeUseByteString :: ByteString -> ((Ptr a, CSize) -> IO b) -> IO b
 unsafeUseByteString bs f =
-    BU.unsafeUseAsCStringLen bs $ \(b, l) ->
+  BU.unsafeUseAsCStringLen bs $ \(b, l) ->
     f (castPtr b, fromIntegral l)
 
 useByteString :: ByteString -> ((Ptr a, CSize) -> IO b) -> IO b
 useByteString bs f =
-    BS.useAsCStringLen bs $ \(b, l) ->
+  BS.useAsCStringLen bs $ \(b, l) ->
     f (castPtr b, fromIntegral l)
 
 unsafePackByteString :: (Ptr a, CSize) -> IO ByteString
 unsafePackByteString (b, l) =
-    BU.unsafePackMallocCStringLen (castPtr b, fromIntegral l)
+  BU.unsafePackMallocCStringLen (castPtr b, fromIntegral l)
 
 packByteString :: (Ptr a, CSize) -> IO ByteString
 packByteString (b, l) =
-    BS.packCStringLen (castPtr b, fromIntegral l)
-
-ctx :: Ctx
-ctx = unsafePerformIO $ contextCreate signVerify
-{-# NOINLINE ctx #-}
+  BS.packCStringLen (castPtr b, fromIntegral l)
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_context_create"
-    contextCreate
-    :: CtxFlags
-    -> IO Ctx
+withRandomSeed :: (Ptr Seed32 -> IO a) -> IO a
+withRandomSeed go = do
+  bs <- getEntropy 32
+  useByteString bs $ go . fst
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_context_clone"
-    contextClone
-    :: Ctx
-    -> IO Ctx
+foreign import ccall safe "secp256k1.h secp256k1_context_create"
+  contextCreate ::
+    CtxFlags ->
+    IO (Ptr LCtx)
 
-foreign import ccall safe
-    "secp256k1.h &secp256k1_context_destroy"
-    contextDestroy
-    :: FunPtr (Ctx -> IO ())
+foreign import ccall safe "secp256k1.h secp256k1_context_clone"
+  contextClone ::
+    Ptr LCtx ->
+    IO (Ptr LCtx)
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_context_set_illegal_callback"
-    setIllegalCallback
-    :: Ctx
-    -> FunPtr (CString -> Ptr a -> IO ()) -- ^ message, data
-    -> Ptr a                              -- ^ data
-    -> IO ()
+foreign import ccall safe "secp256k1.h secp256k1_context_destroy"
+  contextDestroy ::
+    Ptr LCtx ->
+    IO ()
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_context_set_error_callback"
-    setErrorCallback
-    :: Ctx
-    -> FunPtr (CString -> Ptr a -> IO ()) -- ^ message, data
-    -> Ptr a                              -- ^ data
-    -> IO ()
+foreign import ccall safe "secp256k1.h secp256k1_context_set_illegal_callback"
+  setIllegalCallback ::
+    Ptr LCtx ->
+    -- | message, data
+    FunPtr (CString -> Ptr a -> IO ()) ->
+    -- | data
+    Ptr a ->
+    IO ()
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_ec_pubkey_parse"
-    ecPubKeyParse
-    :: Ctx
-    -> Ptr PubKey64
-    -> Ptr CUChar -- ^ encoded public key array
-    -> CSize      -- ^ size of encoded public key array
-    -> IO Ret
+foreign import ccall safe "secp256k1.h secp256k1_context_set_error_callback"
+  setErrorCallback ::
+    Ptr LCtx ->
+    -- | message, data
+    FunPtr (CString -> Ptr a -> IO ()) ->
+    -- | data
+    Ptr a ->
+    IO ()
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_ec_pubkey_serialize"
-    ecPubKeySerialize
-    :: Ctx
-    -> Ptr CUChar -- ^ array for encoded public key, must be large enough
-    -> Ptr CSize  -- ^ size of encoded public key, will be updated
-    -> Ptr PubKey64
-    -> SerFlags
-    -> IO Ret
+foreign import ccall safe "secp256k1.h secp256k1_ec_pubkey_parse"
+  ecPubKeyParse ::
+    Ptr LCtx ->
+    Ptr PubKey64 ->
+    -- | encoded public key array
+    Ptr CUChar ->
+    -- | size of encoded public key array
+    CSize ->
+    IO Ret
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_ecdsa_signature_parse_compact"
-    ecdsaSignatureParseCompact
-    :: Ctx
-    -> Ptr Sig64
-    -> Ptr Compact64
-    -> IO Ret
+foreign import ccall safe "secp256k1.h secp256k1_ec_pubkey_serialize"
+  ecPubKeySerialize ::
+    Ptr LCtx ->
+    -- | array for encoded public key, must be large enough
+    Ptr CUChar ->
+    -- | size of encoded public key, will be updated
+    Ptr CSize ->
+    Ptr PubKey64 ->
+    SerFlags ->
+    IO Ret
 
+foreign import ccall safe "secp256k1.h secp256k1_ecdsa_signature_parse_compact"
+  ecdsaSignatureParseCompact ::
+    Ptr LCtx ->
+    Ptr Sig64 ->
+    Ptr Compact64 ->
+    IO Ret
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_ecdsa_signature_parse_der"
-    ecdsaSignatureParseDer
-    :: Ctx
-    -> Ptr Sig64
-    -> Ptr CUChar -- ^ encoded DER signature
-    -> CSize      -- ^ size of encoded signature
-    -> IO Ret
+foreign import ccall safe "secp256k1.h secp256k1_ecdsa_signature_parse_der"
+  ecdsaSignatureParseDer ::
+    Ptr LCtx ->
+    Ptr Sig64 ->
+    -- | encoded DER signature
+    Ptr CUChar ->
+    -- | size of encoded signature
+    CSize ->
+    IO Ret
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_ecdsa_signature_serialize_der"
-    ecdsaSignatureSerializeDer
-    :: Ctx
-    -> Ptr CUChar -- ^ array for encoded signature, must be large enough
-    -> Ptr CSize  -- ^ size of encoded signature, will be updated
-    -> Ptr Sig64
-    -> IO Ret
+foreign import ccall safe "secp256k1.h secp256k1_ecdsa_signature_serialize_der"
+  ecdsaSignatureSerializeDer ::
+    Ptr LCtx ->
+    -- | array for encoded signature, must be large enough
+    Ptr CUChar ->
+    -- | size of encoded signature, will be updated
+    Ptr CSize ->
+    Ptr Sig64 ->
+    IO Ret
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_ecdsa_signature_serialize_compact"
-    ecdsaSignatureSerializeCompact
-    :: Ctx
-    -> Ptr Compact64
-    -> Ptr Sig64
-    -> IO Ret
+foreign import ccall safe "secp256k1.h secp256k1_ecdsa_signature_serialize_compact"
+  ecdsaSignatureSerializeCompact ::
+    Ptr LCtx ->
+    Ptr Compact64 ->
+    Ptr Sig64 ->
+    IO Ret
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_ecdsa_verify"
-    ecdsaVerify
-    :: Ctx
-    -> Ptr Sig64
-    -> Ptr Msg32
-    -> Ptr PubKey64
-    -> IO Ret
+foreign import ccall safe "secp256k1.h secp256k1_ecdsa_verify"
+  ecdsaVerify ::
+    Ptr LCtx ->
+    Ptr Sig64 ->
+    Ptr Msg32 ->
+    Ptr PubKey64 ->
+    IO Ret
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_ecdsa_signature_normalize"
-    ecdsaSignatureNormalize
-    :: Ctx
-    -> Ptr Sig64 -- ^ output
-    -> Ptr Sig64 -- ^ input
-    -> IO Ret
+foreign import ccall safe "secp256k1.h secp256k1_ecdsa_signature_normalize"
+  ecdsaSignatureNormalize ::
+    Ptr LCtx ->
+    -- | output
+    Ptr Sig64 ->
+    -- | input
+    Ptr Sig64 ->
+    IO Ret
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_ecdsa_sign"
-    ecdsaSign
-    :: Ctx
-    -> Ptr Sig64
-    -> Ptr Msg32
-    -> Ptr SecKey32
-    -> FunPtr (NonceFun a)
-    -> Ptr a -- ^ nonce data
-    -> IO Ret
+foreign import ccall safe "secp256k1.h secp256k1_ecdsa_sign"
+  ecdsaSign ::
+    Ptr LCtx ->
+    Ptr Sig64 ->
+    Ptr Msg32 ->
+    Ptr SecKey32 ->
+    FunPtr (NonceFun a) ->
+    -- | nonce data
+    Ptr a ->
+    IO Ret
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_ec_seckey_verify"
-    ecSecKeyVerify
-    :: Ctx
-    -> Ptr SecKey32
-    -> IO Ret
+foreign import ccall safe "secp256k1.h secp256k1_ec_seckey_verify"
+  ecSecKeyVerify ::
+    Ptr LCtx ->
+    Ptr SecKey32 ->
+    IO Ret
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_ec_pubkey_create"
-    ecPubKeyCreate
-    :: Ctx
-    -> Ptr PubKey64
-    -> Ptr SecKey32
-    -> IO Ret
+foreign import ccall safe "secp256k1.h secp256k1_ec_pubkey_create"
+  ecPubKeyCreate ::
+    Ptr LCtx ->
+    Ptr PubKey64 ->
+    Ptr SecKey32 ->
+    IO Ret
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_ec_privkey_tweak_add"
-    ecSecKeyTweakAdd
-    :: Ctx
-    -> Ptr SecKey32
-    -> Ptr Tweak32
-    -> IO Ret
+foreign import ccall safe "secp256k1.h secp256k1_ec_privkey_tweak_add"
+  ecSecKeyTweakAdd ::
+    Ptr LCtx ->
+    Ptr SecKey32 ->
+    Ptr Tweak32 ->
+    IO Ret
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_ec_privkey_negate"
-    ecTweakNegate
-    :: Ctx
-    -> Ptr Tweak32
-    -> IO Ret
+foreign import ccall safe "secp256k1.h secp256k1_ec_privkey_negate"
+  ecTweakNegate ::
+    Ptr LCtx ->
+    Ptr Tweak32 ->
+    IO Ret
 
-foreign import ccall unsafe
-    "secp256k1.h secp256k1_ec_pubkey_tweak_add"
-    ecPubKeyTweakAdd
-    :: Ctx
-    -> Ptr PubKey64
-    -> Ptr Tweak32
-    -> IO Ret
+foreign import ccall unsafe "secp256k1.h secp256k1_ec_pubkey_tweak_add"
+  ecPubKeyTweakAdd ::
+    Ptr LCtx ->
+    Ptr PubKey64 ->
+    Ptr Tweak32 ->
+    IO Ret
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_ec_privkey_tweak_mul"
-    ecSecKeyTweakMul
-    :: Ctx
-    -> Ptr SecKey32
-    -> Ptr Tweak32
-    -> IO Ret
+foreign import ccall safe "secp256k1.h secp256k1_ec_privkey_tweak_mul"
+  ecSecKeyTweakMul ::
+    Ptr LCtx ->
+    Ptr SecKey32 ->
+    Ptr Tweak32 ->
+    IO Ret
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_ec_pubkey_tweak_mul"
-    ecPubKeyTweakMul
-    :: Ctx
-    -> Ptr PubKey64
-    -> Ptr Tweak32
-    -> IO Ret
+foreign import ccall safe "secp256k1.h secp256k1_ec_pubkey_tweak_mul"
+  ecPubKeyTweakMul ::
+    Ptr LCtx ->
+    Ptr PubKey64 ->
+    Ptr Tweak32 ->
+    IO Ret
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_context_randomize"
-    contextRandomize
-    :: Ctx
-    -> Ptr Seed32
-    -> IO Ret
+foreign import ccall safe "secp256k1.h secp256k1_context_randomize"
+  contextRandomize ::
+    Ptr LCtx ->
+    Ptr Seed32 ->
+    IO Ret
 
-foreign import ccall safe
-    "secp256k1.h secp256k1_ec_pubkey_combine"
-    ecPubKeyCombine
-    :: Ctx
-    -> Ptr PubKey64       -- ^ pointer to public key storage
-    -> Ptr (Ptr PubKey64) -- ^ pointer to array of public keys
-    -> CInt               -- ^ number of public keys
-    -> IO Ret
+foreign import ccall safe "secp256k1.h secp256k1_ec_pubkey_combine"
+  ecPubKeyCombine ::
+    Ptr LCtx ->
+    -- | pointer to public key storage
+    Ptr PubKey64 ->
+    -- | pointer to array of public keys
+    Ptr (Ptr PubKey64) ->
+    -- | number of public keys
+    CInt ->
+    IO Ret
diff --git a/test/Crypto/Secp256k1/InternalSpec.hs b/test/Crypto/Secp256k1/InternalSpec.hs
--- a/test/Crypto/Secp256k1/InternalSpec.hs
+++ b/test/Crypto/Secp256k1/InternalSpec.hs
@@ -2,6 +2,7 @@
 
 module Crypto.Secp256k1.InternalSpec (spec) where
 
+import Control.Exception
 import Control.Monad
 import Control.Monad.Trans
 import Crypto.Secp256k1.Internal
@@ -45,22 +46,21 @@
     it "multiplies public key" ecPubKeyTweakMulTest
     it "combines public keys" ecPubKeyCombineTest
 
+withCtxPtr :: CtxFlags -> (Ptr LCtx -> IO a) -> IO a
+withCtxPtr f = bracket (contextCreate f) contextDestroy
+
 withEntropy :: (Ptr Seed32 -> IO a) -> IO a
 withEntropy f =
   getEntropy 32 >>= \e ->
     useByteString e $ \(s, _) -> f s
 
 createContextTest :: Assertion
-createContextTest = do
-  context_ptr <- liftIO $ contextCreate signVerify
-  assertBool "context not null" $ context_ptr /= nullPtr
+createContextTest = withCtxPtr signVerify $ \ctx -> do
+  assertBool "context not null" $ ctx /= nullPtr
 
 randomizeContextTest :: Assertion
-randomizeContextTest = do
-  ret <-
-    liftIO $
-      contextCreate sign >>= \x ->
-        withEntropy (contextRandomize x)
+randomizeContextTest = withCtxPtr sign $ \ctx -> do
+  ret <- withEntropy (contextRandomize ctx)
   assertBool "context randomized" $ isSuccess ret
 
 cloneContextTest :: Assertion
@@ -125,7 +125,7 @@
           \fb2202206f0415ab0e9a977afd78b2c26ef39b3952096d319fd4b101c768ad6c132e30\
           \45"
 
-parseDer :: Ctx -> ByteString -> IO ByteString
+parseDer :: Ptr LCtx -> ByteString -> IO ByteString
 parseDer x bs =
   useAsCStringLen bs $ \(d, dl) ->
     allocaBytes 64 $ \s -> do
@@ -135,10 +135,9 @@
 
 ecdsaSignatureSerializeDerTest :: Assertion
 ecdsaSignatureSerializeDerTest = do
-  (ret, enc) <- liftIO $ do
-    x <- contextCreate verify
+  (ret, enc) <- liftIO . bracket (contextCreate verify) contextDestroy $ \x -> do
     sig <- parseDer x der
-    alloca $ \ol ->
+    (ret, enc) <- alloca $ \ol ->
       allocaBytes 72 $ \o ->
         useByteString sig $ \(s, _) -> do
           poke ol 72
@@ -146,6 +145,7 @@
           len <- fromIntegral <$> peek ol
           enc <- packCStringLen (castPtr o, len)
           return (ret, enc)
+    return (ret, enc)
   assertBool "serialization successful" $ isSuccess ret
   assertEqual "signatures match" der enc
   where
@@ -157,19 +157,18 @@
           \45"
 
 ecdsaVerifyTest :: Assertion
-ecdsaVerifyTest = do
+ecdsaVerifyTest = withCtxPtr verify $ \ctx -> do
   ret <- liftIO $ do
-    x <- contextCreate verify
-    sig <- parseDer x der
+    sig <- parseDer ctx der
     pk <- useByteString pub $ \(p, pl) ->
       allocaBytes 64 $ \k -> do
-        ret <- ecPubKeyParse x k p (fromIntegral pl)
+        ret <- ecPubKeyParse ctx k p (fromIntegral pl)
         unless (isSuccess ret) $ error "could not parse public key"
         packByteString (k, 64)
     useByteString msg $ \(m, _) ->
       useByteString pk $ \(k, _) ->
         useByteString sig $ \(s, _) ->
-          ecdsaVerify x s m k
+          ecdsaVerify ctx s m k
   assertBool "signature valid" $ isSuccess ret
   where
     der =
@@ -188,44 +187,44 @@
         assertBase16
           "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
 
-signCtx :: IO Ctx
+signCtx :: IO (Ptr LCtx)
 signCtx =
   contextCreate sign >>= \c ->
     withEntropy (contextRandomize c) >>= \r ->
       unless (isSuccess r) (error "failed to randomize context") >> return c
 
-createPubKey :: Ctx -> Ptr SecKey32 -> Ptr PubKey64 -> IO ()
-createPubKey x k p = do
-  ret <- ecPubKeyCreate x p k
+withSignCtxPtr :: (Ptr LCtx -> IO a) -> IO a
+withSignCtxPtr = bracket signCtx contextDestroy
+
+createPubKey :: Ptr LCtx -> Ptr SecKey32 -> Ptr PubKey64 -> IO ()
+createPubKey ctx k p = do
+  ret <- ecPubKeyCreate ctx p k
   unless (isSuccess ret) $ error "failed to create public key"
 
 ecdsaSignTest :: Assertion
 ecdsaSignTest = do
-  der <- liftIO $ do
-    x <- signCtx
+  der <- liftIO $ withSignCtxPtr $ \ctx -> do
     allocaBytes 64 $ \s ->
       useByteString msg $ \(m, _) ->
         useByteString key $ \(k, _) ->
           alloca $ \ol ->
             allocaBytes 72 $ \o -> do
               poke ol 72
-              ret1 <- ecdsaSign x s m k nullFunPtr nullPtr
+              ret1 <- ecdsaSign ctx s m k nullFunPtr nullPtr
               unless (isSuccess ret1) $ error "could not sign message"
-              ret2 <- ecdsaSignatureSerializeDer x o ol s
+              ret2 <- ecdsaSignatureSerializeDer ctx o ol s
               unless (isSuccess ret2) $ error "could not serialize signature"
               len <- peek ol
               packCStringLen (castPtr o, fromIntegral len)
   ret <- liftIO $ do
-    pub <- allocaBytes 64 $ \p ->
+    pub <- withSignCtxPtr $ \ctx -> allocaBytes 64 $ \p ->
       useByteString key $ \(s, _) -> do
-        x <- signCtx
-        createPubKey x s p
+        createPubKey ctx s p
         packByteString (p, 64)
-    useByteString msg $ \(m, _) ->
+    withCtxPtr verify $ \ctx -> useByteString msg $ \(m, _) ->
       useByteString pub $ \(p, _) -> do
-        x <- contextCreate verify
-        s' <- parseDer x der
-        useByteString s' $ \(s, _) -> ecdsaVerify x s m p
+        s' <- parseDer ctx der
+        useByteString s' $ \(s, _) -> ecdsaVerify ctx s m p
   assertBool "signature matches" (isSuccess ret)
   where
     msg =
@@ -238,10 +237,9 @@
           "f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
 
 ecSecKeyVerifyTest :: Assertion
-ecSecKeyVerifyTest = do
+ecSecKeyVerifyTest = withSignCtxPtr $ \ctx -> do
   ret <- liftIO $ useByteString key $ \(k, _) -> do
-    x <- signCtx
-    ecSecKeyVerify x k
+    ecSecKeyVerify ctx k
   assertBool "valid secret key" $ isSuccess ret
   where
     key =
@@ -250,16 +248,15 @@
           "f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
 
 ecPubkeyCreateTest :: Assertion
-ecPubkeyCreateTest = do
+ecPubkeyCreateTest = withSignCtxPtr $ \ctx -> do
   pk <- liftIO $
     useByteString key $ \(s, _) ->
       allocaBytes 64 $ \k -> do
-        x <- signCtx
-        createPubKey x s k
+        createPubKey ctx s k
         allocaBytes 65 $ \o ->
           alloca $ \ol -> do
             poke ol 65
-            rets <- ecPubKeySerialize x o ol k uncompressed
+            rets <- ecPubKeySerialize ctx o ol k uncompressed
             unless (isSuccess rets) $ error "failed to serialize public key"
             len <- fromIntegral <$> peek ol
             packCStringLen (castPtr o, len)
@@ -278,13 +275,12 @@
 ecSecKeyTweakAddTest :: Assertion
 ecSecKeyTweakAddTest = do
   (ret, tweaked) <-
-    liftIO $
-      signCtx >>= \x ->
-        useByteString tweak $ \(w, _) ->
-          useByteString key $ \(k, _) -> do
-            ret <- ecSecKeyTweakAdd x k w
-            tweaked <- packByteString (k, 32)
-            return (ret, tweaked)
+    liftIO . withSignCtxPtr $ \ctx ->
+      useByteString tweak $ \(w, _) ->
+        useByteString key $ \(k, _) -> do
+          ret <- ecSecKeyTweakAdd ctx k w
+          tweaked <- packByteString (k, 32)
+          return (ret, tweaked)
   assertBool "successful secret key tweak" $ isSuccess ret
   assertEqual "tweaked keys match" expected tweaked
   where
@@ -303,12 +299,9 @@
 
 ecSecKeyTweakMulTest :: Assertion
 ecSecKeyTweakMulTest = do
-  (ret, tweaked) <- liftIO $ do
-    x <- contextCreate sign
-    retr <- withEntropy $ contextRandomize x
-    unless (isSuccess retr) $ error "failed to randomize context"
+  (ret, tweaked) <- liftIO $ withSignCtxPtr $ \ctx -> do
     useByteString tweak $ \(w, _) -> useByteString key $ \(k, _) -> do
-      ret <- ecSecKeyTweakMul x k w
+      ret <- ecSecKeyTweakMul ctx k w
       tweaked <- packByteString (k, 32)
       return (ret, tweaked)
   assertBool "successful secret key tweak" $ isSuccess ret
@@ -327,7 +320,7 @@
         assertBase16
           "a96f5962493acb179f60a86a9785fc7a30e0c39b64c09d24fe064d9aef15e4c0"
 
-serializeKey :: Ctx -> Ptr PubKey64 -> IO ByteString
+serializeKey :: Ptr LCtx -> Ptr PubKey64 -> IO ByteString
 serializeKey x p = allocaBytes 72 $ \d -> alloca $ \dl -> do
   poke dl 72
   ret <- ecPubKeySerialize x d dl p uncompressed
@@ -335,7 +328,7 @@
   len <- peek dl
   packCStringLen (castPtr d, fromIntegral len)
 
-parseKey :: Ctx -> ByteString -> IO ByteString
+parseKey :: Ptr LCtx -> ByteString -> IO ByteString
 parseKey x bs =
   allocaBytes 64 $ \p ->
     useByteString bs $ \(d, dl) -> do
diff --git a/test/Crypto/Secp256k1Spec.hs b/test/Crypto/Secp256k1Spec.hs
--- a/test/Crypto/Secp256k1Spec.hs
+++ b/test/Crypto/Secp256k1Spec.hs
@@ -1,179 +1,179 @@
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+
 module Crypto.Secp256k1Spec (spec) where
 
-import Control.Monad.Par
-import qualified Control.Monad.Par as P
+import Control.Arrow (first)
+import Control.Monad.Par qualified as P
 import Crypto.Secp256k1
 import Data.Base16.Types (assertBase16, extractBase16)
-import qualified Data.ByteString as BS
+import Data.ByteString qualified as BS
 import Data.ByteString.Base16 (decodeBase16, encodeBase16)
-import qualified Data.ByteString.Char8 as B8
+import Data.ByteString.Char8 qualified as B8
 import Data.Either (fromRight)
-import Data.Maybe (fromMaybe, isNothing)
+import Data.Maybe (fromJust, fromMaybe, isJust, isNothing)
 import Data.String (fromString)
 import Data.String.Conversions (cs)
-import Test.HUnit (Assertion, assertEqual)
+import Test.HUnit (Assertion, assertBool, assertEqual)
 import Test.Hspec
 import Test.QuickCheck
 
 spec :: Spec
-spec = do
+spec = around withContext $ do
   describe "signatures" $ do
-    it "signs message" $
-      property signMsgTest
-    it "signs messages in parallel" $
-      property signMsgParTest
-    it "detects bad signature" $
-      property badSignatureTest
-    it "normalizes signatures" $
-      property normalizeSigTest
+    it "signs message" $ \ctx ->
+      property $ signMsgTest ctx
+    it "signs messages in parallel" $ \ctx ->
+      property $ signMsgParTest ctx
+    it "detects bad signature" $ \ctx ->
+      property $ badSignatureTest ctx
+    it "normalizes signatures" $ \ctx ->
+      property $ normalizeSigTest ctx
   describe "serialization" $ do
-    it "serializes public key" $
-      property serializePubKeyTest
-    it "serializes public keys in parallel" $
-      property parSerializePubKeyTest
-    it "serializes DER signature" $
-      property serializeSigTest
-    it "serializes DER signatures in parallel" $
-      property parSerializeSigTest
-    it "serializes compact signature" $
-      property serializeCompactSigTest
-    it "serialize secret key" $
+    it "serializes public key" $ \ctx ->
+      property $ serializePubKeyTest ctx
+    it "serializes public keys in parallel" $ \ctx ->
+      property $ parSerializePubKeyTest ctx
+    it "serializes DER signature" $ \ctx ->
+      property $ serializeSigTest ctx
+    it "serializes DER signatures in parallel" $ \ctx ->
+      property $ parSerializeSigTest ctx
+    it "serializes compact signature" $ \ctx ->
+      property $ serializeCompactSigTest ctx
+    it "serialize secret key" $ \_ ->
       property serializeSecKeyTest
-    it "shows and reads public key" $
-      property (showRead :: PubKey -> Bool)
-    it "shows and reads secret key" $
+    it "shows and reads secret key" $ \_ ->
       property (showRead :: SecKey -> Bool)
-    it "shows and reads tweak" $
-      property (showReadTweak :: SecKey -> Bool)
-    it "shows and reads signature" $
-      property (showReadSig :: (SecKey, Msg) -> Bool)
-    it "shows and reads message" $
+    it "shows and reads tweak" $ \_ ->
+      property showReadTweak
+    it "shows and reads message" $ \_ ->
       property (showRead :: Msg -> Bool)
-    it "reads public key from string" $
-      property isStringPubKey
-    it "reads secret key from string" $
+    it "shows and reads public key" $ \ctx ->
+      property $ showReadPubKey ctx
+    it "reads secret key from string" $ \_ ->
       property isStringSecKey
-    it "reads signature from string" $
-      property isStringSig
-    it "reads message from string" $
+    it "reads message from string" $ \_ ->
       property isStringMsg
-    it "reads tweak from string" $
+    it "reads tweak from string" $ \_ ->
       property isStringTweak
   describe "tweaks" $ do
-    it "add secret key" $ property tweakAddSecKeyTest
-    it "multiply secret key" $ property tweakMulSecKeyTest
-    it "add public key" $ property tweakAddPubKeyTest
-    it "multiply public key" $ property tweakMulPubKeyTest
-    it "combine public keys" $ property combinePubKeyTest
-    it "can't combine 0 public keys" $ property combinePubKeyEmptyListTest
-    it "negates tweak" $ property negateTweakTest
+    it "add secret key" $ \ctx ->
+      property $ tweakAddSecKeyTest ctx
+    it "multiply secret key" $ \ctx ->
+      property $ tweakMulSecKeyTest ctx
+    it "add public key" $ \ctx ->
+      property $ tweakAddPubKeyTest ctx
+    it "multiply public key" $ \ctx ->
+      property $ tweakMulPubKeyTest ctx
+    it "combine public keys" $ \ctx ->
+      property $ combinePubKeyTest ctx
+    it "can't combine 0 public keys" $ \ctx ->
+      property $ combinePubKeyEmptyListTest ctx
+    it "negates tweak" $ \ctx ->
+      property $ negateTweakTest ctx
 
 hexToBytes :: String -> BS.ByteString
 hexToBytes = decodeBase16 . assertBase16 . B8.pack
 
-isStringPubKey :: (PubKey, Bool) -> Bool
-isStringPubKey (k, c) = k == fromString (cs hex)
-  where
-    hex = extractBase16 . encodeBase16 $ exportPubKey c k
-
-isStringSig :: (SecKey, Msg) -> Bool
-isStringSig (k, m) = g == fromString (cs hex)
-  where
-    g = signMsg k m
-    hex = extractBase16 . encodeBase16 $ exportSig g
-
 isStringMsg :: Msg -> Bool
 isStringMsg m = m == fromString (cs m')
   where
-    m' = extractBase16 . encodeBase16 $ getMsg m
+    m' = (extractBase16 . encodeBase16) m.get
 
 isStringSecKey :: SecKey -> Bool
 isStringSecKey k = k == fromString (cs hex)
   where
-    hex = extractBase16 . encodeBase16 $ getSecKey k
+    hex = (extractBase16 . encodeBase16) k.get
 
 isStringTweak :: SecKey -> Bool
 isStringTweak k = t == fromString (cs hex)
   where
-    t = fromMaybe e . tweak $ getSecKey k
-    hex = extractBase16 . encodeBase16 $ getTweak t
+    t = (fromMaybe e . tweak) k.get
+    hex = (extractBase16 . encodeBase16) t.get
     e = error "Could not extract tweak from secret key"
 
 showReadTweak :: SecKey -> Bool
 showReadTweak k = showRead t
   where
-    t = tweak $ getSecKey k
+    t = tweak k.get
 
-showReadSig :: (SecKey, Msg) -> Bool
-showReadSig (k, m) = showRead sig
+showReadPubKey :: Ctx -> SecKey -> Bool
+showReadPubKey ctx k =
+  (read . show) p == p
   where
-    sig = signMsg k m
+    p = derivePubKey ctx k
 
 showRead :: (Show a, Read a, Eq a) => a -> Bool
 showRead x = read (show x) == x
 
-signMsgTest :: (Msg, SecKey) -> Bool
-signMsgTest (fm, fk) = verifySig fp fg fm
+signMsgTest :: Ctx -> (Msg, SecKey) -> Bool
+signMsgTest ctx (fm, fk) = verifySig ctx fp fg fm
   where
-    fp = derivePubKey fk
-    fg = signMsg fk fm
+    fp = derivePubKey ctx fk
+    fg = signMsg ctx fk fm
 
-signMsgParTest :: [(Msg, SecKey)] -> Bool
-signMsgParTest xs = P.runPar $ do
-  ys <- mapM (P.spawnP . signMsgTest) xs
+signMsgParTest :: Ctx -> [(Msg, SecKey)] -> Bool
+signMsgParTest ctx xs = P.runPar $ do
+  ys <- mapM (P.spawnP . signMsgTest ctx) xs
   and <$> mapM P.get ys
 
-badSignatureTest :: (Msg, SecKey, PubKey) -> Bool
-badSignatureTest (fm, fk, fp) = not $ verifySig fp fg fm
+badSignatureTest :: Ctx -> (Msg, SecKey, SecKey) -> Bool
+badSignatureTest ctx (fm, fk, fk') = not $ verifySig ctx fp fg fm
   where
-    fg = signMsg fk fm
+    fp = derivePubKey ctx fk'
+    fg = signMsg ctx fk fm
 
-normalizeSigTest :: (Msg, SecKey) -> Bool
-normalizeSigTest (fm, fk) = isNothing sig
+normalizeSigTest :: Ctx -> (Msg, SecKey) -> Bool
+normalizeSigTest ctx (fm, fk) = isNothing sig
   where
-    fg = signMsg fk fm
-    sig = normalizeSig fg
+    fg = signMsg ctx fk fm
+    sig = normalizeSig ctx fg
 
-serializePubKeyTest :: (PubKey, Bool) -> Bool
-serializePubKeyTest (fp, b) =
-  case importPubKey $ exportPubKey b fp of
+serializePubKeyTest :: Ctx -> (SecKey, Bool) -> Bool
+serializePubKeyTest ctx (fk, b) =
+  case importPubKey ctx $ exportPubKey ctx b fp of
     Just fp' -> fp == fp'
     Nothing -> False
+  where
+    fp = derivePubKey ctx fk
 
-parSerializePubKeyTest :: [(PubKey, Bool)] -> Bool
-parSerializePubKeyTest ps = runPar $ do
-  as <- mapM (spawnP . serializePubKeyTest) ps
-  and <$> mapM get as
+parSerializePubKeyTest :: Ctx -> [(SecKey, Bool)] -> Bool
+parSerializePubKeyTest ctx ks = P.runPar $ do
+  as <- mapM (P.spawnP . serializePubKeyTest ctx) ks
+  and <$> mapM P.get as
+  where
+    ps = map (first (derivePubKey ctx)) ks
 
-serializeSigTest :: (Msg, SecKey) -> Bool
-serializeSigTest (fm, fk) =
-  case importSig $ exportSig fg of
+serializeSigTest :: Ctx -> (Msg, SecKey) -> Bool
+serializeSigTest ctx (fm, fk) =
+  case importSig ctx $ exportSig ctx fg of
     Just fg' -> fg == fg'
     Nothing -> False
   where
-    fg = signMsg fk fm
+    fg = signMsg ctx fk fm
 
-parSerializeSigTest :: [(Msg, SecKey)] -> Bool
-parSerializeSigTest ms = runPar $ do
-  as <- mapM (spawnP . serializeSigTest) ms
-  and <$> mapM get as
+parSerializeSigTest :: Ctx -> [(Msg, SecKey)] -> Bool
+parSerializeSigTest ctx ms = P.runPar $ do
+  as <- mapM (P.spawnP . serializeSigTest ctx) ms
+  and <$> mapM P.get as
 
-serializeCompactSigTest :: (Msg, SecKey) -> Bool
-serializeCompactSigTest (fm, fk) =
-  case importCompactSig $ exportCompactSig fg of
+serializeCompactSigTest :: Ctx -> (Msg, SecKey) -> Bool
+serializeCompactSigTest ctx (fm, fk) =
+  case importCompactSig ctx $ exportCompactSig ctx fg of
     Just fg' -> fg == fg'
     Nothing -> False
   where
-    fg = signMsg fk fm
+    fg = signMsg ctx fk fm
 
 serializeSecKeyTest :: SecKey -> Bool
 serializeSecKeyTest fk =
-  case secKey $ getSecKey fk of
+  case secKey fk.get of
     Just fk' -> fk == fk'
     Nothing -> False
 
-tweakAddSecKeyTest :: Assertion
-tweakAddSecKeyTest =
+tweakAddSecKeyTest :: Ctx -> Assertion
+tweakAddSecKeyTest ctx =
   assertEqual "tweaked keys match" expected tweaked
   where
     tweaked = do
@@ -185,14 +185,14 @@
         tweak $
           hexToBytes
             "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
-      tweakAddSecKey key twk
+      tweakAddSecKey ctx key twk
     expected =
       secKey $
         hexToBytes
           "ec1e3ce1cefa18a671d51125e2b249688d934b0e28f5d1665384d9b02f929059"
 
-tweakMulSecKeyTest :: Assertion
-tweakMulSecKeyTest =
+tweakMulSecKeyTest :: Ctx -> Assertion
+tweakMulSecKeyTest ctx =
   assertEqual "tweaked keys match" expected tweaked
   where
     tweaked = do
@@ -204,82 +204,99 @@
         tweak $
           hexToBytes
             "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
-      tweakMulSecKey key twk
+      tweakMulSecKey ctx key twk
     expected =
       secKey $
         hexToBytes
           "a96f5962493acb179f60a86a9785fc7a30e0c39b64c09d24fe064d9aef15e4c0"
 
-tweakAddPubKeyTest :: Assertion
-tweakAddPubKeyTest =
+tweakAddPubKeyTest :: Ctx -> Assertion
+tweakAddPubKeyTest ctx = do
+  assertBool "did not fail to decode" $ isJust tweaked
+  assertBool "is not empty" $ not $ maybe False BS.null tweaked
   assertEqual "tweaked keys match" expected tweaked
   where
     tweaked = do
       pub <-
-        importPubKey $
+        importPubKey ctx $
           hexToBytes
             "04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd4470512213d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
       twk <-
         tweak $
           hexToBytes
             "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
-      tweakAddPubKey pub twk
-    expected =
-      importPubKey $
-        hexToBytes
-          "04441c3982b97576646e0df0c96736063df6b42f2ee566d13b9f6424302d1379e518fdc87a14c5435bff7a5db4552042cb4120c6b86a4bbd3d0643f3c14ad01368"
+      key <- tweakAddPubKey ctx pub twk
+      return $ exportPubKey ctx True key
+    expected = do
+      key <-
+        importPubKey ctx $
+          hexToBytes
+            "04441c3982b97576646e0df0c96736063df6b42f2ee566d13b9f6424302d1379e518fdc87a14c5435bff7a5db4552042cb4120c6b86a4bbd3d0643f3c14ad01368"
+      return $ exportPubKey ctx True key
 
-tweakMulPubKeyTest :: Assertion
-tweakMulPubKeyTest =
+tweakMulPubKeyTest :: Ctx -> Assertion
+tweakMulPubKeyTest ctx = do
+  assertBool "did not fail to decode" $ isJust tweaked
+  assertBool "is not empty" $ not $ maybe False BS.null tweaked
   assertEqual "tweaked keys match" expected tweaked
   where
     tweaked = do
       pub <-
-        importPubKey $
+        importPubKey ctx $
           hexToBytes
             "04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd4470512213d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
       twk <-
         tweak $
           hexToBytes
             "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
-      tweakMulPubKey pub twk
-    expected =
-      importPubKey $
-        hexToBytes
-          "04f379dc99cdf5c83e433defa267fbb3377d61d6b779c06a0e4ce29ae3ff5353b12ae49c9d07e7368f2ba5a446c203255ce912322991a2d6a9d5d5761c61ed1845"
+      key <- tweakMulPubKey ctx pub twk
+      return $ exportPubKey ctx True key
+    expected = do
+      key <-
+        importPubKey ctx $
+          hexToBytes
+            "04f379dc99cdf5c83e433defa267fbb3377d61d6b779c06a0e4ce29ae3ff5353b12ae49c9d07e7368f2ba5a446c203255ce912322991a2d6a9d5d5761c61ed1845"
+      return $ exportPubKey ctx True key
 
-combinePubKeyTest :: Assertion
-combinePubKeyTest =
+combinePubKeyTest :: Ctx -> Assertion
+combinePubKeyTest ctx = do
+  assertBool "did not fail to decode" $ isJust combined
+  assertBool "is not empty" $ not $ maybe False BS.null combined
   assertEqual "combined keys match" expected combined
   where
     combined = do
       pub1 <-
-        importPubKey $
+        importPubKey ctx $
           hexToBytes
             "04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd4470512213d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
       pub2 <-
-        importPubKey $
+        importPubKey ctx $
           hexToBytes
             "0487d82042d93447008dfe2af762068a1e53ff394a5bf8f68a045fa642b99ea5d153f577dd2dba6c7ae4cfd7b6622409d7edd2d76dd13a8092cd3af97b77bd2c77"
       pub3 <-
-        importPubKey $
+        importPubKey ctx $
           hexToBytes
             "049b101edcbe1ee37ff6b2318526a425b629e823d7d8d9154417880595a28000ee3febd908754b8ce4e491aa6fe488b41fb5d4bb3788e33c9ff95a7a9229166d59"
-      combinePubKeys [pub1, pub2, pub3]
-    expected =
-      importPubKey $
-        hexToBytes
-          "043d9a7ec70011efc23c33a7e62d2ea73cca87797e3b659d93bea6aa871aebde56c3bc6134ca82e324b0ab9c0e601a6d2933afe7fb5d9f3aae900f5c5dc6e362c8"
+      key <- combinePubKeys ctx [pub1, pub2, pub3]
+      return $ exportPubKey ctx True key
+    expected = do
+      key <-
+        importPubKey ctx $
+          hexToBytes
+            "043d9a7ec70011efc23c33a7e62d2ea73cca87797e3b659d93bea6aa871aebde56c3bc6134ca82e324b0ab9c0e601a6d2933afe7fb5d9f3aae900f5c5dc6e362c8"
+      return $ exportPubKey ctx True key
 
-combinePubKeyEmptyListTest :: Assertion
-combinePubKeyEmptyListTest =
+combinePubKeyEmptyListTest :: Ctx -> Assertion
+combinePubKeyEmptyListTest ctx =
   assertEqual "empty pubkey list must return Nothing" expected combined
   where
     expected = Nothing
-    combined = combinePubKeys []
+    combined = do
+      key <- combinePubKeys ctx []
+      return $ exportPubKey ctx True key
 
-negateTweakTest :: Assertion
-negateTweakTest =
+negateTweakTest :: Ctx -> Assertion
+negateTweakTest ctx =
   assertEqual "can recover secret key 1 after adding tweak 1" oneKey subtracted
   where
     Just oneKey =
@@ -290,6 +307,6 @@
       tweak . decodeBase16 . assertBase16 $
         B8.pack
           "0000000000000000000000000000000000000000000000000000000000000001"
-    Just minusOneTwk = tweakNegate oneTwk
-    Just twoKey = tweakAddSecKey oneKey oneTwk
-    Just subtracted = tweakAddSecKey twoKey minusOneTwk
+    Just minusOneTwk = tweakNegate ctx oneTwk
+    Just twoKey = tweakAddSecKey ctx oneKey oneTwk
+    Just subtracted = tweakAddSecKey ctx twoKey minusOneTwk
