The Core Issue: libsecp256k1, Bitcoin’s Cryptographic Heart
Common phrases heard among Bitcoiners include “don’t trust, verify” or “not your keys, not your coins”, sometimes even claiming that it’s “backed by math”. But what do these proverbs ultimately boil down to, and how exactly is this involved math put into practice? Most readers are surely aware that a fundamental ingredient in the design of Bitcoin is public-key cryptography and more specifically digital signatures, which are essential to prove ownership without needing a central entity. Probably less well-known is what piece of software is under the hood to make that elliptic curve math work and what efforts are involved to ensure that this happens in the most secure and performant way, with continuous improvements. Let’s dive into the exciting history and evolution of “libsecp256k1”, a library that started out as a small hobby project and over the years evolved into an essential part of consensus rules protecting a multi-trillion dollar asset.
The Genesis
For reasons we don’t know for sure, Satoshi picked an elliptic curve named “secp256k1” for creating and verifying digital signatures in Bitcoin. The initial version of the Bitcoin client was shipped using the widespread OpenSSL library for signing and verifying transactions. Relying on a third-party library sounds like a reasonable approach from a software engineering perspective (even more so if it is something as domain-specific and complex as elliptic-curve
cryptography), but this choice turned out to be problematic later due to inconsistencies in the signature parsing code. In the worst case, this could even lead to unintended chain splits. One lesson from that time period was that OpenSSL is not a suitable library for a consensus-critical system like Bitcoin. The issue was later fixed by BIP66, which ensured a strict encoding of ECDSA signatures. After that, the OpenSSL dependency was replaced with libsecp256k1 in Bitcoin Core v0.12, released in early 2016.1
But taking a step back, the initial motivation behind starting the libsecp256k1 project was mostly curiosity about a potential speed-up. Sometime in the year 2012, Bitcoin Core developer Pieter Wuille a.k.a. “sipa” stumbled upon a bitcointalk thread by Hal Finney (footnote: known for being the recipient of the very first Bitcoin transaction in 2009 from Satoshi).
Under the subject “Speeding up signature verification”, the post discussed an optimization that would make use of a so-called “endomorphism” (footnote: more specifically using the so-called GLV-method, Gallant-Lambert-Vanstone), something that only certain elliptic curves allow, secp256k1 conveniently being one of them. Hal Finney himself implemented it using OpenSSL primitives, it was later even submitted as a PR to Bitcoin Core.2 Even though it showed a solid
~20% speedup, it was never merged in the end due to concerns about increasing code complexity and missing assurance that the involved cryptography is sound.
Pieter Wuille went ahead and decided to start a new library from scratch, with the initial commit of the “secp256k1” repository dating back to March 5th 2013. After only one week the library was able to verify the full blockchain (block height ~225000 at that time), within another week the signing functionality was implemented. It took some more time and testing until the library was ready to be used in Bitcoin Core as a replacement for OpenSSL, first for signing in the
wallet (release v0.10, 2015), and finally for ECDSA signature verification in consensus (release v0.12, 2016). The efforts were absolutely worth it: according to the PR description in Core, using libsecp256k1 for signature verification was “anywhere between 2.5 and 5.5 times faster”. Ironically, this didn’t yet include the earlier mentioned endomorphism optimization, since it wasn’t turned on by default due to worries about patent violation. It was only activated in the year 2020, after the patent expired (enabled in release v0.20), leading to another solid speed-up of around 16%.
Over time, the project attracted several other contributors. This naturally involved people that were closely working with Pieter from the start at Blockstream, namely then-CTO Gregory Maxwell and researcher Andrew Poelstra. In 2015, Jonas Nick and a few years later Tim Ruffing joined, both employed by Blockstream as researchers and now holding the role of maintainers of libsecp256k1 for several years. As they are responsible for both specifying new cryptographic
protocols (including detailed security proofs) and putting them into practice by implementing and reviewing them, it is very appropriate to call them “full-stack cryptographers”, as Tim Ruffing likes to describe himself.
Occasionally even cryptographers from outside the Bitcoin space have contributed to
libsecp256k1. One notable example of that is Peter Dettman, known for being one of the maintainers of the C#/Java cryptography library BouncyCastle, who up to this day shows up every now and then with various performance improvement suggestions. One of his major contributions was implementing modular inversion using the “safegcd” algorithm in 2021 to safely improve , following a paper by Daniel J. Bernstein and Bo-Yin Yang.
Why Reinvent The Wheel?
The goal of libsecp256k1 is to provide the highest quality library for cryptographic operations on the secp256k1 curve, with the primary intent of being useful in the broader Bitcoin ecosystem–Bitcoin Core is simply the main client using it. The API of libsecp256k1 is designed to be robust and hard to misuse, in order to prevent users from performing insecure operations (e.g. by rolling their own cryptographic schemes) that could lead to a loss of funds in the worst case. By focussing only on one elliptic curve and by limiting its functionality to operations
relevant to Bitcoin (that is, primarily signing and verifying transactions), the code can be both faster and simpler to review, leading to a lower maintenance burden and higher overall quality in comparison to other implementations. libsecp256k1 is written in C and doesn’t have any dependency on other libraries, so it only uses internal code written specifically for the project. As such it is designed to also run on constrained devices like micro-controllers, which are commonly used in hardware wallets.
Measure Twice, Cut Once
From very early on, libsecp256k1 had a strong focus on quality assurance that was continuously improved and honed over the years. Now it has a testing code coverage of close to 100%, and new modules only have a chance of getting merged if that bar is still met. In addition to that, there is also a special form of assurance called “exhaustive testing”. The basic idea is to exercise the functionality of the library for the whole space of possible values on the curve. As this would be infeasible on the actual secp256k1 curve, consisting of ~2^256 points, a special, much smaller but very similar curve is used which has an order that is merely in the double or triple digit range, so it can easily be executed within a reasonable amount of time. Another important part of testing is assurance of constant-time behaviour, which is particularly relevant for signing, as we will see below.
Schnorr: A Whole New World
Shifting our focus from QA to new features, one of the major milestones within the last decade in libsecp256k1, and in the Bitcoin protocol in general, was the introduction of Schnorr signatures. Being an essential part of the Schnorr/Taproot soft-fork activated in late 2021, they offer many advantages over ECDSA signatures, including being provably secure under standard assumptions, more compact, and enabling a whole lot of other constructions on top like key and signature aggregation for more efficient multisignature schemes. Both the specification in BIP340 and implementation was created by the current three maintainers of libsecp256k1, Pieter Wuille, Jonas Nick and Tim Ruffing.
libsecp256k1 Is Good For Your Node And The Network
It goes without saying that verifying digital signatures is one of the (if not the) most important and security-critical code paths of the Bitcoin consensus engine. No matter what complex script-paths and extra spending conditions might be included in some locking script, at the end there is likely at least one signature check involved in the transaction to ensure that it was actually created by the owner of the coins being spent. For such an essential operation, we want the code to be as robust, well-tested and performant as possible. Fast signature verification is also critical for both fast transaction and block propagation, and also to speed-up the Initial Block Download (IBD) for new participants in the network. We have already mentioned earlier the ~5x speedup when libsecp256k1 replaced OpenSSL for the first time about ten years ago. Over time, further performance improvements were implemented, and a recent investigation shows that libsecp256k1 is now about ~8x faster than OpenSSL for ECDSA signature verification using the most current version of each.3
Signing Can Be Dangerous, So Do It Right
So far we have focused on the verification functionality of libsecp256k1, being the most crucial for performance of node runners and miners. The other side of the coin (no pun intended!) is signing, i.e. the process of creating a digital signature for a transaction in order to spend funds. What makes this process delicate is the fact that secret key material is involved. If this material is in any way leaked, it could in the worst case lead to a catastrophic loss of funds, so special care has to be taken at the implementation level. libsecp256k1 tries to combat against so-called “side-channel attacks” by avoiding data-dependent branches, i.e. instances where different pieces of code are executed depending on what data is fed into it. This is a non-trivial task and takes some extra effort with regards to modern compilers, which are sometimes “too smart” in the sense that they try to optimize code while compiling it to software with resource saving branches where we explicitly don’t want that to happen. This is not just a theoretical concern, but has happened more than once, requiring patches to be shipped (e.g. releases 0.3.1 and 0.3.2). The important constant-time property is also tested using a tool called “valgrind” that was originally built for debugging memory issues. By using it to find any branching in code operating on secret data, we can detect if a potential side-channel risk exists.
Another way secret material could be leaked is by leaving it in memory unintentionally. Overwriting a memory region to make sure it is erased sounds trivial, but this has to be done in a way that prevents the compiler from getting in our way due to code optimization during compiling. Great care is taken to ensure that doesn’t occur.
Some Happy Accidents
More than once during the development of the library interesting things came up by surprise. In 2014, Pieter Wuille and Gregory Maxwell were already working on an extensive test suite for the library. One of the strategies to achieve a higher degree of assurance was verifying the behaviour of internal functions in the library against other implementations with special random inputs. This revealed a case where OpenSSL gave a wrong result when squaring a number, a serious security relevant bug filed as CVE-2014-3570 (“Bignum squaring may produce incorrect results.”).
In another instance a few years later, Pieter Wuille proposed a new method for computing a bound (or limit) on the number of iterations needed for the previously mentioned “safegcd” algorithm for computing modular inverses. This allowed shrinking that bound, leading to a faster computation. But it didn’t stop there. Mostly by accident, Gregory Maxwell discovered a different variant of Bernstein and Yang’s algorithm with even lower bounds, leading to another significant speedup both for signing and verification.
It’s noteworthy to mention that correctness (so, safety) of the “safegcd” implementation has been formally verified using a special theorem proving software called “Rocq” (formerly named “Coq”) and the “Verifiable C” program logic.4 This impressive work was done by Russell O’Connor and Andrew Poelstra, who state that the entirety of libsecp256k1 could be verified in the same way.
Cryptography Is Still Evolving
We have now shown that libsecp256k1 is primarily used for creating and verifying digital signatures in Bitcoin transactions, taking great care to do so in the safest and most efficient way possible, but it doesn’t stop there. Whenever other proposals are put forward that involve cryptographic operations on the secp256k1 curve (ideally formalized in a BIP) and are seen as overall beneficial for the Bitcoin ecosystem, the chances are good that the necessary code is considered in-scope for the library. In such a case, given enough developer time for implementation and review, it has good odds at winding up in a release of libsecp256k1. This has notably happened before with the ElligatorSwift module, a piece that was essential for enabling encryption for nodes’ P2P communication [see BIP324; discussed in-depth on page XXX], and most recently for MuSig2, a key aggregation scheme based on Schnorr signatures that allows creating n-on-n multi-signatures in a space-efficient and privacy-preserving way. There is also an ongoing effort to add a new module for Silent Payments, a proposal for a privacy-preserving static reusable address that doesn’t need interaction before payment between sender and receiver. And there is yet so much more to come: Batch Validation for Schnorr Signatures, DLEQ proofs, FROST, etc. Let’s see what the next 10 years of development in libsecp256k1 will bring!
Readers interested in libsecp256k1 are encouraged to take a look at and play around with secp256k1lab, a Python implementation of the secp256k1 curve that is intended for prototyping and experimentation.5
Get your copy of The Core Issue today!
Don’t miss your chance to own The Core Issue — featuring articles written by many Core Developers explaining the projects they work on themselves!
This piece is the Letter from the Editor featured in the latest Print edition of Bitcoin Magazine, The Core Issue. We’re sharing it here as an early look at the ideas explored throughout the full issue.
[1] https://gnusha.org/pi/bitcoindev/55B79146.70309@gmail.com/
[2] (#2061, https://github.com/bitcoin/bitcoin/pull/2061)
[3] https://delvingbitcoin.org/t/comparing-the-performance-of-ecdsa-signature-validation-in-openssl-vs-libsecp256k1-over-the-last-decade/2087?u=thestack
[4] [https://www.arxiv.org/abs/2507.17956]
[5] https://github.com/secp256k1lab/secp256k1lab/
This post The Core Issue: libsecp256k1, Bitcoin’s Cryptographic Heart first appeared on Bitcoin Magazine and is written by Sebastian Falbesoner.
