# secp256k1-haskell
[](https://hackage.haskell.org/package/secp256k1-haskell)
[](https://github.com/jprupp/secp256k1-haskell/actions/workflows/ci.yml)
[](https://github.com/jprupp/secp256k1-haskell/blob/master/LICENSE)
Haskell bindings to [libsecp256k1](https://github.com/bitcoin-core/secp256k1),
the library that Bitcoin Core uses for elliptic curve cryptography on the
secp256k1 curve. It covers ECDSA signatures, BIP-340 Schnorr signatures,
BIP-341 Taproot tweaks, key tweaking for BIP-32 derivation, and ECDSA public
key recovery for Bitcoin signed messages.
- [API documentation](https://hackage.haskell.org/package/secp256k1-haskell)
- [Changelog](https://github.com/jprupp/secp256k1-haskell/blob/master/CHANGELOG.md)
- [Report a bug](https://github.com/jprupp/secp256k1-haskell/issues)
## Contents
- [Why secp256k1-haskell](#why-secp256k1-haskell)
- [Design principles](#design-principles)
- [Features](#features)
- [Installation](#installation)
- [Quick start](#quick-start)
- [Tutorial: sign, verify and serialize](#tutorial-sign-verify-and-serialize)
- [Concepts](#concepts)
- [Usage guide](#usage-guide)
- [Differences from Bitcoin Core](#differences-from-bitcoin-core)
- [Module overview](#module-overview)
- [Security notes](#security-notes)
- [Development](#development)
- [Related projects](#related-projects)
- [License](#license)
## Why secp256k1-haskell
Elliptic curve cryptography is easy to write and very hard to get right. A
signing routine whose running time depends on the secret key leaks it through
timing. A nonce that repeats once reveals the key outright. An edge case in
point arithmetic accepts a forged signature. These are not theoretical: each has
cost real users real money.
libsecp256k1 is the answer the Bitcoin community converged on. It is
constant-time wherever secrets are involved, blinds secret key operations
against side channels, is tested exhaustively, and has been reviewed for over a
decade. It is what Bitcoin Core runs.
This package lets a Haskell program use that library instead of reimplementing
it, and wraps it in types that make the usual mistakes hard to make. It is the
cryptographic foundation of [haskoin-core](https://github.com/jprupp/haskoin-core)
and, through it, of the rest of the Haskoin libraries.
## Design principles
- **Valid by construction.** Every key, signature, hash and tweak goes through
a smart constructor or an import function that checks it. A `PubKey` is
always a point on the curve; a `Sig` always has both components in range.
Invalid input is refused where it enters the program, not deep inside a
computation.
- **Never abort the process.** By default libsecp256k1 calls `abort()` on an
illegal argument, which kills the whole Haskell program and cannot be caught.
Every context this package creates installs a callback that makes the C
function fail instead, so a bad argument becomes `Nothing`, `False` or an
exception.
- **Pure where the mathematics is pure.** Signing with deterministic nonces,
verifying, deriving and tweaking keys are pure functions. Only operations
that need fresh randomness run in `IO`.
- **Standard encodings at the edges.** `Show`, `Read` and `IsString` use SEC1,
compact or plain hex, never libsecp256k1's internal memory layout, which
differs between platforms and library versions.
- **Explicit about Bitcoin Core.** Where Bitcoin Core goes beyond the
standards, such as grinding signatures to a low R value or accepting high-S
signatures from old blocks, this package offers the behaviour as a separate,
documented function instead of silently picking one.
- **Thin.** Hashing, address encoding, BIP-32 paths and transaction formats
belong in higher-level libraries such as haskoin-core.
## Features
| Area | What you get |
| -------------------- | ---------------------------------------------------------------------------------------------- |
| ECDSA | Deterministic RFC 6979 signing, Bitcoin Core's low-R grinding, verification, lower-S normalization |
| Encodings | SEC1 public keys (33 or 65 bytes), DER and compact signatures, hex `Show`/`Read`/`IsString` |
| Schnorr (BIP-340) | Signing (deterministic, with given or fresh auxiliary randomness), verification, x-only keys |
| Taproot (BIP-341) | Tweaking an x-only public key and its secret key |
| Key arithmetic | Add or multiply secret and public keys by a tweak, negate, combine public keys |
| Recovery | Recoverable ECDSA signatures and public key recovery, as Bitcoin signed messages use |
| Safety | Validated values, no aborts, length checks before every C call, contexts shared across threads |
| Testing | QuickCheck `Arbitrary` instances for secret keys, hashes and tweaks |
### What is not included
- No hashing. Pass a 32-byte hash you computed with a hash library, such as
`cryptohash-sha256` or haskoin-core.
- No ECDH, MuSig2 or adaptor signatures, although libsecp256k1 or its
forks offer some of them.
- No secure erasure of secret keys from memory. See [Security notes](#security-notes).
## Installation
### System library
The package needs [libsecp256k1](https://github.com/bitcoin-core/secp256k1)
**0.2.0 or later** and `pkg-config` to find it. Public key recovery also needs
the library's recovery module, which the packages below include.
```sh
# Debian and Ubuntu (24.04 ships 0.2.0)
sudo apt-get install libsecp256k1-dev pkg-config
# Fedora
sudo dnf install libsecp256k1-devel pkgconf
# macOS with Homebrew
brew install secp256k1 pkg-config
# Nix
nix-shell -p secp256k1 pkg-config
```
Check what you have:
```sh
pkg-config --modversion libsecp256k1 # must print 0.2.0 or later
```
Older distributions, such as Debian 11, ship an earlier version. Build the
library from source then:
```sh
git clone https://github.com/bitcoin-core/secp256k1.git
cd secp256k1
./autogen.sh
./configure --enable-module-recovery
make && sudo make install
```
If your library was built without the recovery module, build this package with
the `recovery` flag off: `stack build --flag secp256k1-haskell:-recovery`, or
`cabal build -f -recovery`. Everything except `Crypto.Secp256k1.Recovery` still
works.
### Step by step with Stack
1. Install [Stack](https://docs.haskellstack.org/) and the library above.
2. Create a project:
```sh
stack new sig-demo simple
cd sig-demo
```
3. Add `secp256k1-haskell` and `bytestring` to `build-depends` in
`sig-demo.cabal`.
4. If your resolver does not include the package, add it to `stack.yaml`:
```yaml
extra-deps:
- secp256k1-haskell-2.0.1
- base16-2.0
```
5. Put the [quick start](#quick-start) program in `src/Main.hs` and run it with
`stack run`. It prints `True` three times.
### With Cabal
```cabal
build-depends:
base
, bytestring
, secp256k1-haskell
```
Run `cabal update` first, then `cabal build`.
## Quick start
```haskell
{-# LANGUAGE OverloadedStrings #-}
import Crypto.Secp256k1
main :: IO ()
main = do
ctx <- createContext
-- Hex literals go through IsString, which validates them.
let key = "c7e5a2b4f4c5d7e8a9b0c1d2e3f405162738495a6b7c8d9e0f1a2b3c4d5e6f70" :: SecKey
hash = "4b688df40bcedbe641ddb16ff0a1842d9c67ea1c3bf63f3e0471baa664531d1a" :: Msg
pub = derivePubKey ctx key
-- ECDSA
let s = signMsg ctx key hash
print (verifySig ctx pub s hash)
-- BIP-340 Schnorr, with fresh auxiliary randomness
schnorr <- signSchnorrIO ctx key hash
print (verifySchnorr ctx pub schnorr hash)
-- Round trip through the standard encodings
let Just pub' = importPubKey ctx (exportPubKey ctx True pub)
print (pub == pub')
```
## Tutorial: sign, verify and serialize
This walks through what a wallet or a protocol implementation typically does,
and why each step looks the way it does.
### 1. Create one context and share it
```haskell
ctx <- createContext
```
A context holds precomputed tables and random blinding values. Creating one is
relatively expensive, and one context can be used by every thread for the life
of the program. `createContext` randomizes it once, which is what libsecp256k1
recommends.
### 2. Get keys from bytes
```haskell
case secKey keyBytes of
Nothing -> fail "not a valid secret key"
Just key -> ...
```
`secKey` refuses 0 and numbers not below the curve order, so a `SecKey` is
always usable. In practice, keys come from a wallet: from a BIP-39 mnemonic and
BIP-32 derivation in haskoin-core, or from 32 bytes of the operating system's
secure randomness (`getEntropy 32` from the `entropy` package).
### 3. Hash, then sign
The library signs 32-byte hashes, not messages. Which hash to use is part of
the protocol: Bitcoin transactions use double SHA-256 of a specific
serialization, BIP-340 uses tagged hashes. Build the `Msg` from your hash:
```haskell
let Just m = msg hashBytes
s = signMsgLowR ctx key m -- or signMsg for plain RFC 6979
```
Signing is deterministic: the same key and hash give the same signature, so
there is no random number generator to get wrong.
### 4. Serialize for the wire
Never send the `get` bytes of a `PubKey` or `Sig`: they are libsecp256k1's
internal layout. Use the standard encodings:
```haskell
let pubBytes = exportPubKey ctx True pub -- 33-byte compressed SEC1
derBytes = exportSig ctx s -- DER, as in Bitcoin transactions
compactBytes = exportCompactSig ctx s -- 64 bytes r || s
```
### 5. Parse and verify what you receive
```haskell
verified = fromMaybe False $ do
pub <- importPubKey ctx pubBytes
s <- importSig ctx derBytes
m <- msg hashBytes
return (verifySig ctx pub (normalizeSig ctx s) m)
```
Every import returns `Maybe`, so malformed input is handled in one place.
`normalizeSig` is there because `verifySig` only accepts lower-S signatures;
apply it when the signature may come from software that does not produce them,
such as old Bitcoin transactions. Leave it out when your protocol requires
lower-S, as Bitcoin's relay rules do.
### 6. Move to Schnorr where the protocol allows
```haskell
sig <- signSchnorrIO ctx key m
let ok = verifySchnorr ctx pub sig m
xonly = exportXOnlyPubKey ctx pub -- 32 bytes, as BIP-340 publishes keys
```
Schnorr signatures are always 64 bytes, are not malleable, and are what Taproot
uses. `signSchnorrIO` mixes fresh randomness into the nonce, as BIP-340
recommends, to protect against fault attacks.
## Concepts
### Values
| Type | What it holds | Built with |
| ------------ | -------------------------------------------------------- | --------------------------------------- |
| `SecKey` | 32-byte number from 1 to n − 1 | `secKey`, `IsString` |
| `PubKey` | A point on the curve | `importPubKey`, `derivePubKey` |
| `Msg` | 32-byte hash | `msg` |
| `Sig` | ECDSA signature with both components from 1 to n − 1 | `signMsg`, `importSig`, `importCompactSig` |
| `CompactSig` | 64 bytes, r ‖ s | `exportCompactSig`, `compactSig` |
| `SchnorrSig` | 64-byte BIP-340 signature | `signSchnorr`, `schnorrSig` |
| `Tweak` | 32-byte number from 0 to n − 1 | `tweak` |
| `RecSig` | ECDSA signature with its recovery id | `signRecMsg`, `importCompactRecSig` |
Here n is the order of the curve. The raw constructors are hidden, so these
are the only ways to build values. Each type has a `get` field with its bytes.
### Contexts
Every operation takes a `Ctx`. It is created once, shared freely between
threads, and destroyed by the garbage collector. The only operation that
modifies a context is `randomizeContext`, which needs exclusive access; most
programs never call it.
### Errors
Parsing and constructing return `Maybe`. Arithmetic that can produce an
invalid result, such as a tweak that cancels a key, returns `Maybe`. Pure
operations on valid values, such as signing, cannot fail and return their
result directly. Programming errors, such as a value of the wrong length built
with an internal constructor, raise an exception.
## Usage guide
### Lower-S and low-R
```haskell
isLowS ctx s -- the form verifySig accepts
isLowR ctx s -- r below 2^255, so DER is at most 70 bytes
normalizeSig ctx s -- the lower-S form of any signature
```
### Key tweaking
```haskell
let Just t = tweak tweakBytes
Just childKey = tweakAddSecKey ctx parentKey t
Just childPub = tweakAddPubKey ctx parentPub t
-- derivePubKey ctx childKey == childPub
```
This is the operation behind BIP-32 derivation; the tweak itself comes from an
HMAC of the chain code, computed by haskoin-core. `tweakMulSecKey`,
`tweakMulPubKey`, `tweakNegate` and `combinePubKeys` cover the other group
operations.
### Taproot
```haskell
let Just t = tweak taprootTweakBytes -- tagged hash of key and script tree
Just outputKey = tweakAddXOnlyPubKey ctx internalKey t
Just outputSecKey = tweakAddXOnlySecKey ctx internalSecKey t
```
Computing the tweak, a BIP-341 tagged hash, is left to the caller.
### Bitcoin signed messages
```haskell
import Crypto.Secp256k1.Recovery
let r = signRecMsg ctx key m
(compact, recId) = exportCompactRecSig ctx r
-- the 65-byte message signature is a header byte (27 + recId, + 4 if the key
-- is compressed) followed by the 64 compact bytes
recover ctx r m -- Just the signer's PubKey
```
### Testing with QuickCheck
`SecKey`, `Msg` and `Tweak` have `Arbitrary` instances, so properties can
range over them directly. With a context `ctx` created once for the test suite:
```haskell
prop_roundTrip :: SecKey -> Msg -> Bool
prop_roundTrip key m =
verifySig ctx (derivePubKey ctx key) (signMsg ctx key m) m
```
## Differences from Bitcoin Core
| Topic | This package | Bitcoin Core |
| -------------------------- | ------------------------------------------------ | ------------------------------------- |
| ECDSA nonce | `signMsg`: plain RFC 6979 | Grinds for low R |
| Low-R signatures | `signMsgLowR`: same grinding, same signatures | Default since 0.17 |
| High-S signatures | `verifySig` refuses them; use `normalizeSig` | Normalizes before verifying |
| Verify after ECDSA signing | No | Yes, as a precaution |
| Taproot tweak when signing | Separate step: `tweakAddXOnlySecKey` | Built into Schnorr signing |
| Message signatures | `signRecMsg`, no grinding | Same, so signatures are identical |
## Module overview
| Module | Contents |
| --------------------------------------- | --------------------------------------------------------- |
| `Crypto.Secp256k1` | The public API: contexts, keys, ECDSA, Schnorr, tweaks |
| `Crypto.Secp256k1.Recovery` | Recoverable signatures and public key recovery |
| `Crypto.Secp256k1.Internal.*` | Raw constructors and foreign imports; no stability promise |
## Security notes
- **Secret keys stay in memory.** A `SecKey` is an ordinary `ByteString`. The
garbage collector may copy it, and nothing clears it when it is no longer
used. If your threat model includes an attacker reading process memory,
keep keys in a hardware device instead.
- **`Show` prints secret keys.** Showing a record that holds a `SecKey`, in a
log line or an error message, reveals it.
- **Verification only proves what was signed.** It is your protocol's job to
hash the right data; signing a hash an attacker chose is as good as signing
whatever they want.
- **Use fresh randomness for Schnorr when you can.** `signSchnorrIO` protects
against fault attacks that deterministic signing does not.
- **Keep libsecp256k1 up to date.** Security fixes land in the C library;
this package only binds it.
## Development
```sh
git clone https://github.com/jprupp/secp256k1-haskell.git
cd secp256k1-haskell
stack build # build against the system library
stack --nix build # or take GHC and libsecp256k1 from nixpkgs
stack test # run the tests
stack haddock --no-haddock-deps # build the API documentation
```
`package.yaml` is the source of the package description. Change it rather than
`secp256k1-haskell.cabal`, which [hpack](https://github.com/sol/hpack)
generates.
The tests are in `test/` and use [hspec](https://hspec.github.io/) and
QuickCheck, with published test vectors from RFC 6979, Bitcoin Core and
BIP-340. CI runs them with the system library and
with `stack --nix`.
### Contributing
Pull requests are welcome.
- Add tests for the behaviour you change, with published test vectors where
they exist.
- Keep the documentation in step with the code, as `CLAUDE.md` describes.
- Describe the change in `CHANGELOG.md`, which follows
[Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Related projects
- [haskoin-core](https://github.com/jprupp/haskoin-core) builds Bitcoin keys,
addresses, transactions and PSBTs on this package.
- [libsecp256k1](https://github.com/bitcoin-core/secp256k1) is the C library
underneath.
## License
secp256k1-haskell is released under the
[MIT License](https://github.com/jprupp/secp256k1-haskell/blob/master/LICENSE).