rust-secp256k1
Rust bindings to upstream libsecp256k1. Wraps the constant-time C implementation; safe API.
Repo: github.com/rust-bitcoin/rust-secp256k1. As of 0.33.1 (August 2026)
the crate's own metadata points at git.rust-bitcoin.org/rust-bitcoin/rust-secp256k1,
which is where upstream development now happens.
Install
[dependencies]
secp256k1 = { version = "0.33", features = ["rand"] }
Current release: 0.33.1, published to crates.io 2026-08-29. There is no
schnorr or musig Cargo feature - both modules are compiled
unconditionally; the opt-in features are std (default), alloc,
rand, recovery, lowmemory, global-context,
global-context-less-secure, serde, arbitrary.
0.32.0 was never released: the 0.32.0-beta.* crates are yanked and
0.33.0 (crates.io 2026-08-28) supersedes them.
Quick examples
Since 0.33.0 the Secp256k1 context is no longer threaded through the
public API - the free functions below use an internal global context.
Key generation
use secp256k1::{rand, PublicKey, SecretKey}; // secp256k1 re-exports rand
let mut rng = rand::rng(); // rand 0.9 renamed `thread_rng()` to `rng()`
let (sk, pk) = secp256k1::generate_keypair(&mut rng);
// equivalently
let sk = SecretKey::new(&mut rng);
let pk = PublicKey::from_secret_key(&sk);
ECDSA sign
use secp256k1::{ecdsa, Message};
let msg = Message::from_digest([0u8; 32]); // 32-byte sighash
let sig = ecdsa::sign(msg, &sk);
ecdsa::verify(&sig, msg, &pk)?;
Schnorr (BIP340)
use secp256k1::{schnorr, Keypair};
let kp = Keypair::from_secret_key(&sk);
let (xonly, _parity) = kp.x_only_public_key();
let sighash = [0u8; 32]; // BIP340 signs any-length msg
let sig = schnorr::sign_no_aux_rand(&sighash, &kp);
schnorr::verify(&sig, &sighash, &xonly)?;
MuSig2 (BIP327)
use secp256k1::musig::{
new_nonce_pair, AggregatedNonce, KeyAggCache, Session, SessionSecretRand,
};
let mut pubkeys = [&pk_a, &pk_b];
secp256k1::sort_pubkeys(&mut pubkeys); // BIP327 key sorting
let cache = KeyAggCache::new(&pubkeys);
let agg_pk = cache.agg_pk(); // XOnlyPublicKey
// round 1: new_nonce_pair(...) per signer -> AggregatedNonce::new(&nonces)
// round 2: Session::new(&cache, agg_nonce, msg).partial_sign(...)
// -> session.partial_sig_agg(&sigs).verify(&agg_pk, msg)
Use cases
- Low-level crypto without Bitcoin tx context.
- Building higher-level abstractions on top.
- Performance-critical paths where you avoid the rust-bitcoin layer.
Compared
rust-bitcoin re-exports secp256k1 types; for Bitcoin app code,
import from bitcoin::secp256k1::* for type compatibility.
Common pitfalls
Message::from_digest_sliceis deprecated (since 0.31.0) in favour ofMessage::from_digest, which takes a[u8; 32]array outright.ecdsa::verifytakes(sig, msg, pk): 0.33.0 reordered the oldverify_ecdsa(msg, sig, pk)args to matchschnorr::verify.- Pre-0.33 code threaded a
Secp256k1context through every call, and creating that context was expensive enough to hoist out of loops. The context methods (secp.sign_ecdsa,secp.verify_schnorr, ...) still exist but are deprecated since 0.33.0; the global context replacing them is rerandomized internally, andrerandomize_global_context(&seed)is the replacement for the oldSecp256k1::randomize. - 0.33.0 renamed
secret_bytestoto_secret_bytes(onKeypairandecdh::SharedSecret) and deprecatedSecretKey::from_slicein favour ofSecretKey::from_secret_bytes. - Unsafe /
unstablefeatures have additional risk. Keypairtypes vary slightly across releases; pin version.