Changelog

An entry for anything a reader would notice: what changed, and the issue it answers. That is section 9 of the organization standard, and it is narrower than “every change” — a comment reworded inside a workflow changes nothing a reader of this repository meets, and lands without an entry. RELEASE_NOTES.md has the release notes, which say what a user has to act on; this file is the record behind them.

Neither file states how many entries it holds: a stated number is a line every open branch has to edit.

v2026.11 (work in progress, not released yet)

The catalogue no longer tests its primes at import

The catalogue builds its curves without testing p and n prime, and the test suite tests them. A caller’s curve is checked as before (closes #112).

ECDSA key recovery answers only points of the subgroup

On a curve with a cofactor above 1, key recovery refuses a lifted point outside the subgroup generated by G, so every key it answers is one point_from_pub_key accepts. secp112r2 and secp128r2 answered keys outside it (closes #125).

dependabot.yml does not say that every workflow passes --locked

.github/dependabot.yml says the workflows install from uv.lock with --locked, bar the exceptions CONTRIBUTING.md lists (issue btclib-org/.github#1538).

.gitattributes marks where the shared copy ends

The -text lines for the vendored files sit below a ## This repository in particular heading, so they are not compared with the organization’s copy (issue btclib-org/.github#1543).

REPOSITORY.md reads back the web sign-off setting

REPOSITORY.md reads web_commit_signoff_required back, the organization setting section 11 of the standard states (issue btclib-org/.github#1540).

The Sign-off check is required

CONTRIBUTING.md’s shared half says a pull request whose commits lack the Signed-off-by: trailer cannot merge, and REPOSITORY.md lists lint / Sign-off among the required checks (issue btclib-org/.github#1550).

CONTRIBUTING.md says the ack of record is a bot’s

The review says the maintainer lands their own pull requests through the bypass, and that the ack of record is a bot’s (issue btclib-org/.github#452).

The bypass is for emergencies

CONTRIBUTING.md, RELEASING.md, REPOSITORY.md and REVIEWING.md say every pull request lands with an approving review from somebody other than its author; the bypass is for emergencies (issue btclib-org/.github#1362).

Earlier entries on how a pull request lands

Entries above that have the maintainer landing without another person’s approval describe the rule before issue btclib-org/.github#1362 (issue btclib-org/.github#1569).

The release notes name GHSA-r5pw-9wrg-m3mj, fixed in v2026.10.2

The open section of the release notes names the advisory v2026.10.2 fixed, and says what a user of ssa on secp112r2 and secp128r2 does about it (closes #135).

The forms set a type, the history files have no merge driver

Issue forms set type:; .gitattributes loses merge=union and the heading the entry above added; a release reviews the bestpractices.dev answers (issue btclib-org/.github#1582).

The post-release install refreshes the index

RELEASING.md installs the release with --refresh-package btclib-ecc, because uv can answer from a cached index and miss a version just published (issue btclib-org/.github#1595).

--admin waits for no required check

CONTRIBUTING.md’s emergency paragraph says --admin skips the required checks too, and REVIEWING.md’s “hold the merge” excepts it (issue btclib-org/.github#1597).

Release notes name a change to the release’s assets

RELEASING.md asks that RELEASE_NOTES.md name a change to the release’s assets or to how they are verified. RELEASE_NOTES.md names the .intoto.jsonl bundle (issue btclib-org/.github#1596).

check-changelog refuses an entry added to an older release

A ### heading under a release older than the newest, absent at the merge base with origin/main, is refused (issue btclib-org/.github#1614).

The symbol set follows upstream’s tip

tests/_data/secp256k1_symbols.txt is pinned at bitcoin-core/secp256k1’s 186eec1c1b93d71d2131cd228ca16b4b97738f60 and gains secp256k1_ecmult_const_ge and secp256k1_ecmult_const_gej (closes #146).

rangeproof.sign warns about its nonce, _grind_low_r cites BIP461

rangeproof.sign’s docstring says nonce must be unique, never reused and kept secret. _grind_low_r’s docstring cites BIP461 (closes #151).

The bindings are checked against known answers

When the bindings are first selected, they derive one public key and verify one ECDSA signature, refuse it over another message and verify one BIP340 signature. A wrong answer raises BTClibEccRuntimeError (closes #152).

DLEQ proofs follow BIP374 0.3.0

The challenge is reduced modulo n, and a proof with e of n or more is refused, as BIP374 0.3.0 says. Only a challenge hash of n or more, once in 2^128, changes a proof this library makes (closes #149).

v2026.10.2

The OpenSSF Baseline badge

README.md’s badge row ends with the OpenSSF Baseline badge, beside the Best Practices badge, section 2 of the organization standard admitting it on the same property (issue btclib-org/.github#1460).

pypi-install.yml retries the install of the version the release published

Each install cell runs pip install btclib-ecc==<version> through btclib-org/.github’s install_published_release.py, which retries only while the installer says the pin is not resolvable (issue btclib-org/.github#1458).

The release’s attestation bundle is attached as *.intoto.jsonl

RELEASING.md’s verification and recovery commands name the bundle <tag>.intoto.jsonl, the name reusable-github-release.yml attaches it under (issue btclib-org/.github#1468).

codeql.yml’s aggregate runs check_run_jobs.py

The aggregate’s step runs check_run_jobs.py, which reads the run’s jobs listing again up to a deadline while a row of analyze is unfinished (issue btclib-org/.github#1463).

generate_sbom.py carries a not-affected list into the bill of materials

generate_sbom.py reads .github/vex.toml, where the tree lists the vulnerabilities its release is not affected by, into the document’s vulnerabilities; no list, no key (issue btclib-org/.github#1469).

SECURITY.md promises a response time

SECURITY.md says a report is acknowledged within 7 days, and a fix or a published advisory within 90 (issue btclib-org/.github#1460).

MuSig2 refuses a signer index outside its participant arrays

partial_sig_verify raises BTClibEccValueError when the signer index is negative or at least the number of public keys, instead of selecting from the end or leaking IndexError (closes #78).

release.yml audits the lock before it publishes

The audit job calls btclib-org/.github’s reusable-audit.yml, which runs uv audit over what the wheel declares, and both publish jobs wait for its success (issue btclib-org/.github#1466).

CLAUDE.md carries the shared primary-checkout section

The section The primary checkout is the maintainer’s is the organization’s shared text, and the Model section names only the model to use (issue btclib-org/.github#1494).

generate_sbom.py is btclib-org/.github’s

The dist job writes the bill of materials with btclib-org/.github’s generate_sbom.py, served from main, and the tree keeps no copy of it (issue btclib-org/.github#1478).

CLAUDE.md names both aggregates a draft pull request fails

codeql: every job passed fails a draft as test: every job passed does; only the second is a required check on main (issue btclib-org/.github#1494).

Dependency review is a required check

  • REPOSITORY.md reads lint / Dependency review back with the other required checks (issue btclib-org/.github#1465).

[tool.uv] required-version is >=0.12.18

  • required-version reads >=0.12.18, not >=0.12.19 (issue btclib-org/.github#1482): the Dependabot service refused 0.12.19 with tool_version_not_supported.

SECURITY.md names the latest security review

SECURITY.md gives the date of the latest security review and links the issue that records it (issue btclib-org/.github#1362).

The curve constructor tests p and n with Baillie-PSW

A base-2 Fermat pseudoprime such as 341 or 561 is refused as p or n (closes #80).

A hybrid public key is refused on both arms

mult_pub_key, ecies.derive_keys, dsa.assert_as_valid, dsa.assert_as_valid_ and dsa.sign_ raise BTClibEccValueError for a key with the prefix 0x06 or 0x07, as point_from_octets does (closes #75).

FROST refuses a signer id outside the session

partial_sig_verify_ raises BTClibEccValueError for an id not in ids, and returns False for a public share other than the session’s (closes #77).

FROST coerces no integer

SessionContext, ThresholdInfo, sign, deterministic_sign and both partial_sig_verify raise BTClibEccTypeError for a bool, float or str as n, t, an id or the signer index: ids=[0, 1.9] is not (0, 1) (closes #79).

ASSURANCE_CASE.md states what the fuzz test, the layering and --locked cover

The assurance case no longer claims every decoder is fuzzed, that the package imports none of the organization’s packages, or --locked everywhere; four more module-level decoders are fuzzed (closes #82).

BorromeanSig checks the types of e0 and of every s

BorromeanSig raises BTClibEccTypeError, from verify too, for an e0 that is not bytes, an s that is not an int or a ring that is not an iterable of ints. A signature of the right types that does not verify is False (closes #81).

Pedersen refuses a generator at infinity or outside the subgroup

commit, verify and assert_as_valid raise BTClibEccValueError for a generator off the curve, at infinity or, on a cofactor curve, outside the subgroup generated by G; verify answered instead (closes #76).

second_generator skips a point outside the subgroup

second_generator skips a point outside the subgroup generated by G: H changes where it returned one, as at sha256 on secp112r2 and secp128r2, and never on a curve of cofactor 1 (issue #76).

An exception does not repeat a private key passed as a public key

A private key passed as a public key, a committed value and a blinding factor are not repeated in an exception, its cause or its context. A non-integer coordinate is named by its type (closes #74).

A Signed-off-by: trailer on every commit of a pull request

Pull requests says every commit of a pull request carries a Signed-off-by: trailer, and how to add it (issue btclib-org/.github#1467).

The primary-checkout section uses one form for the checkout

  • The section writes the checkout as "${checkout:?}" throughout, says what <scratchpad> is and names the pull (issue btclib-org/.github#1500).

The python inventory has a copy kept in the tree

docs/source/_inventories/python.inv is read when docs.python.org fails, so an outage of that site no longer fails the -n -W docs build (issue btclib-org/.github#1508).

public-api is red for a break RELEASE_NOTES.md does not name

release.yml and RELEASING.md say that a red public-api means RELEASE_NOTES.md misses a name (closes btclib-org/.github#1517).

The release’s attestation is signed by reusable-build.yml, at SLSA Build L3

release.yml calls reusable-build.yml, which signs the files before the publish jobs wait for approval (issue btclib-org/.github#1506).

Exception messages name no scalar operand

signed_odd_digits, the functions of number_theory and the multiplication helpers of curve_group and curve_group_2 print no scalar or operand they refuse, except a modulus, and name a non-integer by its type (closes #100).

While the bot review is off, CONTRIBUTING.md says what stands in for the ack

The review says there is no ack of record while claude-review.yml is off, and that a local review of a named sha by a reviewer other than the author stands in for it (issue btclib-org/.github#1527).

The release publishes the files the build job built

The publish jobs, github-release and test.yml’s dist job on a release stop where the dist they download differs from the digests reusable-build.yml outputs (issue #83, btclib-org/btclib#2449).

CONTRIBUTING.md lists the jobs that do not pass --locked

CONTRIBUTING.md and ASSURANCE_CASE.md no longer say every job passes --locked: they list the exceptions, none of which installs the project’s dependencies unpinned in the build job (btclib-org/btclib#2440, issue #83).

The verification command pins the tag for every signer

SECURITY.md, RELEASING.md and RELEASE_NOTES.md said a reusable-attest.yml release took no --source-ref; it takes the tag, and v2026.9.26 takes --repo btclib-org/ellipticcurves (issue #83, btclib-org/btclib#2447).

The weekly vendored-vectors check compares bytes

check_vendored_vectors.py hashes each vendored file against the blob the ledger records and compares that blob with upstream’s at the pinned commit (issue #83, btclib-org/btclib#2439).

check_vendored_vectors does not read a removed pin as changed content

A pin whose file upstream deleted or renamed is reported as the commit that removed it, which the report says may be a deletion or a rename; “no commit” names a path the branch walked never held (issue #83, btclib-org/btclib#2312).

A drift line names both commits whole

check_vendored_vectors.py prints the pinned commit and upstream’s tip as full shas, so two commits alike in their first twelve characters print as two (issue #83, btclib-org/.github#1343).

ssa keys and nonce points of a cofactor curve are points of ⟨G⟩

On a curve of cofactor above 1 (secp112r2, secp128r2), ssa refuses a key outside ⟨G⟩, and a batch an r whose point is outside it, with dsa’s message (GHSA-r5pw-9wrg-m3mj). secp256k1 is unchanged.

The BIP340 nonce covers the curve order when the hash is shorter

With a hash shorter than the curve order, such as sha256 on secp521r1, the aux mask covers the whole key and the nonce the whole order: the ssa signatures of those pairs change, and no other pair’s do (GHSA-m38m-987v-j55h).

v2026.9.30

Vendored-vector pins follow upstream’s tip

Every entry of tests/_data/README.md the weekly job reports behind is pinned at upstream’s tip, and no vendored content changes (closes #48).

[tool.uv] required-version follows the uv dependabot-core bundles to 0.12.19

required-version reads >=0.12.19: a floor below the uv pin dependabot-core bundles admits a uv older than the one Dependabot writes uv.lock with (issue btclib-org/.github#1438).

REPOSITORY.md reads back the imported Read the Docs project

btclib-ecc.readthedocs.io serves the documentation, and Read the Docs records the project’s settings with the answers their read-backs give (issue #51).

RELEASING.md’s bundle verification names the signer workflow

The --bundle form of Verify the provenance of an asset passes --signer-workflow "$signer", without which gh attestation verify refuses a good release (issue btclib-org/.github#1446).

A btclib-secp256k1 too old to import says which version and what to install

An installed release lacking a name _libsecp256k1 imports still raises, and the ImportError names the installed version, the floor the secp256k1 extra declares and how to meet it, instead of the missing symbol alone (closes #56).

CONTRIBUTING.md points a newcomer at good first issue

The shared half takes btclib-org/.github’s paragraph on the label and its one search across the organization (issue btclib-org/.github#1362, closes #53).

.clusterfuzzlite/.python-version pins the fuzz image’s interpreter

.clusterfuzzlite/.python-version names 3.11, the fuzz image’s interpreter, so that the Dependency Graph’s pip job there reads it rather than a root pin Dependabot does not support yet (issue btclib-org/.github#1436).

The README carries the OpenSSF Best Practices badge

The row links the tree’s bestpractices.dev registration (closes #1).

An assurance case, and a first use the suite runs

ASSURANCE_CASE.md argues the threat model, the trust boundaries and the design principles, and the README shows a signature made and verified, which tests/readme_test.py runs (issue #52).

RELEASING.md’s griffe step searches src

griffe check takes -s . -s src, the pair release.yml’s public-api job passes, where it answered ModuleNotFoundError for the previous release (issue btclib-org/.github#1447).

pypi-install.yml installs the version the release published

The install names btclib-ecc==<version> from the tag release.yml passes, where a bare name let a lagging index serve the release before it (issue btclib-org/.github#1456).

v2026.9.28

REVIEWING.md lets a filed issue carry its fix

An issue filed from a review may say the fix where one is known: What is filed, and what is not dropped its “no fix”, the filing bar standing as it was (issue btclib-org/.github#1378).

codeql.yml, REPOSITORY.md and dependabot.yml follow sections 10 and 11

The aggregate accepts analyze’s lagging rows (issue btclib-org/.github#1395), signatures and SHA pinning are read back (issue btclib-org/.github#1409), and pre-commit is an ecosystem left unused (issue btclib-org/.github#1391).

The fuzz image is pinned by digest, and Dependabot moves it

.clusterfuzzlite/Dockerfile builds from base-builder-python:latest pinned to its digest (closes #12), and dependabot.yml’s docker block is what proposes the next one (issue btclib-org/.github#1405).

codeql.yml rereads a lagging row and fails on a failed needs result

An analyze row listed unfinished is read again before it is accepted (issue btclib-org/.github#1416), and an analyze result other than success or skipped fails the step (issue btclib-org/.github#1424).

REPOSITORY.md records the btclib-ecc/ellipticcurves naming exception

Section 3’s name-equals-repository rule is declined here, with the maintainer’s decision and its citation, rather than left unexplained (closes #17).

The fuzz build installs from uv.lock, not the index

.clusterfuzzlite/build.sh installs requirements.txt, uv.lock’s base dependencies exported with their hashes, with --require-hashes --no-deps before the package itself, instead of resolving off the index (closes #13).

_utils.int_from_integer reads only ASCII hex digits after 0x

Only ASCII whitespace is stripped, and the refusal no longer quotes the string, which may be a private key (closes #21).

Sig.parse refuses a long-form or oversized DER length under strict

A DER length octet at 0x80 or above, or an oversized r,s total, is refused under strict instead of CompactSize; errors now name the DER length, not var_int or a psbt caller this tree lacks (closes #8) (closes #14).

is_on_curve and the two cofactor-4 curves get their missing checks

is_on_curve refuses an x outside 0..p-1 (closes #7) and a tuple (x, 0) ambiguous with INF on a cofactor curve (closes #16); every public key is confined to the subgroup G generates (closes #15).

The fuzz build’s own build backend installs from uv.lock too

requirements.txt’s fuzz group hashes it, and build.sh installs the package -e --no-build-isolation, its own backend built from that hash rather than the index (closes #24).

dh.diffie_hellman validates dU before either arithmetic arm sees it

dU is read through curves.scalar_from_prv_key, and an infinity QV is refused, before the bindings or the Python arithmetic can disagree on either one (closes #10).

Curve() accepts a cofactor Hasse’s bound cannot pin down on its own

The constructor now checks cofactor*n against Hasse’s bound, in place of refusing every cofactor but the interval’s largest multiple of n, wrong wherever several multiples fit (closes #19).

tonelli_var and mod_sqrt_var raise, not hang, on a composite p

The z search is bounded at p, and the inner loop raises BTClibEccValueError when it is exhausted, instead of an outer loop that ran forever past the “p must be a prime” precondition (closes #9).

kdf, tagged_hash, ecies.encrypt and sign refuse a malformed input

A bare bytes, bytearray or hash function used to leak a builtin TypeError for a malformed input in kdf, hashes.tagged_hash, ecies.encrypt and the two signs; each now refuses it by name (issue #11).

signed_odd_digits refuses a non-integer m, w or size

A non-integer used to leak a bare builtin TypeError from the comparisons guarding the recoding; each of the three is now checked and refused as a BTClibEccTypeError ahead of them (closes #34).

_libsecp256k1 no longer reads an installed, too-old package as absent

Only ModuleNotFoundError naming btclib_secp256k1 itself means absent; a name an older, installed package lacks now raises instead of a silent fallback (closes #25).

ec17_13 and ec19_13’s test fixtures carry their true cofactor, 1

A genuinely cofactor-2 curve this small has a two-torsion point is_on_curve refuses outright, which an exhaustive sweep reaches; secp112r2 carries the cofactor above 1 case now (closes #32).

Curve.__init__’s order_check no longer mistakes 2n for n

_mult_jac_var, never leaving Jacobian coordinates, replaces the windowed _mult, whose affine table of odd multiples of G could hold the curve’s own two-torsion point and read it as infinity (closes #39).

_assert_in_subgroup no longer mistakes 2n for n

The same collision order_check was fixed against, at the call site that confines a parsed public key to ⟨G⟩ on a cofactor > 1 curve: _mult_jac_var replaces the windowed _mult there too (closes #42).

The repository is btclib-org/btclib-ecc, not btclib-org/ellipticcurves

GitHub’s repository was renamed to match the PyPI distribution; every hardcoded reference in this tree, from release.yml’s publish guards to its own issue citations, now reads btclib-org/btclib-ecc.

v2026.9.26

The repository opens

The package: elliptic curve arithmetic over any short Weierstrass curve, the signature, commitment and key-agreement schemes built on it, and the vectors they answer to (issue btclib-org/btclib#2282).

REPOSITORY.md records what each read-back answers

Each read-back carries what it answered on 2026-09-26; classic protection’s also reads its force-push and deletion switches, and the environments’ their required reviewers (issue btclib-org/btclib#2282).

The distribution is btclib-ecc, imported as btclib_ecc

Its exceptions are BTClibEcc* and its switch BTCLIB_ECC_NO_LIBSECP256K1; the documentation is btclib-ecc.readthedocs.io (issue btclib-org/btclib#2282).