Skip to content

Commit 75995d0

Browse files
committed
Assure Python 3.13 dependencies
Carry the owner-authored bip32 Coincurve 21 range patch by exact source hash and pin every supported 3.12/3.13 Coincurve wheel. Keep Python 3.14 as a non-blocking probe because no selected native wheel exists yet.
1 parent 5244932 commit 75995d0

15 files changed

Lines changed: 345 additions & 58 deletions

‎.github/workflows/python-package.yml‎

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,51 @@ on:
44
push:
55
pull_request:
66

7+
env:
8+
SOURCE_DATE_EPOCH: "1763060600"
9+
710
jobs:
811
test:
9-
runs-on: ubuntu-latest
12+
runs-on: ${{ matrix.os }}
1013
strategy:
1114
fail-fast: false
1215
matrix:
13-
python-version: ["3.12", "3.13", "3.14"]
16+
os: [ubuntu-latest, macos-latest, windows-latest]
17+
python-version: ["3.12", "3.13"]
1418
steps:
1519
- uses: actions/checkout@v4
1620
- uses: actions/setup-python@v5
1721
with:
1822
python-version: ${{ matrix.python-version }}
19-
- run: python -m pip install -e '.[dev]'
23+
- run: python -m pip install --upgrade pip
24+
- run: python -m pip install --require-hashes -r requirements/cli-build-dependencies.txt
25+
- run: >-
26+
python -m pip install --no-build-isolation --require-hashes
27+
-r requirements/cli-dependencies.txt
28+
- run: python -m pip install --no-build-isolation -e '.[dev]'
29+
- run: python -m pip check
2030
- run: python -m pytest -q
2131
- run: python -m mypy src/codex32
2232
- run: python -m ruff check .
23-
- run: python -m build
33+
- run: python -m ruff format --check .
34+
- run: python tools/differential_correction.py --verify
35+
- run: python tools/differential_wallet.py --verify
36+
- run: python -m build --no-isolation
37+
38+
experimental-python:
39+
continue-on-error: true
40+
runs-on: ubuntu-latest
41+
steps:
42+
- uses: actions/checkout@v4
43+
- uses: actions/setup-python@v5
44+
with:
45+
python-version: "3.14"
46+
- run: python -m pip install --upgrade pip
47+
- run: python -m pip install --require-hashes -r requirements/cli-build-dependencies.txt
48+
- run: >-
49+
python -m pip install --no-build-isolation --require-hashes
50+
-r requirements/cli-dependencies.txt
51+
- run: python -m pip install --no-build-isolation -e '.[dev]'
52+
- run: python -m pip check
53+
- run: python -m pytest -q
54+
- run: python tools/differential_wallet.py --verify

‎MANIFEST.in‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
include LICENSE README.md SECURITY.md
22
recursive-include docs *.md
3+
recursive-include requirements *.txt
34
recursive-include tests *.py *.json *.md
45
recursive-include tools *.py

‎README.md‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,17 @@ reviewed by someone you trust before relying on it with funds. See
2525

2626
## Install and test
2727

28-
Supported Python versions are 3.12 through 3.14.
28+
Supported Python versions are 3.12 and 3.13. Python 3.14 remains a
29+
non-blocking compatibility probe until the required native wheels are
30+
available and the platform matrix passes.
2931

3032
```bash
3133
python -m venv .venv
3234
source .venv/bin/activate
33-
python -m pip install -e '.[dev]'
35+
python -m pip install --require-hashes -r requirements/cli-build-dependencies.txt
36+
SOURCE_DATE_EPOCH=1763060600 python -m pip install \
37+
--no-build-isolation --require-hashes -r requirements/cli-dependencies.txt
38+
python -m pip install --no-build-isolation -e '.[dev]'
3439
python -m pytest -q
3540
python -m mypy src/codex32
3641
python -m ruff check .

‎SECURITY.md‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,12 @@ through 64 bytes.
3939
- On supported terminals, a rejected entry is temporarily retained for editing.
4040
Automatic Readline history is disabled and this project never writes a history
4141
file, but terminal scrollback and Python or native editor memory may retain it.
42-
- `bip32>=5,<6` is a security-sensitive dependency. This project wraps it
43-
narrowly and verifies BIP93 BIP32 vectors, but does not independently audit
44-
its cryptographic implementation.
42+
- `bip32>=5,<6` and Coincurve are security-sensitive dependencies. This project
43+
wraps BIP32 narrowly and verifies official BIP32 plus wallet vectors, but
44+
does not independently audit their cryptographic implementations. The tested
45+
CLI resolution carries upstream BIP32 PR #53 and pins Coincurve 21 wheels.
46+
- Python 3.12 and 3.13 are supported. Python 3.14 CI is non-blocking until the
47+
selected Coincurve release has the required binary wheels.
4548
- Fresh unshared `ms` identifiers expose 20 bits of the BIP32 fingerprint.
4649
Shared sets, supplied raw seeds, re-sharing, and CL generation use random or
4750
explicit identifiers.

‎docs/accepted-risks.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ turn a checksum into authentication or remove the verification gates in the
77
| ID | Risk and exposure | Disposition and controls | Review trigger |
88
|---|---|---|---|
99
| AR-001 | Pending BIP93 PR #2258 changes the checksum boundary for expanded HRPs. `ms` strings carrying 44--46-byte seeds can be incompatible with software implementing only the currently published BIP93 rule. | Accepted pending-standard compatibility risk. Follow the frozen PR head and boundary vectors; do not add ambiguous dual decoding. Fresh CLI generation permits only 16 or 32 bytes. The API and imported existing seeds retain all 16--64-byte BIP93 sizes. | Recheck the exact upstream revision before the RC and final release; reassess if the PR changes, closes, or merges differently. |
10-
| AR-002 | Root-key and wallet derivation rely on `bip32` and its native secp256k1 dependency stack, which this project does not independently audit. | Accepted architecture boundary. Keep all interaction in `_bip32.py`; retain official BIP32, BIP48, descriptor, and wallet fixtures. Gate 2 must add reproducible hash-pinned CLI constraints and cross-platform evidence. | Any resolved dependency change, adapter change, vector failure, advisory, or unsupported release artifact. |
10+
| AR-002 | Root-key and wallet derivation rely on `bip32` and Coincurve/libsecp256k1, which this project does not independently audit. Published `bip32` 5.0.0 metadata has a stale Coincurve `<21` limit. | Accepted architecture boundary. Keep all interaction in `_bip32.py`; retain official BIP32, BIP48, descriptor, wallet, and large differential fixtures. The tested CLI resolution carries owner-authored upstream PR #53 and hash-pins Coincurve 21 wheels for Python 3.12/3.13. Python 3.14 remains non-blocking. | Any resolved dependency or adapter change, vector failure, advisory, upstream PR change, or unsupported release artifact. |
1111
| AR-003 | A fresh unshared `ms` identifier reveals 20 bits of the BIP32 fingerprint. Identifiers and checksums are public metadata, not authentication. | Accepted BIP93 usability/privacy tradeoff. Shared sets, supplied raw seeds, re-sharing, and CL generation instead use random or explicit identifiers. | Any workflow starts treating an identifier as secret, unique, or proof of wallet identity. |
1212
| AR-004 | Python and terminal environments cannot guarantee secret zeroization, locked memory, constant-time execution, or removal from scrollback and editor memory. | Accepted implementation-platform limitation. Keep protected material out of argv and machine stdout, disable automatic line history, and document offline use. | A supported runtime or interface adds a stronger secret-memory or terminal boundary. |
1313

‎docs/dependencies.md‎

Lines changed: 97 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,115 @@
11
# Runtime dependency review
22

3-
The package has one direct runtime dependency: `bip32>=5,<6`. The compatible
4-
range permits bug-fix releases, but a release must record and review the exact
5-
resolved dependency set. A resolved-version change requires the full BIP32 and
6-
wallet-vector suite before publication.
3+
The package retains one direct runtime dependency: `bip32>=5,<6`. Only
4+
`src/codex32/_bip32.py` imports it. That 49-line typed adapter exposes the three
5+
operations this project needs and converts an invalid master scalar to
6+
`CodexError`. Bitcoin Core cannot replace it because Core has no interface that
7+
derives this project's root key or wallet records from raw seed bytes.
78

8-
The metadata now reserves `1.0.0rc1`; it is not authorization to publish. The
9-
compatible library range remains deliberate. Gate 2 must add a tested,
10-
hash-pinned CLI installation constraint and reconcile native wheels across
11-
Python 3.12--3.14 before the release candidate can be considered dependency-
12-
assured. Bitcoin Core cannot replace this boundary because it has no interface
13-
that derives this project's root key and wallet records from raw seed bytes.
9+
Python 3.12 and 3.13 are supported. Python 3.14 is an explicitly non-blocking
10+
CI probe until the selected Coincurve release publishes 3.14 wheels and the
11+
required platform matrix passes. It is not a v1 support claim.
1412

15-
## Reviewed resolution
13+
## Reviewed Coincurve 21 resolution
1614

17-
Reviewed on 2026-08-22 with Python 3.13 on Linux x86-64:
15+
The published `bip32` 5.0.0 metadata says `coincurve>=15,<21`. Upstream owner
16+
Antoine Poinsot's open [PR #53](https://github.com/darosior/python-bip32/pull/53)
17+
changes only that range to `<22` and adds Coincurve 21 to upstream CI. The CLI
18+
constraint carries its exact commit
19+
`45db547bdf5a5bc19a8c55ef447dbf9169928792`; the archive SHA-256 is
20+
`a25be30641b381eed9a5249bbc4a73124905223c9c50f93140f519bcf259e415`.
21+
The compatible library dependency remains unchanged, so this is not a local
22+
BIP32 fork or a change to the installed implementation.
1823

19-
| Package | Version | Purpose |
20-
|---|---:|---|
21-
| `bip32` | 5.0.0 | BIP32 root keys, fingerprints, and path derivation |
22-
| `coincurve` | 20.0.0 | secp256k1 implementation selected by `bip32` |
23-
| `asn1crypto` | 1.5.1 | transitive `coincurve` dependency |
24-
| `cffi` | 2.1.1 | Python/native boundary used by `coincurve` |
25-
| `pycparser` | 3.0 | transitive `cffi` dependency |
24+
The resolution is:
2625

27-
`bip32` 5.0.0 declares `coincurve>=15,<21`. It does not publish `py.typed` or
28-
type stubs. Only `src/codex32/_bip32.py` imports it; that adapter defines the
29-
three operations used by this project and converts its invalid-seed exception
30-
to `CodexError`. The rest of the package is checked by strict mypy without
31-
import suppression.
26+
| Package | Version | License | Purpose |
27+
|---|---:|---|---|
28+
| `bip32` | 5.0.0 plus PR #53 metadata | BSD-3-Clause | BIP32 private-tree derivation |
29+
| `coincurve` | 21.0.0 | MIT or Apache-2.0 | libsecp256k1 binding |
3230

33-
Published `bip32` 5.0.0 artifact SHA-256 hashes:
31+
The installed dependency license-file SHA-256 values are:
3432

35-
- sdist: `4caa1f74eed9f2cd4624b55f34a4094f52542552fe3d0cc52e1179b8d6e9f21e`
36-
- wheel: `b20872795ae2bb4e5fac351f53ccdf2b998f82e927413922a2c5473a004bd6d0`
33+
- BIP32 `LICENCE`: `ba32f1ce36d4b107164ff3b70f145eca686e6e5e74f9080f628545ebe9209dcc`;
34+
- Coincurve `LICENSE-MIT`: `d502748a33db7ade1318e37f0b5f219f478330ed74a673e387756e53fb516715`;
35+
- Coincurve `LICENSE-APACHE`: `cebfb5eab4eff50df87c3c5e7eb11a634d0fa32bb4b6380800f82fae606599ae`;
36+
- Coincurve's retained `LICENSE-cffi` notice:
37+
`04b80f5b077bbed68808cfebadeb5e3523f2a8c9a96495c587bd96df1eac2a33`.
3738

38-
The project verifies the official BIP93 BIP32 vectors, mainnet and testnet
39-
extended keys, BIP48 account xpubs, and public/private Bitcoin Core descriptor
40-
fixtures. This is dependency-boundary evidence, not an independent audit of
41-
`bip32`, `coincurve`, or libsecp256k1.
39+
Coincurve 21 removes the former runtime CFFI and ASN.1 dependencies. Its
40+
[release notes](https://github.com/ofek/coincurve/releases/tag/v21.0.0) record
41+
Python 3.13 support and libsecp256k1 0.6.0. Its CPython 3.12 and 3.13 release has
42+
binary wheels for Intel and ARM macOS, x86-64/ARM/i686 glibc and musl Linux,
43+
and AMD64/ARM64 Windows. `requirements/cli-dependencies.txt` pins every one of
44+
those wheel hashes and prohibits a Coincurve source build.
4245

43-
Sources: [`bip32` on PyPI](https://pypi.org/project/bip32/) and the installed
44-
wheel metadata captured by the clean-environment release check.
46+
The BIP32 patch archive is pure Python. `requirements/cli-build-dependencies.txt`
47+
pins the universal Setuptools 80.9.0 wheel used to build it, and the dependency
48+
installation disables build isolation. A clean Python 3.13 install completed
49+
without a compiler and `pip check` reported no broken requirements.
50+
Setting `SOURCE_DATE_EPOCH=1763060600`, the upstream patch commit time, produced
51+
the same BIP32 wheel twice: SHA-256
52+
`637e98a3fc3318d29a6bf69025abe0dbe4a02f5911fabd8443b16ef4f5088176`.
53+
Without that environment variable, ZIP timestamps make the wheel hash vary even
54+
though its files are identical. CI sets it explicitly.
55+
56+
## Compatibility evidence
57+
58+
The Coincurve APIs used by `bip32` were inspected at tags v20.0.0 and v21.0.0:
59+
`PrivateKey`, its `secret` property and `add` method, plus `PublicKey`,
60+
`from_secret`, `combine_keys`, and `format`. Their signatures, libsecp256k1
61+
operations, and `ValueError` failure behavior are unchanged at this boundary.
62+
63+
On 2026-08-24 under Python 3.13.12:
64+
65+
- all seven upstream `bip32` tests passed with Coincurve 21, including the four
66+
official private-tree BIP32 vectors and invalid extended-key cases;
67+
- all 499 repository tests passed with the carried patch and Coincurve 21;
68+
- a deterministic corpus covered all seed lengths 16--64, 64 cases per length,
69+
both networks, root xprvs, BIP48 account xpubs, and all public/private Core
70+
descriptors;
71+
- all 6,272 records matched Coincurve 20 and 21 byte for byte, with SHA-256
72+
`a51c9833408bd02d8fbafc339a7d4b48ada02af655e268e0bd165040c45b0249`.
73+
74+
`python tools/differential_wallet.py --verify` reproduces the last check. A
75+
resolved dependency, adapter, or vector change requires the upstream and local
76+
suites plus this corpus before publication.
77+
78+
Pip-audit 2.10.1, using its PyPI advisory service on 2026-08-24, reported no
79+
known vulnerability for exact versions BIP32 5.0.0 and Coincurve 21.0.0. The
80+
local unpublished codex32 release candidate is necessarily outside that service,
81+
and this result is a known-advisory check rather than a cryptographic audit.
82+
83+
## Bounded `cryptography` prototype
84+
85+
A private-seed-only prototype using `cryptography` 50.0.0 was evaluated before
86+
this decision. It matched all 17 official vector path outputs and the complete
87+
6,272-record corpus. Five forced HMAC cases explicitly checked zero or
88+
out-of-range master scalars, out-of-range child tweaks, zero child scalars, and
89+
the valid zero-tweak case.
90+
91+
It did not meet the replacement cut conditions:
92+
93+
- 167 implementation lines would replace the 49-line adapter, taking the
94+
installed project from 2,999 to about 3,117 physical Python lines;
95+
- the corpus took about 90 seconds rather than 27 seconds in this diagnostic
96+
run;
97+
- the installed cryptography/CFFI surface was about 17 MB, compared with a
98+
2.6 MB Coincurve install, and introduced CFFI plus a general OpenSSL binding;
99+
- cryptography 50.0.0 publishes no Intel macOS or Windows ARM wheel, while
100+
Coincurve 21 covers both.
101+
102+
The prototype proved feasibility, not a security or performance benchmark.
103+
Because it is neither materially smaller nor easier to audit and violates the
104+
line and wheel criteria, the roadmap retains `bip32` behind its narrow adapter.
45105

46106
## Development-tool baseline
47107

48108
Most development tools remain compatible ranges or unpinned extras rather than
49109
a reproducible release environment. Ruff is pinned to 0.16.2 because it defines
50110
the formatting baseline. On 2026-08-24 the existing Python 3.13.12 environment
51111
also contained pytest 8.4.2, Hypothesis 6.165.2, mypy 2.3.0, build
52-
1.2.2.post1, and Twine 6.2.0. Ruff 0.16.2 reports formatting drift already
53-
present at the Gate 0 starting revision, despite lint passing. A mechanical
54-
110-column Ruff baseline reconciles that drift at 2,970 production lines; a
55-
100-column probe produced 3,021 and was rejected. Gate 2 must freeze the final
56-
cross-platform release-tool and dependency evidence.
112+
1.2.2.post1, and Twine 6.2.0. A mechanical 110-column Ruff baseline reconciles
113+
the inherited formatting at 2,970 production lines; a 100-column probe produced
114+
3,021 and was rejected. New code still prefers lines under 100 characters when
115+
that preserves readability.

‎docs/generation.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,14 @@ Explicit output indices preserve caller order. A share count uses
3434
`SystemRandom.sample` over the 31 ordinary indices and preserves sample order.
3535
There is no entropy injection, sorting, partial-basis completion, or BIP39
3636
generation.
37+
38+
## Independent Gate 2 review record
39+
40+
The delegated standard security scan on 2026-08-24 independently reviewed the
41+
OS CSPRNG boundary, one-call mask sampling, unbiased u5 mapping, rejection
42+
sampling, CRC/zero padding, identifier policy, random distinct-index selection,
43+
and re-sharing. It found no medium-or-higher generation issue. Its one low
44+
availability finding was that a string index selector was copied and normalized
45+
before enforcing the 31-index maximum. Gate 0 fixed that ordering in
46+
`generation._indices` and added a regression through every public generation
47+
API. No injectable or fallback entropy source was added.

‎docs/production-ready-v1.md‎

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Production-ready v1 completion plan
22

33
Status: active implementation roadmap. The mandatory new-session scan
4-
precondition and Gates 0--1 passed on 2026-08-24; Gate 2 is next.
4+
precondition and Gates 0--1 passed on 2026-08-24; Gate 2 local work is in
5+
progress and its remote platform matrix remains pending.
56

67
This plan turns the current reference implementation into a narrowly scoped
78
real-funds release. It does not add a GUI, networking, RPC, secret storage,
@@ -216,13 +217,18 @@ Work:
216217
compatible library dependency range;
217218
- reconcile the documented and tested versions of `bip32`, coincurve, CFFI,
218219
libsecp256k1 bindings, and transitive dependencies;
219-
- run BIP32 and wallet vectors on Python 3.12--3.14 and supported Linux, macOS,
220-
and Windows wheels.
221-
222-
Do not replace `bip32` with locally implemented BIP32. Bitcoin Core currently
223-
has no interface that accepts raw seed bytes and returns this project's root
224-
key, account xpubs, or descriptors. A Core proposal belongs to a separate
225-
project.
220+
- run BIP32 and wallet vectors on Python 3.12 and 3.13 across supported Linux,
221+
macOS, and Windows wheels;
222+
- keep Python 3.14 as a non-blocking CI probe until the selected Coincurve
223+
release publishes the required wheels and the full platform matrix passes.
224+
225+
Do not replace `bip32` merely to work around stale dependency metadata. A
226+
bounded `cryptography` prototype must be materially smaller and easier to audit,
227+
fit the 3,000-line budget, match every official vector and a large differential
228+
corpus, handle invalid scalars explicitly, and have wheels on every supported
229+
platform before replacement is reconsidered. Bitcoin Core currently has no
230+
interface that accepts raw seed bytes and returns this project's root key,
231+
account xpubs, or descriptors.
226232

227233
Success criteria:
228234

@@ -236,6 +242,16 @@ Success criteria:
236242

237243
Dependency: Gate 1.
238244

245+
Local evidence (2026-08-24): upstream `bip32`'s complete seven-test suite and
246+
all 499 project tests pass with Coincurve 21. The inspected Coincurve 20-to-21
247+
APIs used by `bip32` are behaviorally unchanged. A 6,272-record wallet corpus
248+
matches exactly under both versions. The owner-authored upstream range-only PR
249+
#53 is carried by exact commit and archive hash, and all published Python 3.12
250+
and 3.13 Coincurve wheel hashes are pinned. A bounded `cryptography` prototype
251+
matched the vectors and corpus but failed the size, audit-surface, performance,
252+
and wheel-coverage cut conditions, so the roadmap retains the narrow adapter.
253+
The required remote Linux/macOS/Windows matrix must pass before Gate 2 closes.
254+
239255
## Gate 3 -- Cuttable bounded structural correction
240256

241257
Objective: reduce realistic transcription burden while keeping the BCH core
@@ -395,7 +411,8 @@ Required evidence:
395411
- extended malformed-input and fuzz campaigns;
396412
- clean Bitcoin Core integration from both wheel and sdist;
397413
- reproducible build, dependency manifest, and package-content comparison;
398-
- clean installation and CLI smoke tests on Python 3.12--3.14.
414+
- clean installation and CLI smoke tests on supported Python 3.12 and 3.13,
415+
with Python 3.14 reported separately as non-blocking compatibility evidence.
399416

400417
Release criteria:
401418

0 commit comments

Comments
 (0)