packages feed

libsecp256k1-0.0.2: src/Crypto/Secp256k1.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -fno-show-valid-hole-fits #-}

-- |
-- Module      : Crypto.Secp256k1
-- License     : UNLICENSE
-- Maintainer  : Keagan McClelland <keagan.mcclelland@gmail.com>
-- Stability   : experimental
-- Portability : POSIX
--
-- Crytpographic functions from Bitcoin’s secp256k1 library.
module Crypto.Secp256k1 (
    -- * Core Types
    SecKey,
    PubKeyXY,
    PubKeyXO,
    KeyPair,
    Signature,
    RecoverableSignature,
    Tweak,

    -- * Parsing and Serialization
    importSecKey,
    importPubKeyXY,
    exportPubKeyXY,
    importPubKeyXO,
    exportPubKeyXO,
    importSignature,
    exportSignatureCompact,
    exportSignatureDer,
    importRecoverableSignature,
    exportRecoverableSignature,
    importTweak,

    -- * ECDSA Operations
    ecdsaVerify,
    ecdsaSign,
    ecdsaSignRecoverable,
    ecdsaRecover,

    -- * Conversions
    recSigToSig,
    derivePubKey,
    keyPairCreate,
    keyPairPubKeyXY,
    keyPairPubKeyXO,
    xyToXO,

    -- * Tweaks
    ecSecKeyTweakAdd,
    ecSecKeyTweakMul,
    keyPairPubKeyXOTweakAdd,
    pubKeyCombine,
    pubKeyNegate,
    secKeyNegate,
    pubKeyTweakAdd,
    pubKeyTweakMul,
    pubKeyXOTweakAdd,
    pubKeyXOTweakAddCheck,

    -- * Schnorr Operations
    schnorrSign,
    SchnorrExtra (..),
    schnorrSignCustom,
    schnorrVerify,

    -- * Other
    taggedSha256,
    ecdh,
) where

import Control.Monad (replicateM, unless, (<=<))
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Cont (ContT (..), evalContT)
import Crypto.Secp256k1.Internal
import Crypto.Secp256k1.Prim (flagsEcUncompressed)
import qualified Crypto.Secp256k1.Prim as Prim
import Data.ByteArray.Sized
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS

-- import qualified Data.ByteString.Base16 as B16
import Data.ByteString.Unsafe (unsafePackCStringLen, unsafePackMallocCStringLen)
import Data.Functor (($>))

-- import Data.Hashable (Hashable (..))
import Data.Maybe (fromJust, fromMaybe, isJust)
import Data.String (IsString (..))

-- import Data.String.Conversions (ConvertibleStrings, cs)

import Crypto.Hash (Digest, SHA256, digestFromByteString)
import Data.Foldable (for_)
import Foreign (
    Bits (..),
    ForeignPtr,
    FunPtr,
    Ptr,
    Storable,
    Word8,
    alloca,
    allocaArray,
    allocaBytes,
    castForeignPtr,
    castPtr,
    finalizerFree,
    free,
    freeHaskellFunPtr,
    malloc,
    mallocBytes,
    newForeignPtr,
    newForeignPtr_,
    nullFunPtr,
    nullPtr,
    peek,
    peekByteOff,
    peekElemOff,
    plusPtr,
    poke,
    pokeArray,
    pokeByteOff,
    pokeElemOff,
    sizeOf,
    withForeignPtr,
 )
import Foreign.C (CInt (..), CSize (..))
import GHC.Generics (Generic)
import GHC.IO.Handle.Text (memcpy)
import System.IO.Unsafe (unsafePerformIO)
import Text.Read (
    Lexeme (String),
    lexP,
    parens,
    pfail,
    readPrec,
 )


newtype SecKey = SecKey {secKeyFPtr :: ForeignPtr Prim.Seckey32}
newtype PubKeyXY = PubKeyXY {pubKeyXYFPtr :: ForeignPtr Prim.Pubkey64}
newtype PubKeyXO = PubKeyXO {pubKeyXOFPtr :: ForeignPtr Prim.XonlyPubkey64}
newtype KeyPair = KeyPair {keyPairFPtr :: ForeignPtr Prim.Keypair96}
newtype Signature = Signature {signatureFPtr :: ForeignPtr Prim.Sig64}
newtype RecoverableSignature = RecoverableSignature {recoverableSignatureFPtr :: ForeignPtr Prim.RecSig65}
newtype Tweak = Tweak {tweakFPtr :: ForeignPtr Prim.Tweak32}


-- | Preinitialized context for signing and verification
ctx :: Prim.Ctx
ctx = unsafePerformIO $ Prim.contextCreate (Prim.flagsContextSign .|. Prim.flagsContextVerify)
{-# NOINLINE ctx #-}


-- | Parses 'SecKey', will be @Nothing@ if the @ByteString@ corresponds to 0{32} or is not 32 bytes in length
importSecKey :: ByteString -> Maybe SecKey
importSecKey bs
    | BS.length bs /= 32 = Nothing
    | otherwise = unsafePerformIO $ do
        unsafeUseByteString bs $ \(ptr, len) -> do
            ret <- Prim.ecSecKeyVerify ctx ptr
            if isSuccess ret
                then do
                    newPtr <- mallocBytes 32
                    memcpy newPtr ptr 32
                    Just . SecKey <$> newForeignPtr finalizerFree newPtr
                else pure Nothing


-- | Parses a 33 or 65 byte 'PubKeyXY', all other lengths will result in @Nothing@
importPubKeyXY :: ByteString -> Maybe PubKeyXY
importPubKeyXY bs = unsafePerformIO $
    unsafeUseByteString bs $ \(input, len) -> do
        pubkeyOutputBuf <- mallocBytes 64
        if len == 33 || len == 65
            then do
                ret <- Prim.ecPubkeyParse ctx (castPtr pubkeyOutputBuf) input len
                if isSuccess ret
                    then Just . PubKeyXY <$> newForeignPtr finalizerFree (castPtr pubkeyOutputBuf)
                    else free pubkeyOutputBuf $> Nothing
            else pure Nothing


-- | Serialize 'PubKeyXY'. First argument @True@ for compressed output (33 bytes), @False@ for uncompressed (65 bytes).
exportPubKeyXY :: Bool -> PubKeyXY -> ByteString
exportPubKeyXY compress (PubKeyXY fptr) = unsafePerformIO $ do
    let flags = if compress then Prim.flagsEcCompressed else Prim.flagsEcUncompressed
    let sz = if compress then 33 else 65
    buf <- mallocBytes sz
    alloca $ \written -> do
        -- always succeeds so we don't need to check
        _ret <- withForeignPtr fptr $ \ptr -> Prim.ecPubkeySerialize ctx buf written ptr flags
        len <- peek written
        unsafePackMallocCStringLen (castPtr buf, fromIntegral len)


-- | Parses 'PubKeyXO' from @ByteString@, will be @Nothing@ if the pubkey corresponds to the Point at Infinity or the
-- the @ByteString@ is not 32 bytes long
importPubKeyXO :: ByteString -> Maybe PubKeyXO
importPubKeyXO bs
    | BS.length bs /= 32 = Nothing
    | otherwise = unsafePerformIO $ do
        outBuf <- mallocBytes 64
        unsafeUseByteString bs $ \(ptr, _) -> do
            ret <- Prim.xonlyPubkeyParse ctx outBuf ptr
            if isSuccess ret
                then Just . PubKeyXO <$> newForeignPtr finalizerFree outBuf
                else pure Nothing


-- | Serializes 'PubKeyXO' to 32 byte @ByteString@
exportPubKeyXO :: PubKeyXO -> ByteString
exportPubKeyXO (PubKeyXO pkFPtr) = unsafePerformIO $ do
    outBuf <- mallocBytes 32
    _ret <- withForeignPtr pkFPtr $ Prim.xonlyPubkeySerialize ctx outBuf
    unsafePackByteString (outBuf, 32)


-- | Parses 'Signature' from DER (71 | 72 | 73 bytes) or Compact (64 bytes) representations.
importSignature :: ByteString -> Maybe Signature
importSignature bs = unsafePerformIO $
    unsafeUseByteString bs $ \(inBuf, len) -> do
        outBuf <- mallocBytes 64
        ret <-
            if
                    -- compact
                    | len == 64 -> Prim.ecdsaSignatureParseCompact ctx outBuf inBuf
                    -- der
                    | len >= 71 && len <= 73 -> Prim.ecdsaSignatureParseDer ctx outBuf inBuf len
                    -- invalid
                    | otherwise -> pure 0
        if isSuccess ret
            then Just . Signature <$> newForeignPtr finalizerFree (castPtr outBuf)
            else free outBuf $> Nothing


-- | Serializes 'Signature' to Compact (64 byte) representation
exportSignatureCompact :: Signature -> ByteString
exportSignatureCompact (Signature fptr) = unsafePerformIO $ do
    outBuf <- mallocBytes 64
    -- always succeeds
    _ret <- withForeignPtr fptr $ Prim.ecdsaSignatureSerializeCompact ctx outBuf
    unsafePackByteString (outBuf, 64)


-- | Serializes 'Signature' to DER (71 | 72 bytes) representation
exportSignatureDer :: Signature -> ByteString
exportSignatureDer (Signature fptr) = unsafePerformIO $ do
    -- as of Q4'2015 73 byte sigs became nonstandard so we will never create one that big
    outBuf <- mallocBytes 72
    alloca $ \written -> do
        -- always succeeds
        _ret <- withForeignPtr fptr $ Prim.ecdsaSignatureSerializeDer ctx outBuf written
        len <- peek written
        unsafePackByteString (outBuf, len)


-- | Parses 'RecoverableSignature' from Compact (65 byte) representation
importRecoverableSignature :: ByteString -> Maybe RecoverableSignature
importRecoverableSignature bs
    | BS.length bs /= 65 = Nothing
    | otherwise = unsafePerformIO . evalContT $ do
        outBuf <- lift (mallocBytes 65)
        (ptr, len) <- ContT (unsafeUseByteString bs)
        recId <- lift (peekByteOff @Word8 ptr 64)
        let recIdCInt = fromIntegral recId
        ret <- lift (Prim.ecdsaRecoverableSignatureParseCompact ctx outBuf ptr recIdCInt)
        lift $
            if isSuccess ret
                then Just . RecoverableSignature <$> newForeignPtr finalizerFree outBuf
                else free outBuf $> Nothing


-- | Serializes 'RecoverableSignature' to Compact (65 byte) representation
exportRecoverableSignature :: RecoverableSignature -> ByteString
exportRecoverableSignature RecoverableSignature{..} = unsafePerformIO . evalContT $ do
    recSigPtr <- ContT (withForeignPtr recoverableSignatureFPtr)
    lift $ do
        outBuf <- mallocBytes 65
        recIdPtr <- malloc
        _ret <- Prim.ecdsaRecoverableSignatureSerializeCompact ctx outBuf recIdPtr recSigPtr
        recId <- peek recIdPtr
        unsafePackByteString (outBuf, 65)


-- | Parses 'Tweak' from 32 byte @ByteString@. If the @ByteString@ is an invalid 'SecKey' then this will yield @Nothing@
importTweak :: ByteString -> Maybe Tweak
importTweak = fmap (Tweak . castForeignPtr . secKeyFPtr) . importSecKey


-- | Verify message signature. 'True' means that the signature is correct.
ecdsaVerify :: ByteString -> PubKeyXY -> Signature -> Bool
ecdsaVerify msgHash (PubKeyXY pkFPtr) (Signature sigFPtr) = unsafePerformIO $
    evalContT $ do
        pkPtr <- ContT (withForeignPtr pkFPtr)
        sigPtr <- ContT (withForeignPtr sigFPtr)
        (msgHashPtr, n) <- ContT (unsafeUseByteString msgHash)
        lift $ isSuccess <$> Prim.ecdsaVerify ctx sigPtr msgHashPtr pkPtr


-- | Signs @ByteString@ with 'SecKey' only if @ByteString@ is 32 bytes.
ecdsaSign :: SecKey -> ByteString -> Maybe Signature
ecdsaSign (SecKey skFPtr) msgHash
    | BS.length msgHash /= 32 = Nothing
    | otherwise = unsafePerformIO $
        evalContT $ do
            skPtr <- ContT (withForeignPtr skFPtr)
            (msgHashPtr, _) <- ContT (unsafeUseByteString msgHash)
            sigBuf <- lift $ mallocBytes 64
            ret <- lift $ Prim.ecdsaSign ctx sigBuf msgHashPtr skPtr Prim.nonceFunctionDefault nullPtr
            lift $
                if isSuccess ret
                    then Just . Signature <$> newForeignPtr finalizerFree sigBuf
                    else free sigBuf $> Nothing


-- | Signs @ByteString@ with 'SecKey' only if @ByteString@ is 32 bytes. Retains ability to compute 'PubKeyXY' from the
-- 'RecoverableSignature' and the original message (@ByteString@)
ecdsaSignRecoverable :: SecKey -> ByteString -> Maybe RecoverableSignature
ecdsaSignRecoverable SecKey{..} bs
    | BS.length bs /= 32 = Nothing
    | otherwise = unsafePerformIO . evalContT $ do
        (msgHashPtr, _) <- ContT (unsafeUseByteString bs)
        secKeyPtr <- ContT (withForeignPtr secKeyFPtr)
        lift $ do
            recSigBuf <- mallocBytes 65
            ret <- Prim.ecdsaSignRecoverable ctx recSigBuf msgHashPtr secKeyPtr Prim.nonceFunctionDefault nullPtr
            if isSuccess ret
                then Just . RecoverableSignature <$> newForeignPtr finalizerFree recSigBuf
                else free recSigBuf $> Nothing


-- | Computes 'PubKeyXY' from 'RecoverableSignature' and the original message that was signed (must be 32 bytes).
ecdsaRecover :: RecoverableSignature -> ByteString -> Maybe PubKeyXY
ecdsaRecover RecoverableSignature{..} msgHash
    | BS.length msgHash /= 32 = Nothing
    | otherwise = unsafePerformIO . evalContT $ do
        recSigPtr <- ContT (withForeignPtr recoverableSignatureFPtr)
        (msgHashPtr, _) <- ContT (unsafeUseByteString msgHash)
        lift $ do
            pubKeyBuf <- mallocBytes 64
            ret <- Prim.ecdsaRecover ctx pubKeyBuf recSigPtr msgHashPtr
            if isSuccess ret
                then Just . PubKeyXY <$> newForeignPtr finalizerFree pubKeyBuf
                else free pubKeyBuf $> Nothing


-- | Forgets the recovery id of a signature
recSigToSig :: RecoverableSignature -> Signature
recSigToSig RecoverableSignature{..} = unsafePerformIO . evalContT $ do
    recSigPtr <- ContT (withForeignPtr recoverableSignatureFPtr)
    lift $ do
        sigBuf <- mallocBytes 64
        _ret <- Prim.ecdsaRecoverableSignatureConvert ctx sigBuf recSigPtr
        Signature <$> newForeignPtr finalizerFree sigBuf


-- | Use 'SecKey' to compute the corresponding 'PubKeyXY'
derivePubKey :: SecKey -> PubKeyXY
derivePubKey (SecKey skFPtr) = unsafePerformIO $ do
    outBuf <- mallocBytes 64
    ret <- withForeignPtr skFPtr $ Prim.ecPubkeyCreate ctx outBuf
    unless (isSuccess ret) $ do
        free outBuf
        error "Bug: Invalid SecKey Constructed"
    PubKeyXY <$> newForeignPtr finalizerFree outBuf


-- | Compute a shared secret using ECDH and SHA256. This algorithm uses your own 'SecKey', your counterparty's 'PubKeyXY'
-- and results in a 32 byte SHA256 Digest.
ecdh :: SecKey -> PubKeyXY -> Digest SHA256
ecdh SecKey{..} PubKeyXY{..} = unsafePerformIO . evalContT $ do
    outBuf <- lift (mallocBytes 32)
    sk <- ContT (withForeignPtr secKeyFPtr)
    pk <- ContT (withForeignPtr pubKeyXYFPtr)
    ret <- lift (Prim.ecdh ctx outBuf pk sk Prim.ecdhHashFunctionSha256 nullPtr)
    if isSuccess ret
        then do
            bs <- lift $ unsafePackByteString (outBuf, 32)
            let Just digest = digestFromByteString bs
            pure digest
        else lift (free outBuf) *> error "Bug: Invalid Scalar or Overflow"


-- -- | Add 'Tweak' to 'SecKey'.
ecSecKeyTweakAdd :: SecKey -> Tweak -> Maybe SecKey
ecSecKeyTweakAdd SecKey{..} Tweak{..} = unsafePerformIO . evalContT $ do
    skPtr <- ContT (withForeignPtr secKeyFPtr)
    skOut <- lift (mallocBytes 32)
    lift (memcpy skOut skPtr 32)
    twkPtr <- ContT (withForeignPtr tweakFPtr)
    ret <- lift (Prim.ecSeckeyTweakAdd ctx skOut twkPtr)
    lift $
        if isSuccess ret
            then Just . SecKey <$> newForeignPtr finalizerFree skOut
            else free skOut $> Nothing


-- | Multiply 'SecKey' by 'Tweak'.
ecSecKeyTweakMul :: SecKey -> Tweak -> Maybe SecKey
ecSecKeyTweakMul SecKey{..} Tweak{..} = unsafePerformIO . evalContT $ do
    skPtr <- ContT (withForeignPtr secKeyFPtr)
    skOut <- lift (mallocBytes 32)
    lift (memcpy skOut skPtr 32)
    twkPtr <- ContT (withForeignPtr tweakFPtr)
    ret <- lift (Prim.ecSeckeyTweakMul ctx skOut twkPtr)
    lift $
        if isSuccess ret
            then Just . SecKey <$> newForeignPtr finalizerFree skOut
            else free skOut $> Nothing


-- | Compute 'KeyPair' structure from 'SecKey'
keyPairCreate :: SecKey -> KeyPair
keyPairCreate SecKey{..} = unsafePerformIO $ do
    keyPairBuf <- mallocBytes 96
    ret <- withForeignPtr secKeyFPtr $ Prim.keypairCreate ctx keyPairBuf
    unless (isSuccess ret) $ do
        free keyPairBuf
        error "Bug: Invalid SecKey Constructed"
    KeyPair <$> newForeignPtr finalizerFree keyPairBuf


-- | Project 'PubKeyXY' from 'KeyPair'
keyPairPubKeyXY :: KeyPair -> PubKeyXY
keyPairPubKeyXY KeyPair{..} = unsafePerformIO $ do
    pubKeyBuf <- mallocBytes 64
    ret <- withForeignPtr keyPairFPtr $ Prim.keypairPub ctx pubKeyBuf
    unless (isSuccess ret) $ do
        free pubKeyBuf
        error "Bug: Invalid KeyPair Constructed"
    PubKeyXY <$> newForeignPtr finalizerFree pubKeyBuf


-- | Project 'SecKey' from 'KeyPair'
keyPairSecKey :: KeyPair -> SecKey
keyPairSecKey KeyPair{..} = unsafePerformIO $ do
    secKeyBuf <- mallocBytes 32
    ret <- withForeignPtr keyPairFPtr $ Prim.keypairSec ctx secKeyBuf
    unless (isSuccess ret) $ do
        free secKeyBuf
        error "Bug: Invalid KeyPair Constructed"
    SecKey <$> newForeignPtr finalizerFree secKeyBuf


-- | Project 'PubKeyXO' from 'KeyPair' as well as parity bit. @True@ indicates that the public key is the same as it
-- would be if you had serialized the 'PubKeyXO' and it was prefixed with 'Prim.flagsTagPubkeyOdd'. @False@ indicates
-- it would be prefixed by 'Prim.flagsTagPubkeyEven'
keyPairPubKeyXO :: KeyPair -> (PubKeyXO, Bool)
keyPairPubKeyXO KeyPair{..} = unsafePerformIO $ do
    pubKeyBuf <- mallocBytes 64
    parityPtr <- malloc
    ret <- withForeignPtr keyPairFPtr $ Prim.keypairXonlyPub ctx pubKeyBuf parityPtr
    unless (isSuccess ret) $ do
        free pubKeyBuf
        free parityPtr
        error "Bug: Invalid KeyPair Constructed"
    parity <- peek parityPtr
    negated <- case parity of
        0 -> pure False
        1 -> pure True
        _ -> do
            free pubKeyBuf
            free parityPtr
            error "Bug: Invalid pk_parity result from Prim"
    (,negated) . PubKeyXO <$> newForeignPtr finalizerFree pubKeyBuf


-- | Tweak a 'KeyPair' with a 'Tweak'. If the resulting 'KeyPair' is invalid (0, Infinity), then the result is @Nothing@
keyPairPubKeyXOTweakAdd :: KeyPair -> Tweak -> Maybe KeyPair
keyPairPubKeyXOTweakAdd KeyPair{..} Tweak{..} = unsafePerformIO . evalContT $ do
    keyPairPtr <- ContT (withForeignPtr keyPairFPtr)
    tweakPtr <- ContT (withForeignPtr tweakFPtr)
    lift $ do
        keyPairOut <- (mallocBytes 96)
        _ <- (memcpy keyPairOut keyPairPtr 96)
        ret <- Prim.keypairXonlyTweakAdd ctx keyPairOut tweakPtr
        if isSuccess ret
            then Just . KeyPair <$> newForeignPtr finalizerFree keyPairOut
            else free keyPairOut $> Nothing


-- | Compute a schnorr signature using a 'KeyPair'. The @ByteString@ must be 32 bytes long to get a @Just@ out of this
-- function
schnorrSign :: KeyPair -> ByteString -> Maybe Signature
schnorrSign KeyPair{..} bs
    | BS.length bs /= 32 = Nothing
    | otherwise = unsafePerformIO . evalContT $ do
        (msgHashPtr, _) <- ContT (unsafeUseByteString bs)
        keyPairPtr <- ContT (withForeignPtr keyPairFPtr)
        lift $ do
            sigBuf <- mallocBytes 64
            -- TODO: provide randomness here instead of supplying a null pointer
            ret <- Prim.schnorrsigSign ctx sigBuf msgHashPtr keyPairPtr nullPtr
            if isSuccess ret
                then Just . Signature <$> newForeignPtr finalizerFree sigBuf
                else free sigBuf $> Nothing


-- | Extra parameters object for alternative nonce generation
data SchnorrExtra a = Storable a =>
    SchnorrExtra
    { schnorrExtraNonceFunHardened :: ByteString -> SecKey -> PubKeyXO -> ByteString -> a -> Maybe (SizedByteArray 32 ByteString)
    , schnorrExtraData :: a
    }


-- | Compute a schnorr signature with an alternative scheme for generating nonces, it is not recommended you use this
-- unless you know what you are doing. Instead, favor the usage of 'schnorrSign'
schnorrSignCustom :: forall a. KeyPair -> ByteString -> SchnorrExtra a -> Maybe Signature
schnorrSignCustom KeyPair{..} msg SchnorrExtra{..} = unsafePerformIO . evalContT $ do
    (msgPtr, msgLen) <- ContT (unsafeUseByteString msg)
    keyPairPtr <- ContT (withForeignPtr keyPairFPtr)
    lift $ do
        sigBuf <- mallocBytes 64
        -- convert fn into funptr
        funptr <- mkNonceFunHardened primFn
        -- allocate memory for extra data ptr
        dataptr <- malloc
        -- copy data to new pointer
        poke dataptr schnorrExtraData
        -- allocate extraparams structure
        extraPtr <- mallocBytes (4 + sizeOf funptr + sizeOf dataptr)
        -- fill magic
        pokeByteOff extraPtr 0 (0xDA :: Word8)
        pokeByteOff extraPtr 1 (0x6F :: Word8)
        pokeByteOff extraPtr 2 (0xB3 :: Word8)
        pokeByteOff extraPtr 3 (0x8C :: Word8)
        -- fill funptr
        pokeByteOff extraPtr 4 funptr
        -- fill dataptr
        pokeByteOff extraPtr (4 + sizeOf funptr) dataptr
        ret <- Prim.schnorrsigSignCustom ctx sigBuf msgPtr msgLen keyPairPtr extraPtr
        freeHaskellFunPtr funptr
        free dataptr
        free extraPtr
        if isSuccess ret
            then Just . Signature <$> newForeignPtr finalizerFree sigBuf
            else free sigBuf $> Nothing
    where
        primFn :: Storable a => Prim.NonceFunHardened a
        primFn outBuf msgPtr msgLen sk xopk algo algolen dataPtr = do
            msg <- unsafePackByteString (msgPtr, msgLen)
            sk <- SecKey <$> newForeignPtr_ (castPtr sk)
            xopk <- PubKeyXO <$> newForeignPtr_ (castPtr xopk)
            algo <- unsafePackByteString (algo, algolen)
            extra <- peek dataPtr
            case schnorrExtraNonceFunHardened msg sk xopk algo extra of
                Nothing -> pure 0
                Just bs -> evalContT $ do
                    (hashPtr, _) <- ContT (unsafeUseByteString (unSizedByteArray bs))
                    lift (memcpy outBuf hashPtr 32)
                    pure 1


-- | Verify the authenticity of a schnorr signature. @True@ means the 'Signature' is correct.
schnorrVerify :: PubKeyXO -> ByteString -> Signature -> Bool
schnorrVerify PubKeyXO{..} bs Signature{..} = unsafePerformIO . evalContT $ do
    pubKeyPtr <- ContT (withForeignPtr pubKeyXOFPtr)
    signaturePtr <- ContT (withForeignPtr signatureFPtr)
    (msgPtr, msgLen) <- ContT (unsafeUseByteString bs)
    lift $ isSuccess <$> Prim.schnorrsigSignVerify ctx signaturePtr msgPtr msgLen pubKeyPtr


-- | Generate a tagged sha256 digest as specified in BIP340
taggedSha256 :: ByteString -> ByteString -> Digest SHA256
taggedSha256 tag msg = unsafePerformIO . evalContT $ do
    (tagBuf, tagLen) <- ContT (unsafeUseByteString tag)
    (msgBuf, msgLen) <- ContT (unsafeUseByteString msg)
    lift $ do
        hashBuf <- mallocBytes 32
        ret <- Prim.taggedSha256 ctx hashBuf tagBuf tagLen msgBuf msgLen
        unless (isSuccess ret) $ do
            free hashBuf
            error "Bug: Invalid use of C Lib"
        bs <- unsafePackByteString (hashBuf, 32)
        let Just digest = digestFromByteString bs
        pure digest


-- | Combine a list of 'PubKeyXY's into a single 'PubKeyXY'. This will result in @Nothing@ if the group operation results
-- in the Point at Infinity
pubKeyCombine :: [PubKeyXY] -> Maybe PubKeyXY
pubKeyCombine keys = unsafePerformIO $ do
    let n = length keys
    keysBuf <- mallocBytes (64 * n)
    for_ (zip [0 ..] keys) $ \(i, PubKeyXY{..}) ->
        withForeignPtr pubKeyXYFPtr $ pokeElemOff keysBuf i
    outBuf <- mallocBytes 64
    ret <- Prim.ecPubkeyCombine ctx outBuf keysBuf (fromIntegral n)
    if isSuccess ret
        then Just . PubKeyXY <$> newForeignPtr finalizerFree outBuf
        else free outBuf $> Nothing


-- | Negate a 'PubKeyXY'
pubKeyNegate :: PubKeyXY -> PubKeyXY
pubKeyNegate PubKeyXY{..} = unsafePerformIO $ do
    outBuf <- mallocBytes 64
    withForeignPtr pubKeyXYFPtr $ flip (memcpy outBuf) 64
    _ret <- Prim.ecPubkeyNegate ctx outBuf
    PubKeyXY <$> newForeignPtr finalizerFree outBuf


-- | Add 'Tweak' to 'PubKeyXY'. This will result in @Nothing@ if the group operation results in the Point at Infinity
pubKeyTweakAdd :: PubKeyXY -> Tweak -> Maybe PubKeyXY
pubKeyTweakAdd PubKeyXY{..} Tweak{..} = unsafePerformIO . evalContT $ do
    pubKeyPtr <- ContT (withForeignPtr pubKeyXYFPtr)
    tweakPtr <- ContT (withForeignPtr tweakFPtr)
    lift $ do
        pubKeyOutBuf <- mallocBytes 64
        memcpy pubKeyOutBuf pubKeyPtr 64
        ret <- Prim.ecPubkeyTweakAdd ctx pubKeyOutBuf tweakPtr
        if isSuccess ret
            then Just . PubKeyXY <$> newForeignPtr finalizerFree pubKeyOutBuf
            else free pubKeyOutBuf $> Nothing


-- | Multiply 'PubKeyXY' by 'Tweak'. This will result in @Nothing@ if the group operation results in the Point at Infinity
pubKeyTweakMul :: PubKeyXY -> Tweak -> Maybe PubKeyXY
pubKeyTweakMul PubKeyXY{..} Tweak{..} = unsafePerformIO . evalContT $ do
    pubKeyPtr <- ContT (withForeignPtr pubKeyXYFPtr)
    tweakPtr <- ContT (withForeignPtr tweakFPtr)
    lift $ do
        pubKeyOutBuf <- mallocBytes 64
        memcpy pubKeyOutBuf pubKeyPtr 64
        ret <- Prim.ecPubkeyTweakMul ctx pubKeyOutBuf tweakPtr
        if isSuccess ret
            then Just . PubKeyXY <$> newForeignPtr finalizerFree pubKeyOutBuf
            else free pubKeyOutBuf $> Nothing


-- | Negate a 'SecKey'
secKeyNegate :: SecKey -> SecKey
secKeyNegate SecKey{..} = unsafePerformIO $ do
    outBuf <- mallocBytes 32
    withForeignPtr secKeyFPtr $ flip (memcpy outBuf) 32
    _ret <- Prim.ecSeckeyNegate ctx outBuf
    SecKey <$> newForeignPtr finalizerFree outBuf


-- | Convert 'PubKeyXY' to 'PubKeyXO'. See 'keyPairPubKeyXO' for more information on how to interpret the parity bit.
xyToXO :: PubKeyXY -> (PubKeyXO, Bool)
xyToXO PubKeyXY{..} = unsafePerformIO $ do
    outBuf <- mallocBytes 64
    parityPtr <- malloc
    ret <- withForeignPtr pubKeyXYFPtr $ Prim.xonlyPubkeyFromPubkey ctx outBuf parityPtr
    unless (isSuccess ret) $ do
        free outBuf
        error "Bug: Couldn't convert xy to xo"
    parity <- peek parityPtr
    negated <- case parity of
        0 -> pure False
        1 -> pure True
        _ -> free outBuf *> error "Bug: Invalid pk_parity from Prim"
    (,negated) . PubKeyXO <$> newForeignPtr finalizerFree outBuf


-- | Add 'Tweak' to 'PubKeyXO'. This will result in @Nothing@ if the group operation results in the Point at Infinity
pubKeyXOTweakAdd :: PubKeyXO -> Tweak -> Maybe PubKeyXY
pubKeyXOTweakAdd PubKeyXO{..} Tweak{..} = unsafePerformIO . evalContT $ do
    pubKeyXOPtr <- ContT (withForeignPtr pubKeyXOFPtr)
    tweakPtr <- ContT (withForeignPtr tweakFPtr)
    lift $ do
        outBuf <- mallocBytes 64
        ret <- Prim.xonlyPubkeyTweakAdd ctx outBuf pubKeyXOPtr tweakPtr
        if isSuccess ret
            then Just . PubKeyXY <$> newForeignPtr finalizerFree outBuf
            else free outBuf $> Nothing


-- | Check that a 'PubKeyXO' is the result of the specified tweak operation. @True@ means it was.
pubKeyXOTweakAddCheck :: PubKeyXO -> Bool -> PubKeyXO -> Tweak -> Bool
pubKeyXOTweakAddCheck PubKeyXO{pubKeyXOFPtr = tweakedFPtr} parity PubKeyXO{pubKeyXOFPtr = origFPtr} Tweak{..} =
    unsafePerformIO . evalContT $ do
        tweakedPtr <- ContT (withForeignPtr tweakedFPtr)
        origPtr <- ContT (withForeignPtr origFPtr)
        tweakPtr <- ContT (withForeignPtr tweakFPtr)
        let parityInt = if parity then 1 else 0
        lift $ isSuccess <$> Prim.xonlyPubkeyTweakAddCheck ctx tweakedPtr parityInt origPtr tweakPtr


foreign import ccall "wrapper"
    mkNonceFunHardened :: Prim.NonceFunHardened a -> IO (FunPtr (Prim.NonceFunHardened a))