Skip to content

Releases: btclib-org/btclib-libsecp256k1

v0.7.1.3

Choose a tag to compare

@github-actions github-actions released this 08 Aug 23:33

The same
libsecp256k1 0.7.1
(1a53f49) as v0.7.1.2: no breaking changes.

  • A dynamic wheel's import failure says why. ImportError now names
    every shared-object candidate _load_lib rejected and chains the last
    loader error, instead of only "no loadable shared libsecp256k1 found".
  • cryptography moved to 50.0.0, closing CVE-2026-69247 — a PKCS#7
    decryption oracle pulled in transitively through the packaging tools,
    and unreachable from this package's own code either way.
  • Two README clarifications: why pubkey_from_prvkey is two
    libsecp256k1 calls rather than one, and which btclib functions guard a
    MuSig2 nonce against reuse.

v0.7.1.2

Choose a tag to compare

@github-actions github-actions released this 07 Aug 12:10

The same
libsecp256k1 0.7.1
(1a53f49) as v0.7.1: no breaking changes — nothing in the public API
moved, and the one wrapper whose behaviour changed changed the text of an
error message. What a fourth number carries here is one new function, the
contract of every function where a caller reads it, a static extension
compiled the way the interpreter compiles its own, and metadata that says
what the wheels are.

  • keys.pubkey_from_prvkey is the public key of a private key,
    compressed by default, and the 65 bytes of mult.mult_ with
    compressed=False — which is what mult_ now is. Generator
    multiplication was the one producer here that could not answer the
    compressed form, so a caller who wanted the 33 bytes had to parse and
    re-serialize what libsecp256k1 had just serialized, or slice the parity
    out of the 65 bytes themselves. mult_ answers exactly what it did;
    the one thing to know is that the ValueError of a scalar outside
    [1, n-1] now names a private key, which is what
    secp256k1_ec_pubkey_create calls its argument, so code matching on
    that message matches on the new text.
  • Every function documents its arguments, its return value and what it
    raises.
    That contract used to be prose in the README, several hundred
    lines from the functions it applies to, which for a package shipped as
    a compiled wheel is all a reader of help() or of an IDE tooltip
    could have had. mult.mult_ and mult.mult, which shared a docstring
    verbatim while returning 65 uncompressed bytes and a pair of ints, now
    each say which. A pydoclint hook keeps it that way.
  • The README opens with a Quickstart — sign and verify, ECDSA and
    BIP340 — and every example in it, and in every docstring, is executed
    by the suite on each interpreter and each kind of wheel. An example
    that stops working fails a build instead of sitting there.
  • The static extension is faster on Unix, and correct on universal2.
    It was compiled without the interpreter's own CFLAGS: no
    optimization at all, unlike everything CMake builds beside it, and on
    a universal2 interpreter a single-arch object inside a dual-arch
    bundle.
  • A wheel whose extensions disagree on static or dynamic is refused
    rather than reported. It would have been tagged py3-none-<platform>
    while holding an ABI-specific extension: installable on any
    interpreter of that platform, and broken on most of them.
  • The classifiers say what the package is: the three systems the
    wheels are built for instead of OS Independent, and Typing :: Typed, which is the classifier PyPI users filter on.
  • More of the library is held to published vectors: BIP324 for
    ellswift encoding and the ellswift_xdh shared secret, BIP327 for
    every step of MuSig2 aggregation, the four long BIP340 messages
    through ssa.sign_custom, and recovery ids 2 and 3, which no test had
    ever reached.
  • Two hooks that were passing without reading a file now read one.
    The copyright notice hook was checking only newly added staged files,
    so on a clean checkout — every run in CI — it checked nothing; the
    entropy detectors of detect-secrets were off for the whole tree
    rather than for the vector files that motivated it.
  • The release path checks before it builds what it used to discover
    after uploading
    : that the tag is on master, and that this file has
    a section for it. A rehearsal re-run gets a version of its own instead
    of colliding with itself on TestPyPI, and no longer shares a
    concurrency group with a push to the branch it was dispatched from.

v0.7.1.1

Choose a tag to compare

@github-actions github-actions released this 06 Aug 11:55

The same
libsecp256k1 0.7.1
(1a53f49) as v0.7.1, and the same bindings: no wrapper changed
behaviour, and nothing in the public API moved. What a fourth number
carries here is the floor the package is built on, its documentation,
and the gate around both.

  • Dropped support for Python 3.9 (minimum is now 3.10): it reached end
    of life in October 2025, and keeping it held uv.lock at a second
    resolution of its own, cffi 2.1 requiring 3.10 and cffi 2.0 staying
    pinned for 3.9 alone. It also cost the build matrix its widest cell,
    CPython having no Windows arm64 build before 3.11
  • Published documentation: a Sphinx build under docs/, served at
    Read the Docs, which the
    documentation metadata url now names instead of the README
  • The distribution names The btclib developers as its author and ships
    AUTHORS.md beside LICENSE and COPYRIGHT. The licence header of
    every source file is the short MIT one, so the three no longer state
    the same holder and the same terms three different ways
  • Three scheduled workflows join published, each asking on a morning
    of its own a question no pull request can: whether the urls in the
    prose still resolve (links), whether the tree survives every
    dependency at its newest (latest), and whether the suite would
    notice a wrong line (mutation). None of them gates a merge, and that
    is the design: what they report is the outside world moving, or a test
    nobody has written, and neither is something a branch can act on
  • Each vendored test vector is pinned to a commit and a blob SHA-1 in
    tests/README.md, so a citation names the revision actually copied
    rather than a file that changes underneath it, and a monthly workflow
    opens an issue when upstream moves one
  • New REPOSITORY.md: the branch rules, required checks, token
    permissions, publishing environments and scanning settings that live
    outside the tree and cannot be recovered by reading it, each with the
    API call that reads it back
  • The pre-commit gate, which is the one definition of clean and what CI
    runs rather than a second list of its own, now also measures the yaml,
    the prose and the packaging metadata, and checks itself. mypy runs
    with more of its optional error codes and ruff with more rule-sets,
    every one of which the tree already satisfied
  • More tests, and what each one verifies is now stated in its docstring
    and gated as such: the callbacks, the copyright headers, the
    documentation build and the vendored data are covered where they were
    not

v0.7.1

Choose a tag to compare

@github-actions github-actions released this 04 Aug 21:55
9563ebb

Major changes include:

  • Wrapped
    libsecp256k1 0.7.1
    (1a53f49)
  • Wrapped the ecdh, recovery, ellswift (BIP324) and musig
    (MuSig2) libsecp256k1 modules, besides the already wrapped
    extrakeys and schnorrsig ones; new ecdh, recovery, and
    ellswift binding 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 keys binding module: private and public key algebra (verify,
    negate, tweak add and multiply, combine), including the
    multiplication of an arbitrary point, which mult does not provide
  • New xonly binding 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 with ssa
  • New hashes binding module: the BIP340 tagged hash
    (tagged_sha256), which the taproot tags of BIP341 (TapLeaf,
    TapBranch, TapTweak) and the BIP340 challenge are built on
  • keys also 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,
    which sign cannot do and verify could already check
  • dsa now 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.verify and ssa.verify return bool instead of int
  • ssa.verify takes the 32-byte x-only public key BIP340 verifies
    against, and so do xonly.tweak_add and xonly.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_pubkey is 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) and rnd32 (ellswift.encode)
    used to be left padded when shorter, turning a caller mistake into a
    valid argument; a wrong length now raises ValueError. 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, so git reset --hard and
    git clean -fxd on 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 in bin) 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_arm64 from 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
    which ffi and lib are Any and the whole package type-checks
    vacuously. Opaque libsecp256k1 handles are spelled CData in the
    public signatures that return them
  • The package now exposes __version__
  • CI additionally gates on the pre-commit hooks, on branch coverage
    with a fail_under ratchet, on installing from the sdist (the only
    path that compiles libsecp256k1 on the user's machine), and on the
    twine check --strict and check-wheel-contents validation 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" plus license-files,
    replacing the deprecated license table and the License ::
    classifier)
  • An import failure of the extension now raises ImportError naming
    the directory searched, instead of a bare NameError
  • 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, the fail_under ratchet is at 100%, and the unreachable
    RuntimeError paths 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,
    raising ValueError with 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_NONE flag (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
    ValueError or a RuntimeError. 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 through lib, 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

v0.4.0

Choose a tag to compare

@giacomocaironi giacomocaironi released this 07 Dec 14:27
48a0d56

Major changes include:

v0.3.0

Choose a tag to compare

@giacomocaironi giacomocaironi released this 12 Jul 06:52
55f5fef

Major changes include:

  • Wrapped libsecp256k1 0.3.2(acf5c55)
  • Build platform wheels using cibuildwheel
  • Switched from setuptools to hatch
  • Improved project standards (pyproject.toml, nox)

v0.2.1

Choose a tag to compare

@giacomocaironi giacomocaironi released this 30 Jan 17:54
b086279

Major changes include:

  • Fixed bug in mult

v0.2.0

Choose a tag to compare

@giacomocaironi giacomocaironi released this 27 Jan 11:35
b4f8816

Major changes include:

  • Wrapped libsecp256k1 0.2.0(21ffe4b)
  • Increased test coverage
  • Improved project standards (pre-commit hooks, mypy, tox)

v0.1.1

Choose a tag to compare

@giacomocaironi giacomocaironi released this 12 Jul 06:55

Major changes include:

  • Fixed mult return type

v0.1

Choose a tag to compare

@giacomocaironi giacomocaironi released this 12 Jul 06:54

Major changes include:

  • Wrapped libsecp256k1(3efeb9da)
  • Updated nonce generation
  • Added mult module