v0.7.1
Major changes include:
- Wrapped
libsecp256k1 0.7.1
(1a53f49) - Wrapped the
ecdh,recovery,ellswift(BIP324) andmusig
(MuSig2) libsecp256k1 modules, besides the already wrapped
extrakeysandschnorrsigones; newecdh,recovery, and
ellswiftbinding modules, while MuSig2 is available through the raw
cffi bindings only, by decision: its two-round protocol needs a
session whose secret nonce cannot be reused, which is a property of an
object's lifetime and belongs where the signing state lives, in
btclib; what has no state in it is wrapped here (key ordering and
aggregation, taproot tweaking, tagged hashing, and the BIP340
verification an aggregate signature reduces to) - New
keysbinding module: private and public key algebra (verify,
negate, tweak add and multiply, combine), including the
multiplication of an arbitrary point, whichmultdoes not provide - New
xonlybinding module: BIP341 taproot tweaking of x-only public
keys (tweak_add,tweak_add_check) and of the corresponding
private keys, so that a key path spending can be signed withssa - New
hashesbinding module: the BIP340 tagged hash
(tagged_sha256), which the taproot tags of BIP341 (TapLeaf,
TapBranch, TapTweak) and the BIP340 challenge are built on keysalso provides the lexicographic ordering of public keys
(pubkey_cmp,pubkey_sort), which is the one of a BIP67 multisig
script and the one MuSig2 key aggregation applies by default- New
ssa.sign_custom: BIP340 signing of a message of any length,
whichsigncannot do andverifycould already check dsanow exposes the signature malleability primitives
(normalize,is_low_s) and the conversions between the DER and the
64-byte compact encodings (to_compact,to_der)dsa.verifyandssa.verifyreturnboolinstead ofintssa.verifytakes the 32-byte x-only public key BIP340 verifies
against, and so doxonly.tweak_addandxonly.tweak_add_check: a
full public key used to be accepted and reinterpreted as its even y
point, which is a different key from the one passed whenever y is odd.
xonly.from_pubkeyis that conversion, and making it is the caller's
decision- the 32 bytes of nonce entropy are 32 bytes or omitted:
ndata
(dsa.sign,recovery.sign),aux_rand32(ssa.sign,
ssa.sign_custom,ellswift.create) andrnd32(ellswift.encode)
used to be left padded when shorter, turning a caller mistake into a
valid argument; a wrong length now raisesValueError. The boundary
checks what C cannot see and normalizes nothing else, which the README
states as such under What the boundary checks - All the wrapped modules are now requested explicitly at configure
time (recovery, in particular, is disabled by default upstream):
upstream defaults are not part of its API, and the autotools and
CMake build paths were enabling different module sets - The vendored library is now built with CMake on every platform, in a
single build path: autotools (on POSIX) and mingw cross-compilation
(through--host) are gone, and with them the need for automake,
libtool, pkg-config and autoconf. CMake is declared as a build
requirement, so that installing the sdist provisions it instead of
demanding a package manager step: only a C toolchain is needed - The build no longer writes inside the vendored tree: the callback
stubs are added to the library target from the CMake binary
directory, which is outside the submodule, sogit reset --hardand
git clean -fxdon the submodule (which used to discard any local
change to it) are gone, and a wheel built on Windows can no longer
ship the CMake build tree in an sdist - Fixed the shared object lookup of a dynamic build giving up on the
first candidate directory, which the CMake layout (POSIX libraries in
lib, Windows DLLs inbin) would have hit - New versioning scheme: release numbers now track the wrapped
libsecp256k1 version; binding-only releases append a fourth number
(0.7.1.1, 0.7.1.2, etc.) - Added support for Python 3.13 and 3.14
- Dropped support for Python 3.7 and 3.8 (minimum is now 3.9):
both are end-of-life (3.7 since June 2023, 3.8 since October 2024)
and the current build/test infrastructure cannot support them anymore,
as cibuildwheel 4.x does not build cp37/cp38 wheels and GitHub-hosted
runners no longer provide those interpreters - Added macOS arm64 and Linux aarch64 wheels
- Added native Windows wheels for CPython, built with CMake/MSVC via
cibuildwheel, for both x86_64 and arm64 (win_arm64from CPython 3.11,
the first version with a Windows arm64 build); the mingw
cross-compiled dynamic wheel is still provided, x86_64 only - Dynamic wheels now carry the platform tag of the interpreter they are
built with (it was hardcoded to x86_64, with a fake macOS 10.16
minimum version) - The vendored library is now built for the architecture of the
interpreter, which is the one its toolchain compiles the extension
for, instead of the one CMake infers from the host: an emulated
interpreter (an x86-64 CPython on Windows arm64, which is what uv
installs there by default) used to get an archive of the wrong
architecture, and every libsecp256k1 symbol unresolved at link time - Updated CI to current GitHub Actions runners and actions,
cibuildwheel 4.x (static wheels now cp39-cp314, PyPy opt-in) - Hardened CI: least-privilege GITHUB_TOKEN (contents: read), actions
pinned to commit SHAs, superseded runs auto-cancelled - New release workflow: tag-triggered, publishing to PyPI with Trusted
Publishing (OIDC, no long-lived tokens) and PEP 740 attestations,
behind a manual approval gate - New
SECURITY.md: how to report a vulnerability, and where the
boundary with libsecp256k1 lies, as the cryptography is upstream and
what is ours is the layer driving it - Updated all pre-commit hooks to their latest versions
- Replaced black, isort, flake8, autoflake, pyupgrade, bandit,
pydocstringformatter and yesqa with ruff: one linter and formatter,
one configuration section - mypy now runs in strict mode; the cffi extension module is described
by a hand-written stub (stubs/_btclib_libsecp256k1.pyi), without
whichffiandlibareAnyand the whole package type-checks
vacuously. Opaque libsecp256k1 handles are spelledCDatain the
public signatures that return them - The package now exposes
__version__ - CI additionally gates on the pre-commit hooks, on branch coverage
with afail_underratchet, on installing from the sdist (the only
path that compiles libsecp256k1 on the user's machine), and on the
twine check --strictandcheck-wheel-contentsvalidation of every
wheel and sdist before a release can reach PyPI - Development environment managed by uv,
with PEP 735 dependency groups and the interpreter pinned in
.python-version; the hatch environments and the noxfile are gone - PEP 639 license metadata (
license = "MIT"pluslicense-files,
replacing the deprecated license table and theLicense ::
classifier) - An import failure of the extension now raises
ImportErrornaming
the directory searched, instead of a bareNameError - Fixed sdist builds with multi-pass PEP 517 frontends such as uv:
the callback stubs are now a separate compilation unit,
no longer mutating the vendored sources (#20) - Build scripts now fail fast on subprocess errors
- Tests now cover the official BIP340 test vectors, the published
RFC6979 deterministic ECDSA vectors for secp256k1, the secp256k1-py
ECDSA vectors used by btclib, DER edge cases, and the error paths
of the bindings: every line and branch reachable through the API is
covered, thefail_underratchet is at 100%, and the unreachable
RuntimeErrorpaths are excluded from the measure instead of being
counted as a gap that could never be closed - New invariant tests, over inputs derived from a SHA256 chain rather
than chosen: round trips, the scalar operations agreeing with the
point ones, and the two sides of ECDH and of BIP324 agreeing. They
reach what a fixed key cannot, such as a public key whose x
coordinate starts with a zero byte or a signature whose DER encoding
is 69 bytes, both pinned as well; no test dependency is added, the
suite running inside every wheel test environment too - Bindings now validate inputs and check libsecp256k1 return codes,
raisingValueErrorwith a clear message; malformed keys and
signatures were previously verified against uninitialized memory - A single shared libsecp256k1 context is created with the modern
SECP256K1_CONTEXT_NONEflag (the SIGN/VERIFY flags are deprecated)
and randomized to protect against side-channel leakage - The shared context now records what libsecp256k1 reports through its
illegal argument and internal error callbacks, and the new
context.check()raises it, the failed precondition verbatim, as a
ValueErroror aRuntimeError. The do-nothing stubs of the vendored
build keep an illegal argument from aborting the process, but left the
caller a bare 0 and no reason for it; that is unreachable through the
bindings, which validate their arguments first, and matters for a call
made throughlib, as a MuSig2 session is: signing twice with the
same secret nonce is refused by libsecp256k1 through that very
callback. What is recorded is per thread, as the callback runs on the
thread of the call that triggered it - The bindings are documented as safe to call concurrently, and tested
for it: the shared context is only mutated at import time, and a
wheel is built for the free-threaded interpreter (cp314t), where
those calls are no longer serialized