csecp256k1

Haskell FFI bindings to bitcoin-core/secp256k1 (docs.ppad.tech/csecp256k1).
git clone git://git.ppad.tech/csecp256k1.git
Log | Files | Refs | README | LICENSE

Secp256k1.hs (22038B)


      1 {-# LANGUAGE DeriveGeneric #-}
      2 {-# LANGUAGE DerivingStrategies #-}
      3 {-# LANGUAGE OverloadedStrings #-}
      4 {-# LANGUAGE ViewPatterns #-}
      5 
      6 -- |
      7 -- Module: Crypto.Secp256k1
      8 -- Copyright: (c) 2024 Jared Tobin
      9 -- License: MIT
     10 -- Maintainer: Jared Tobin <jared@ppad.tech>
     11 --
     12 -- Bindings to bitcoin-core/secp256k1, a C library supporting digital
     13 -- signatures and other cryptographic primitives on the secp256k1
     14 -- elliptic curve.
     15 --
     16 -- This library exposes a minimal subset of functionality, primarily
     17 -- supporting ECDSA/Schnorr signatures and ECDH secret computation.
     18 --
     19 -- We're interacting with a C library here, and we don't pretend that we
     20 -- aren't. 'IO' is prevalent and unhidden, and asynchronous exceptions
     21 -- (in the form of 'Secp256k1Exception') are the error-handling method
     22 -- of choice.
     23 
     24 module Crypto.Secp256k1 (
     25     Context(..)
     26   , wcontext
     27   , wrcontext
     28 
     29   , Sig
     30   , sign
     31   , sign_schnorr
     32   , verify
     33   , verify_schnorr
     34   , ecdh
     35 
     36   , parse_der
     37   , serialize_der
     38   , parse_compact
     39   , serialize_compact
     40 
     41   , Pub
     42   , derive_pub
     43   , parse_pub
     44   , tweak_pub_add
     45   , tweak_pub_mul
     46   , tweak_sec_add
     47   , tweak_sec_mul
     48   , serialize_pub
     49   , serialize_pub_u
     50   , XOnlyPub
     51   , xonly
     52   , parse_xonly
     53   , serialize_xonly
     54   , KeyPair
     55   , keypair
     56   , keypair_pub
     57   , keypair_sec
     58 
     59   , Secp256k1Exception(..)
     60   ) where
     61 
     62 import Control.Exception (Exception, bracket, throwIO)
     63 import Control.Monad (when)
     64 import Crypto.Secp256k1.Internal hiding (Context, wcontext)
     65 import qualified Crypto.Secp256k1.Internal as I (Context)
     66 import GHC.Generics
     67 import qualified Data.ByteString as BS
     68 import qualified Foreign.Marshal.Alloc as A (alloca, allocaBytes)
     69 import Foreign.Ptr (Ptr)
     70 import qualified Foreign.Ptr as F (castPtr, nullPtr)
     71 import qualified Foreign.Storable as S (poke, peek)
     72 
     73 -- | A bitcoin-core/secp256k1 context.
     74 --
     75 --   bitcoin-core/secp256k1 computations typically require a context,
     76 --   the primary purpose of which is to store randomization data as
     77 --   increased protection against side-channel attacks (and the second
     78 --   of which is boring pointer storage to various library callbacks).
     79 --
     80 --   You should create and use values of this type via 'wrcontext' or
     81 --   'wcontext'.
     82 --
     83 --   The data constructor is exported only to make the implementation
     84 --   easier to benchmark. You should /not/ pattern match on or
     85 --   manipulate context values.
     86 newtype Context = Context (Ptr I.Context)
     87   deriving stock Generic
     88 
     89 instance Show Context where
     90   show (Context tex) = "<bitcoin-core/secp256k1 context " <> show tex <> ">"
     91 
     92 -- | A bitcoin-core/secp256k1-internal public key.
     93 --
     94 --   Create a value of this type by parsing a compressed or uncompressed
     95 --   public key via 'parse_pub', deriving one from a secret key via
     96 --   'create_pub', or extracting one from a keypair via 'keypair_pub'.
     97 newtype Pub = Pub BS.ByteString
     98   deriving stock Generic
     99 
    100 instance Show Pub where
    101   show _ = "<bitcoin-core/secp256k1 public key>"
    102 
    103 -- | A bitcoin-core/secp256k1-internal x-only public key.
    104 --
    105 --   An "x-only" public key corresponds to a public key with even
    106 --   y-coordinate.
    107 --
    108 --   Create a value of this type from a 'Pub' via 'xonly', or parse one
    109 --   directly via 'parse_xonly'.
    110 newtype XOnlyPub = XOnlyPub BS.ByteString
    111   deriving stock Generic
    112 
    113 instance Show XOnlyPub where
    114   show _ = "<bitcoin-core/secp256k1 x-only public key>"
    115 
    116 -- | A bitcoin-core/secp256k1-internal keypair.
    117 --
    118 --   Create a value of this type by passing a secret key to
    119 --   'keypair'.
    120 newtype KeyPair = KeyPair BS.ByteString
    121   deriving stock Generic
    122 
    123 instance Show KeyPair where
    124   show _ = "<bitcoin-core/secp256k1 keypair>"
    125 
    126 -- | A bitcoin-core/secp256k1-internal ECDSA signature.
    127 --
    128 --   Create a value of this type via 'sign', or parse a DER-encoded
    129 --   signature via 'parse_der'.
    130 newtype Sig = Sig BS.ByteString
    131   deriving stock Generic
    132 
    133 instance Show Sig where
    134   show _ = "<bitcoin-core/secp256k1 signature>"
    135 
    136 -- exceptions
    137 
    138 -- | A catch-all exception type.
    139 --
    140 data Secp256k1Exception =
    141     -- | Thrown when a bitcoin-core/secp256k1 function returns a value
    142     --   indicating failure.
    143     Secp256k1Error
    144     -- | Thrown when a csecp256k1 function has been passed a bad (i.e.,
    145     --   incorrectly-sized) input.
    146   | CSecp256k1Error
    147   deriving Show
    148 
    149 instance Exception Secp256k1Exception
    150 
    151 -- context
    152 
    153 -- | Execute the supplied continuation within a fresh
    154 --   bitcoin-core/secp256k1 context. The context will be destroyed
    155 --   afterwards.
    156 --
    157 --   This function executes the supplied continuation in a context
    158 --   that has /not/ been randomized, and so /doesn't/ offer additional
    159 --   side-channel attack protection. For that, use 'wrcontext'.
    160 --
    161 --   >>> wcontext $ \tex -> parse_pub tex bytestring
    162 --   "<bitcoin-core/secp256k1 public key>"
    163 wcontext
    164   :: (Context -> IO ()) -- ^ continuation to run in the context
    165   -> IO ()
    166 wcontext = bracket create destroy where
    167   create = do
    168     tex <- secp256k1_context_create _SECP256K1_CONTEXT_NONE
    169     pure (Context tex)
    170 
    171   destroy (Context tex) =
    172     secp256k1_context_destroy tex
    173 
    174 -- | Same as 'wcontext', but randomize the bitcoin-core/secp256k1
    175 --   context with the provided 32 bytes of entropy before executing the
    176 --   supplied continuation.
    177 --
    178 --   Use this function to execute computations that may benefit from
    179 --   additional side-channel attack protection.
    180 --
    181 --   >>> wrcontext entropy $ \tex -> sign tex sec msg
    182 --   "<bitcoin-core/secp256k1 signature>"
    183 wrcontext
    184   :: BS.ByteString     -- ^ 32 bytes of fresh entropy
    185   -> (Context -> IO ()) -- ^ continuation to run in the context
    186   -> IO ()
    187 wrcontext enn con
    188     | BS.length enn /= 32 = throwIO CSecp256k1Error
    189     | otherwise = bracket create destroy con
    190   where
    191     create = do
    192       tex <- secp256k1_context_create _SECP256K1_CONTEXT_NONE
    193       BS.useAsCString enn $ \(F.castPtr -> sed) -> do
    194         suc <- secp256k1_context_randomize tex sed
    195         when (suc /= 1) $ throwIO Secp256k1Error
    196         pure (Context tex)
    197 
    198     destroy (Context tex) =
    199       secp256k1_context_destroy tex
    200 
    201 -- ec
    202 
    203 -- | Derive a public key from a 32-byte secret key.
    204 --
    205 --   >>> wrcontext entropy $ \tex -> derive_pub tex sec
    206 --   "<bitcoin-core/secp256k1 public key>"
    207 derive_pub
    208   :: Context
    209   -> BS.ByteString -- ^ 32-byte secret key
    210   -> IO Pub
    211 derive_pub (Context tex) bs
    212   | BS.length bs /= 32 = throwIO CSecp256k1Error
    213   | otherwise = BS.useAsCString bs $ \(F.castPtr -> sec) ->
    214       A.allocaBytes _PUB_BYTES_INTERNAL $ \out -> do
    215         suc <- secp256k1_ec_pubkey_create tex out sec
    216         when (suc /= 1) $ throwIO Secp256k1Error
    217         let pub = F.castPtr out
    218         key <- BS.packCStringLen (pub, _PUB_BYTES_INTERNAL)
    219         pure (Pub key)
    220 
    221 -- | Parse a compressed (33-byte) or uncompressed (65-byte) public key.
    222 --
    223 --   >>> wcontext $ \tex -> parse_pub tex bs
    224 --   "<bitcoin-core/secp256k1 public key>"
    225 parse_pub
    226   :: Context
    227   -> BS.ByteString -- ^ compressed or uncompressed public key
    228   -> IO Pub
    229 parse_pub (Context tex) bs =
    230   BS.useAsCStringLen bs $ \(F.castPtr -> pub, fromIntegral -> len) ->
    231     A.allocaBytes _PUB_BYTES_INTERNAL $ \out -> do
    232       suc <- secp256k1_ec_pubkey_parse tex out pub len
    233       when (suc /= 1) $ throwIO Secp256k1Error
    234       let par = F.castPtr out
    235       key <- BS.packCStringLen (par, _PUB_BYTES_INTERNAL)
    236       pure (Pub key)
    237 
    238 data PubFormat =
    239     Compressed
    240   | Uncompressed
    241 
    242 -- | Serialize a public key into a compressed (33-byte) bytestring
    243 --   representation.
    244 --
    245 --   >>> wcontext $ \tex -> serialize_pub tex pub
    246 serialize_pub
    247   :: Context
    248   -> Pub
    249   -> IO BS.ByteString -- ^ serialized compressed public key
    250 serialize_pub = serialize_pub_in Compressed
    251 
    252 -- | Serialize a public key into an uncompressed (65-byte) bytestring
    253 --   represention.
    254 --
    255 --   >>> wcontext $ \tex -> serialize_pub_u tex pub
    256 serialize_pub_u
    257   :: Context
    258   -> Pub
    259   -> IO BS.ByteString -- ^ serialized uncompressed public key
    260 serialize_pub_u = serialize_pub_in Uncompressed
    261 
    262 serialize_pub_in :: PubFormat -> Context -> Pub -> IO BS.ByteString
    263 serialize_pub_in for (Context tex) (Pub pub) =
    264     BS.useAsCString pub $ \(F.castPtr -> key) ->
    265       A.alloca $ \len ->
    266         A.allocaBytes bys $ \out -> do
    267           let siz = fromIntegral bys
    268           S.poke len siz
    269           suc <- secp256k1_ec_pubkey_serialize tex out len key fal
    270           when (suc /= 1) $ throwIO Secp256k1Error
    271           pec <- S.peek len
    272           let enc = F.castPtr out
    273               nel = fromIntegral pec
    274           BS.packCStringLen (enc, nel)
    275   where
    276     bys = case for of
    277       Compressed -> _PUB_BYTES_COMPRESSED
    278       Uncompressed -> _PUB_BYTES_UNCOMPRESSED
    279 
    280     fal = case for of
    281       Compressed -> _COMPRESSED_FLAG
    282       Uncompressed -> _UNCOMPRESSED_FLAG
    283 
    284 -- | Additively tweak a public key with the supplied 32-byte tweak.
    285 --
    286 --   >>> wrcontext $ \tex -> tweak_pub_add pub tweak
    287 tweak_pub_add
    288   :: Context
    289   -> Pub
    290   -> BS.ByteString -- ^ 32-byte tweak value
    291   -> IO Pub
    292 tweak_pub_add (Context tex) (Pub pub) wee
    293   | BS.length wee /= 32 = throwIO CSecp256k1Error
    294   | otherwise = do
    295       let cop = BS.copy pub
    296       BS.useAsCString cop $ \(F.castPtr -> out) ->
    297         BS.useAsCString wee $ \(F.castPtr -> eek) -> do
    298           suc <- secp256k1_ec_pubkey_tweak_add tex out eek
    299           when (suc /= 1) $ throwIO Secp256k1Error
    300           let enc = F.castPtr out
    301           key <- BS.packCStringLen (enc, _PUB_BYTES_INTERNAL)
    302           pure (Pub key)
    303 
    304 -- | Multiplicatively tweak a public key with the supplied 32-byte
    305 --   tweak.
    306 --
    307 --   >>> wrcontext $ \tex -> tweak_pub_mul pub tweak
    308 tweak_pub_mul
    309   :: Context
    310   -> Pub
    311   -> BS.ByteString -- ^ 32-byte tweak value
    312   -> IO Pub
    313 tweak_pub_mul (Context tex) (Pub pub) wee
    314   | BS.length wee /= 32 = throwIO CSecp256k1Error
    315   | otherwise = do
    316       let cop = BS.copy pub
    317       BS.useAsCString cop $ \(F.castPtr -> out) ->
    318         BS.useAsCString wee $ \(F.castPtr -> eek) -> do
    319           suc <- secp256k1_ec_pubkey_tweak_mul tex out eek
    320           when (suc /= 1) $ throwIO Secp256k1Error
    321           let enc = F.castPtr out
    322           key <- BS.packCStringLen (enc, _PUB_BYTES_INTERNAL)
    323           pure (Pub key)
    324 
    325 -- | Additively tweak a secret key with the supplied 32-byte tweak.
    326 --
    327 --   >>> wrcontext $ \tex -> tweak_sec_add sec tweak
    328 tweak_sec_add
    329   :: Context
    330   -> BS.ByteString    -- ^ 32-byte secret key
    331   -> BS.ByteString    -- ^ 32-byte tweak value
    332   -> IO BS.ByteString -- ^ 32-byte secret key
    333 tweak_sec_add (Context tex) key wee
    334   | BS.length key /= 32 || BS.length wee /= 32 = throwIO CSecp256k1Error
    335   | otherwise = do
    336       let sec = BS.copy key
    337       BS.useAsCString sec $ \(F.castPtr -> out) ->
    338         BS.useAsCString wee $ \(F.castPtr -> eek) -> do
    339           suc <- secp256k1_ec_seckey_tweak_add tex out eek
    340           when (suc /= 1) $ throwIO Secp256k1Error
    341           let enc = F.castPtr out
    342           BS.packCStringLen (enc, _SEC_BYTES)
    343 
    344 -- | Multiplicatively tweak a secret key with the supplied 32-byte
    345 --   tweak.
    346 --
    347 --   >>> wrcontext $ \tex -> tweak_sec_mul sec tweak
    348 tweak_sec_mul
    349   :: Context
    350   -> BS.ByteString    -- ^ 32-byte secret key
    351   -> BS.ByteString    -- ^ 32-byte tweak value
    352   -> IO BS.ByteString -- ^ 32-byte secret key
    353 tweak_sec_mul (Context tex) key wee
    354   | BS.length key /= 32 || BS.length wee /= 32 = throwIO CSecp256k1Error
    355   | otherwise = do
    356       let sec = BS.copy key
    357       BS.useAsCString sec $ \(F.castPtr -> out) ->
    358         BS.useAsCString wee $ \(F.castPtr -> eek) -> do
    359           suc <- secp256k1_ec_seckey_tweak_mul tex out eek
    360           when (suc /= 1) $ throwIO Secp256k1Error
    361           let enc = F.castPtr out
    362           BS.packCStringLen (enc, _SEC_BYTES)
    363 
    364 -- ecdsa
    365 
    366 -- | Sign a 32-byte message hash with the provided secret key.
    367 --
    368 --   >>> wrcontext entropy $ \tex -> sign tex sec msg
    369 --   "<bitcoin-core/secp256k1 signature>"
    370 sign
    371   :: Context
    372   -> BS.ByteString -- ^ 32-byte secret key
    373   -> BS.ByteString -- ^ 32-byte message hash
    374   -> IO Sig
    375 sign (Context tex) key msg
    376   | BS.length key /= 32 || BS.length msg /= 32 = throwIO CSecp256k1Error
    377   | otherwise = A.allocaBytes _SIG_BYTES $ \out ->
    378       BS.useAsCString msg $ \(F.castPtr -> has) ->
    379         BS.useAsCString key $ \(F.castPtr -> sec) -> do
    380           suc <- secp256k1_ecdsa_sign tex out has sec F.nullPtr F.nullPtr
    381           when (suc /= 1) $ throwIO Secp256k1Error
    382           let sig = F.castPtr out
    383           enc <- BS.packCStringLen (sig, _SIG_BYTES)
    384           pure (Sig enc)
    385 
    386 -- | Verify an ECDSA signature for the provided message hash with the
    387 --   supplied public key.
    388 --
    389 --   Returns 'True' for a verifying signature, 'False' otherwise.
    390 --
    391 --   >>> wcontext $ \tex -> verify tex pub msg good_sig
    392 --   True
    393 --   >>> wcontext $ \tex -> verify tex pub msg bad_sig
    394 --   False
    395 verify
    396   :: Context
    397   -> Pub
    398   -> BS.ByteString -- ^ 32-byte message hash
    399   -> Sig
    400   -> IO Bool
    401 verify (Context tex) (Pub pub) msg (Sig sig)
    402   | BS.length msg /= 32 = throwIO CSecp256k1Error
    403   | otherwise = BS.useAsCString pub $ \(F.castPtr -> key) ->
    404       BS.useAsCString sig $ \(F.castPtr -> sip) ->
    405         BS.useAsCString msg $ \(F.castPtr -> has) -> do
    406           suc <- secp256k1_ecdsa_verify tex sip has key
    407           pure (suc == 1)
    408 
    409 -- | Parse a DER-encoded bytestring into a signature.
    410 --
    411 --   >>> wcontext $ \tex -> parse_der tex bytestring
    412 --   "<bitcoin-core/secp256k1 signature>"
    413 --   >>> wcontext $ \tex -> parse_der tex bad_bytestring
    414 --   *** Exception: Secp256k1Error
    415 parse_der
    416   :: Context
    417   -> BS.ByteString -- ^ DER-encoded signature
    418   -> IO Sig
    419 parse_der (Context tex) bs =
    420   BS.useAsCStringLen bs $ \(F.castPtr -> der, fromIntegral -> len) ->
    421     A.allocaBytes _SIG_BYTES $ \out -> do
    422       suc <- secp256k1_ecdsa_signature_parse_der tex out der len
    423       when (suc /= 1) $ throwIO Secp256k1Error
    424       let par = F.castPtr out
    425       sig <- BS.packCStringLen (par, _SIG_BYTES)
    426       pure (Sig sig)
    427 
    428 -- | Serialize a signature into a DER-encoded bytestring.
    429 --
    430 --   >>> wcontext $ \tex -> serialize_der tex sig
    431 serialize_der
    432   :: Context
    433   -> Sig
    434   -> IO BS.ByteString -- ^ DER-encoded signature
    435 serialize_der (Context tex) (Sig sig) =
    436   A.alloca $ \len ->
    437     A.allocaBytes _DER_BYTES $ \out ->
    438       BS.useAsCString sig $ \(F.castPtr -> sip) -> do
    439         let siz = fromIntegral _DER_BYTES
    440         S.poke len siz
    441         suc <- secp256k1_ecdsa_signature_serialize_der tex out len sip
    442         when (suc /= 1) $ throwIO Secp256k1Error
    443         pek <- S.peek len
    444         let der = F.castPtr out
    445             nel = fromIntegral pek
    446         BS.packCStringLen (der, nel)
    447 
    448 -- | Parse a bytestring encoding a compact (64-byte) signature.
    449 --
    450 --   >>> wcontext $ \tex -> parse_compact tex bytestring
    451 parse_compact
    452   :: Context
    453   -> BS.ByteString -- ^ bytestring encoding a 64-byte compact signature
    454   -> IO Sig
    455 parse_compact (Context tex) bs =
    456   BS.useAsCString bs $ \(F.castPtr -> com) ->
    457     A.allocaBytes _SIG_BYTES $ \out -> do
    458       suc <- secp256k1_ecdsa_signature_parse_compact tex out com
    459       when (suc /= 1) $ throwIO Secp256k1Error
    460       let par = F.castPtr out
    461       enc <- BS.packCStringLen (par, _SIG_BYTES)
    462       pure (Sig enc)
    463 
    464 -- | Serialize a signature into a compact (64-byte) bytestring.
    465 --
    466 --   >>> wcontext $ \tex -> serialize_compact tex sig
    467 serialize_compact
    468   :: Context
    469   -> Sig
    470   -> IO BS.ByteString
    471 serialize_compact (Context tex) (Sig sig) =
    472   BS.useAsCString sig $ \(F.castPtr -> sip) ->
    473     A.allocaBytes _SIG_BYTES $ \out -> do
    474       -- always returns 1
    475       _ <- secp256k1_ecdsa_signature_serialize_compact tex out sip
    476       let enc = F.castPtr out
    477       BS.packCStringLen (enc, _SIG_BYTES)
    478 
    479 -- extrakeys
    480 
    481 -- | Convert a public key into an x-only public key (i.e. one with even
    482 --   y coordinate).
    483 --
    484 --   >>> wcontext $ \tex -> xonly tex pub
    485 --   "<bitcoin-core/secp256k1 x-only public key>"
    486 xonly :: Context -> Pub -> IO XOnlyPub
    487 xonly (Context tex) (Pub pub) =
    488   A.allocaBytes _PUB_BYTES_INTERNAL $ \out ->
    489     BS.useAsCString pub $ \(F.castPtr -> pup) -> do
    490       -- returns 1 always
    491       _ <- secp256k1_xonly_pubkey_from_pubkey tex out F.nullPtr pup
    492       let key = F.castPtr out
    493       pux <- BS.packCStringLen (key, _PUB_BYTES_INTERNAL)
    494       pure (XOnlyPub pux)
    495 
    496 -- | Parse a compressed (33-byte) or uncompressed (65-byte) public key into
    497 --   an x-only public key.
    498 --
    499 --   >>> wcontext $ \tex -> parse_xonly tex bytestring
    500 --   "<bitcoin-core/secp256k1 x-only public key>"
    501 parse_xonly
    502   :: Context
    503   -> BS.ByteString -- ^ compressed or uncompressed public key
    504   -> IO XOnlyPub
    505 parse_xonly (Context tex) bs =
    506   A.allocaBytes _PUB_BYTES_INTERNAL $ \out ->
    507     BS.useAsCString bs $ \(F.castPtr -> pub) -> do
    508       suc <- secp256k1_xonly_pubkey_parse tex out pub
    509       when (suc /= 1) $ throwIO Secp256k1Error
    510       let key = F.castPtr out
    511       pux <- BS.packCStringLen (key, _PUB_BYTES_INTERNAL)
    512       pure (XOnlyPub pux)
    513 
    514 -- | Serialize an x-only public key into a 32-byte bytestring
    515 --   representation.
    516 --
    517 --   >>> wcontext $ \tex -> serialize_xonly tex xonly
    518 serialize_xonly
    519   :: Context
    520   -> XOnlyPub
    521   -> IO BS.ByteString -- ^ serialized x-only public key
    522 serialize_xonly (Context tex) (XOnlyPub pux) =
    523   A.allocaBytes _PUB_BYTES_XONLY $ \out -> do
    524     BS.useAsCString pux $ \(F.castPtr -> pub) -> do
    525       -- returns 1 always
    526       _ <- secp256k1_xonly_pubkey_serialize tex out pub
    527       let enc = F.castPtr out
    528       BS.packCStringLen (enc, _PUB_BYTES_XONLY)
    529 
    530 -- | Derive a keypair from the provided 32-byte secret key.
    531 --
    532 --   >>> wrcontext entropy $ \tex -> keypair tex sec
    533 --   "<bitcoin-core/secp256k1 keypair>"
    534 keypair
    535   :: Context
    536   -> BS.ByteString -- ^ 32-byte secret key
    537   -> IO KeyPair
    538 keypair (Context tex) sec
    539   | BS.length sec /= 32 = throwIO CSecp256k1Error
    540   | otherwise = A.allocaBytes _KEYPAIR_BYTES $ \out ->
    541       BS.useAsCString sec $ \(F.castPtr -> key) -> do
    542         suc <- secp256k1_keypair_create tex out key
    543         when (suc /= 1) $ throwIO Secp256k1Error
    544         let enc = F.castPtr out
    545         per <- BS.packCStringLen (enc, _KEYPAIR_BYTES)
    546         pure (KeyPair per)
    547 
    548 -- | Extract a public key from a keypair.
    549 --
    550 --   >>> wrcontext entropy $ \tex -> keypair_pub tex keypair
    551 --   "<bitcoin-core/secp256k1 public key>"
    552 keypair_pub :: Context -> KeyPair -> IO Pub
    553 keypair_pub (Context tex) (KeyPair per) =
    554   A.allocaBytes _PUB_BYTES_INTERNAL $ \out ->
    555     BS.useAsCString per $ \(F.castPtr -> par) -> do
    556       -- returns 1 always
    557       _ <- secp256k1_keypair_pub tex out par
    558       let enc = F.castPtr out
    559       pub <- BS.packCStringLen (enc, _PUB_BYTES_INTERNAL)
    560       pure (Pub pub)
    561 
    562 -- | Extract a secret key from a keypair.
    563 --
    564 --   >>> wrcontext entropy $ \tex -> keypair_sec tex keypair
    565 keypair_sec
    566   :: Context
    567   -> KeyPair
    568   -> IO BS.ByteString -- ^ 32-byte secret key
    569 keypair_sec (Context tex) (KeyPair per) =
    570   A.allocaBytes _SEC_BYTES $ \out ->
    571     BS.useAsCString per $ \(F.castPtr -> par) -> do
    572       _ <- secp256k1_keypair_sec tex out par
    573       let enc = F.castPtr out
    574       BS.packCStringLen (enc, _SEC_BYTES)
    575 
    576 -- ecdh
    577 
    578 -- | Compute an ECDH secret key from the provided public key and
    579 --   (32-byte) secret key.
    580 --
    581 --   >>> wrcontext entropy $ \tex -> ecdh tex pub sec
    582 ecdh
    583   :: Context
    584   -> Pub
    585   -> BS.ByteString    -- ^ 32-byte secret key
    586   -> IO BS.ByteString -- ^ 32-byte secret key
    587 ecdh (Context tex) (Pub pub) sec
    588   | BS.length sec /= 32 = throwIO CSecp256k1Error
    589   | otherwise =
    590       A.allocaBytes _SEC_BYTES $ \out ->
    591         BS.useAsCString pub $ \(F.castPtr -> pup) ->
    592           BS.useAsCString sec $ \(F.castPtr -> sep) -> do
    593             suc <- secp256k1_ecdh tex out pup sep F.nullPtr F.nullPtr
    594             when (suc /= 1) $ throwIO Secp256k1Error
    595             let key = F.castPtr out
    596             BS.packCStringLen (key, _SEC_BYTES)
    597 
    598 -- schnorr
    599 
    600 -- | Sign a 32-byte message hash with the provided secret key, using the
    601 --   provided 32 bytes of fresh auxiliary entropy.
    602 --
    603 --   BIP340 recommends that 32 bytes of fresh auxiliary entropy be
    604 --   generated and added at signing time as additional protection
    605 --   against side-channel attacks (namely, to thwart so-called "fault
    606 --   injection" attacks). This entropy is /supplemental/ to security,
    607 --   and the cryptographic security of the signature scheme itself does
    608 --   not rely on it, so it is not strictly required; 32 zero bytes can
    609 --   be used in its stead.
    610 --
    611 --   The resulting 64-byte Schnorr signature is portable, and so is not
    612 --   wrapped in a newtype.
    613 --
    614 --   >>> import qualified System.Entropy as E  -- example entropy source
    615 --   >>> enn <- E.getEntropy 32
    616 --   >>> aux <- E.getEntropy 32
    617 --   >>> wrcontext enn $ \tex -> sign_schnorr tex msg sec aux
    618 sign_schnorr
    619   :: Context
    620   -> BS.ByteString    -- ^ 32-byte message hash
    621   -> BS.ByteString    -- ^ 32-byte secret key
    622   -> BS.ByteString    -- ^ 32 bytes of fresh entropy
    623   -> IO BS.ByteString -- ^ 64-byte signature
    624 sign_schnorr c@(Context tex) msg sec aux
    625   | BS.length msg /= 32 || BS.length sec /= 32 || BS.length aux /= 32 =
    626       throwIO CSecp256k1Error
    627   | otherwise = A.allocaBytes _SIG_BYTES $ \out ->
    628       BS.useAsCString msg $ \(F.castPtr -> has) ->
    629         BS.useAsCString aux $ \(F.castPtr -> enn) -> do
    630           KeyPair per <- keypair c sec
    631           BS.useAsCString per $ \(F.castPtr -> pur) -> do
    632             suc <- secp256k1_schnorrsig_sign32 tex out has pur enn
    633             when (suc /= 1) $ throwIO Secp256k1Error
    634             let enc = F.castPtr out
    635             BS.packCStringLen (enc, _SIG_BYTES)
    636 
    637 -- | Verify a 64-byte Schnorr signature for the provided 32-byte message
    638 --   hash with the supplied public key.
    639 --
    640 --   >>> wrcontext entropy $ \tex -> verify_schnorr tex pub msg sig
    641 verify_schnorr
    642   :: Context
    643   -> Pub
    644   -> BS.ByteString -- ^ 32-byte message hash
    645   -> BS.ByteString -- ^ 64-byte signature
    646   -> IO Bool
    647 verify_schnorr c@(Context tex) pub msg sig
    648   | BS.length msg /= 32 || BS.length sig /= 64 = throwIO CSecp256k1Error
    649   | otherwise =
    650     BS.useAsCString sig $ \(F.castPtr -> sip) ->
    651       BS.useAsCStringLen msg $ \(F.castPtr -> has, fromIntegral -> len) -> do
    652         XOnlyPub pux <- xonly c pub
    653         BS.useAsCString pux $ \(F.castPtr -> pax) -> do
    654           suc <- secp256k1_schnorrsig_verify tex sip has len pax
    655           pure (suc == 1)
    656