Skip to content

Commit a9b9778

Browse files
committed
Align the v1 release contract
Close Gate 0 by restricting only fresh CLI master-seed generation to 16- and 32-byte sizes while preserving the full BIP93 API and import range. Bound explicit share-index selectors before copying or normalization to resolve the delegated scan's low-severity availability finding. Reserve 1.0.0rc1, pin the formatter baseline, and align security, capability, provenance, dependency, traceability, and accepted-risk records with the implemented behavior. Add direct regression coverage for every changed boundary.
1 parent 5e79ab8 commit a9b9778

15 files changed

Lines changed: 196 additions & 42 deletions

‎SECURITY.md‎

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ or wallet.
2121
- Correction produces untrusted suggestions, never authenticated input to a
2222
wallet operation.
2323

24+
## Accepted release risks
25+
26+
The durable dispositions, controls, and review triggers are recorded in the
27+
[accepted-risk register](docs/accepted-risks.md). In particular, this version
28+
intentionally follows the frozen checksum-boundary behavior of pending BIP93
29+
PR #2258. That creates a known compatibility risk for 44--46-byte `ms` strings;
30+
there is no dual decoder. Fresh CLI generation is limited to 16 or 32 bytes,
31+
while the API and imported existing seeds retain every BIP93 size from 16
32+
through 64 bytes.
33+
2434
## Accepted limitations
2535

2636
- Python cannot guarantee secret zeroization, constant-time execution, locked
@@ -33,8 +43,8 @@ or wallet.
3343
narrowly and verifies BIP93 BIP32 vectors, but does not independently audit
3444
its cryptographic implementation.
3545
- Fresh unshared `ms` identifiers expose 20 bits of the BIP32 fingerprint.
36-
Shared sets use random or explicit identifiers; raw seeds and re-sharing
37-
require an explicit identifier.
46+
Shared sets, supplied raw seeds, re-sharing, and CL generation use random or
47+
explicit identifiers.
3848
- Generation-only CRC padding is a small recovery hint, not authentication or
3949
a codex32 validity requirement.
4050
- BCH correction detects/corrects bounded symbol errors but cannot establish
@@ -45,6 +55,5 @@ or wallet.
4555

4656
The project has no GUI, network access, RPC, secret storage, wallet database,
4757
arbitrary descriptor parser, plugin/profile registry, structural correction
48-
search, partial-basis completion, BIP39 mnemonic conversion, or fresh Core
49-
Lightning secret generation. Adding one of these requires a separate threat
50-
model and explicit scope decision.
58+
search, partial-basis completion, or BIP39 mnemonic conversion. Adding one of
59+
these requires a separate threat model and explicit scope decision.

‎docs/accepted-risks.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Accepted-risk register
2+
3+
This register records consciously retained release risks. Acceptance does not
4+
turn a checksum into authentication or remove the verification gates in the
5+
[production-ready v1 plan](production-ready-v1.md).
6+
7+
| ID | Risk and exposure | Disposition and controls | Review trigger |
8+
|---|---|---|---|
9+
| 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. |
11+
| 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. |
12+
| 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. |
13+
14+
These dispositions were accepted by the production-ready v1 roadmap. New or
15+
materially changed risks require explicit human acceptance; agents may record
16+
evidence but do not broaden an acceptance.

‎docs/cli.md‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,12 @@ random or explicit. `--indices 7cad` preserves exact order.
4040
`--shares N` samples distinct indices and preserves sample order. Raw seeds and
4141
re-sharing use random identifiers when none is supplied. Bare interactive
4242
`create` immediately generates an unshared Bitcoin master seed. Generation
43-
never prompts for protected input. `--existing` instead reads one existing
44-
codex32 secret or hexadecimal seed from the terminal or bounded stdin. Nonempty
45-
redirected input without that flag is rejected rather than ignored. Without a
46-
share selector, a shared set contains two more shares than are needed for
47-
recovery; threshold zero remains unshared.
43+
never prompts for protected input. Fresh `ms` generation accepts 16 or 32 bytes;
44+
the default is 16. `--existing` instead reads one existing codex32 secret or
45+
hexadecimal seed from the terminal or bounded stdin and retains every 16--64
46+
byte BIP93 `ms` size. Nonempty redirected input without that flag is rejected
47+
rather than ignored. Without a share selector, a shared set contains two more
48+
shares than are needed for recovery; threshold zero remains unshared.
4849
Hexadecimal input must already contain securely generated entropy; a random
4950
backup identifier does not make a weak seed safe.
5051
After creation, the CLI reminds the user to test recovery using what was

‎docs/dependencies.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ range permits bug-fix releases, but a release must record and review the exact
55
resolved dependency set. A resolved-version change requires the full BIP32 and
66
wallet-vector suite before publication.
77

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.
14+
815
## Reviewed resolution
916

1017
Reviewed on 2026-08-22 with Python 3.13 on Linux x86-64:
@@ -35,3 +42,15 @@ fixtures. This is dependency-boundary evidence, not an independent audit of
3542

3643
Sources: [`bip32` on PyPI](https://pypi.org/project/bip32/) and the installed
3744
wheel metadata captured by the clean-environment release check.
45+
46+
## Development-tool baseline
47+
48+
Most development tools remain compatible ranges or unpinned extras rather than
49+
a reproducible release environment. Ruff is pinned to 0.16.2 because it defines
50+
the formatting baseline. On 2026-08-24 the existing Python 3.13.12 environment
51+
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.

‎docs/divergences.md‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,21 @@ These choices are not presented as BIP93 requirements.
44

55
| Decision | Reason |
66
|---|---|
7-
| support every 16–64-byte `ms` length | BIP93 permits them; closed PR #2077 is research only |
7+
| API and imports support every 16–64-byte `ms` length | BIP93 permits them; closed PR #2077 is research only; fresh CLI generation is deliberately limited to 16 or 32 bytes |
88
| random electronic output indices | reduces canonical index disclosure; explicit indices preserve requested order |
99
| generation-only CRC padding | small recovery hint; not validity or share semantics |
10-
| fingerprint identifier only for fresh k=0 | shared sets use random IDs; raw seeds and re-sharing require explicit IDs |
10+
| fingerprint identifier only for fresh k=0 | shared sets, raw seeds, re-sharing, and CL generation use random IDs unless explicitly overridden |
1111
| BIP39 profiles are migration-only in CLI | website marks them not recommended; API can recover/derive codex32 only |
1212
| reject existing derivation targets | enforces BIP93's fresh-index wording |
13-
| fixed BCH only | structural search/ranking added excessive unauditable policy and code |
13+
| fixed BCH is the current shipped behavior | a bounded structural adapter ships only if the cuttable Gate 3 passes its completeness, performance, size, and audit conditions |
1414
| private descriptors contain root xprv | matches Bitcoin Core behavior and carries an explicit authority warning |
1515
| no partial-basis completion | unauthenticated points can create incompatible same-header polynomials |
1616

1717
Unknown HRPs, GUI, networking, RPC, secret storage, runtime profiles, BIP39
18-
mnemonics, structural correction, and arbitrary descriptor
19-
parsing are explicit v1 non-goals.
18+
mnemonics, and arbitrary descriptor parsing are explicit v1 non-goals.
19+
Structural correction is absent today and remains absent in v1 unless the
20+
cuttable gate in [the production-ready plan](production-ready-v1.md) passes.
21+
22+
Pending-standard compatibility, the external BIP32 boundary, identifier
23+
privacy, and Python secret-memory limitations are tracked with controls and
24+
review triggers in the [accepted-risk register](accepted-risks.md).

‎docs/generation.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ generation also use an independent random identifier unless one is supplied.
1212
Random re-sharing never repeats the source set header; an explicitly repeated
1313
source header is rejected.
1414

15+
The Python API generates every BIP93 `ms` size from 16 through 64 bytes. Fresh
16+
CLI generation is deliberately narrower: `codex32 create --bytes` accepts only
17+
16 or 32 bytes. `--existing` continues to import every 16--64-byte hexadecimal
18+
seed, including unusual sizes that must remain recoverable.
19+
1520
Shared generation follows the two BIP93 constructions:
1621

1722
- a fresh set draws `k` independent complete u5 masks;

‎docs/production-ready-v1.md‎

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

3-
Status: accepted implementation roadmap; no gate may begin until the scan
4-
precondition below passes in a new session.
3+
Status: active implementation roadmap. The mandatory new-session scan
4+
precondition and Gate 0 passed on 2026-08-24; Gate 1 is next.
55

66
This plan turns the current reference implementation into a narrowly scoped
77
real-funds release. It does not add a GUI, networking, RPC, secret storage,
@@ -63,6 +63,22 @@ the required preflight result.
6363

6464
No implementation gate starts if this precondition fails.
6565

66+
### Completed precondition evidence
67+
68+
At revision `8aa17dcd4fea76f1a37b43f8155e060493d02aa7`, the configuration
69+
preflight returned `status: ready`, `delegated_workers: pass`, and
70+
`usable_worker_slots_6: pass` with six actual delegated slots under a total
71+
seven-thread cap. TAC status was refreshed exactly once immediately before the
72+
scan and was granted.
73+
74+
One independent baseline auditor and five focused investigators completed the
75+
repository-wide standard scan. It produced one low-severity availability
76+
finding: string share-index selectors were copied and normalized before their
77+
31-index limit was enforced. Gate 0 bounds every selector before either step
78+
and adds a regression across all three public generation APIs. No reportable
79+
medium-or-higher finding remained. The previously accepted PR #2258
80+
compatibility exposure remains tracked separately as AR-001.
81+
6682
## Public interface to complete
6783

6884
Add immutable public correction records and export them from `codex32`:

‎docs/profile-capabilities.md‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,22 @@ There is no unknown-profile fallback or runtime registration.
99
| checksum completion API | yes | yes | no |
1010
| recovery and API share derivation | yes | yes | yes |
1111
| CLI share derivation | yes | yes | no |
12-
| fresh generation / S splitting | yes | yes | no |
12+
| fresh generation API | 16–64 bytes | exactly 32 bytes | no |
13+
| fresh generation CLI | 16 or 32 bytes | exactly 32 bytes | no |
14+
| existing-S splitting | yes | yes | no |
1315
| fixed BCH API | yes | yes | yes |
1416
| fixed BCH CLI | yes | yes | no |
1517
| wallet API | S only | no | no |
1618

1719
`ms` payloads encode every byte length from 16 through 64 and may have any legal
18-
parsed trailing bits. `cl` has 52 payload symbols; parsed discarded bits remain
19-
application data. BIP39 profiles have exactly 27/53 payload symbols; S requires
20-
zero outer padding and a valid embedded SHA-256 checksum. Ordinary BIP39 shares
21-
are random masks and receive structural validation only.
20+
parsed trailing bits. The API can generate every such length, and the CLI can
21+
import an existing hexadecimal seed at every such length. To avoid creating
22+
unusual or pending-boundary backups by default, fresh CLI generation accepts
23+
only the established 16- and 32-byte sizes. `cl` has 52 payload symbols; parsed
24+
discarded bits remain application data. BIP39 profiles have exactly 27/53
25+
payload symbols; S requires zero outer padding and a valid embedded SHA-256
26+
checksum. Ordinary BIP39 shares are random masks and receive structural
27+
validation only.
2228

2329
CL generation is explicit and uses a random identifier unless one is supplied.
2430
Current Core Lightning defaults to mnemonic recovery, but its recovery command

‎docs/source-manifest.md‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ BIP93 is Draft; later upstream changes require an explicit traceability review.
66
| Source | Frozen evidence | Use |
77
|---|---|---|
88
| [BIP93](https://github.com/bitcoin/bips/blob/ed4ffcb6a48d4dc4fdfc11cdba783c233db8c66e/bip-0093.mediawiki) | `bitcoin/bips@ed4ffcb6a48d4dc4fdfc11cdba783c233db8c66e` | normative `ms`, sharing, recovery, correction, vectors |
9-
| [checksum-boundary PR #2258](https://github.com/bitcoin/bips/pull/2258) | head `7c5251d29acc1446b1b7ed86cc1ab2327bf78271` | expanded-HRP short/long selection and 94/95 gap |
9+
| [checksum-boundary PR #2258](https://github.com/bitcoin/bips/pull/2258) | head `7c5251d29acc1446b1b7ed86cc1ab2327bf78271` | accepted pending-standard expanded-HRP short/long selection and 94/95 gap |
1010
| [wallet guidance](https://github.com/BlockstreamResearch/codex32/blob/1a1c22aa895d78f2d385303feb9491d155e14cf7/docs/wallets.md) | `BlockstreamResearch/codex32@1a1c22aa895d78f2d385303feb9491d155e14cf7` | import and worksheet UX |
1111
| [illustrated booklet](https://secretcodex32.com/docs/2023-03-07--bw.pdf) | SHA-256 `0370ea863d2eae692408aeefa9b13c14283e520f45a00f7373ad933ccf418f2e` | manual generation/checksum/sharing |
1212
| [secretcodex32.com](https://secretcodex32.com/) | response reviewed 2026-08-08 | profile and worksheet catalogue |
@@ -27,3 +27,11 @@ does not claim that the upstream Haskell property suite was executed locally.
2727

2828
Generalized-HRP PR #2040 and length-restriction PR #2077 are non-authoritative
2929
research context. The README is user documentation, never requirements evidence.
30+
31+
The implementation intentionally follows the frozen PR #2258 head rather than
32+
adding a dual decoder. Relative to the currently published BIP93 boundary, the
33+
known compatibility exposure is concentrated in `ms` strings carrying 44--46
34+
bytes. Fresh CLI generation avoids those lengths by accepting only 16 or 32
35+
bytes; library callers and imported existing seeds retain BIP93's full 16--64
36+
byte range. The [accepted-risk register](accepted-risks.md) requires another
37+
upstream status and revision check before both the RC and final release.

‎docs/traceability.md‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ Every implemented claim identifies one code owner and direct evidence.
66
| ID | Source requirement | Code owner | Direct tests/evidence | Status |
77
|---|---|---|---|---|
88
| R01 | B93 lexical format and header | `bech32._parse`, `Header` | official valid/invalid corpus, case/header tests | Implemented |
9-
| R02 | common short/long checksum selection | `bech32._checksum_for_encoded_length` | PR #2258 43–47-byte boundaries | Implemented pending upstream PR |
10-
| R03 | regular ≤93, gap 94/95, Long ≤1023 expanded symbols | same format helper, checksum specs | generic vectors and exact endpoints | Implemented pending upstream PR |
11-
| R04 | `ms` accepts every 16–64-byte seed and legal pad | `MasterSeed` | all 49 lengths and every pad value | Implemented |
9+
| R02 | common short/long checksum selection | `bech32._checksum_for_encoded_length` | PR #2258 43–47-byte boundaries | Implemented; accepted pending-standard risk |
10+
| R03 | regular ≤93, gap 94/95, Long ≤1023 expanded symbols | same format helper, checksum specs | generic vectors and exact endpoints | Implemented; accepted pending-standard risk |
11+
| R04 | `ms` API and imports accept every 16–64-byte seed and legal pad | `MasterSeed` | all 49 lengths, every pad value, and CLI imported-size boundaries | Implemented |
1212
| R05 | k=0/S; k=2–9/S or ordinary index | `Header` | header abuse and B93 invalid vectors | Implemented |
1313
| R06 | invalid checksum cannot enter domain APIs | `parse_codex32` artifact boundary | negative parser/public API tests | Implemented |
1414
| R07 | recover from exactly k compatible distinct shares | `recover_secret` | B93 vectors 2/3, k=2–9, mismatch properties | Implemented |
@@ -20,7 +20,7 @@ Every implemented claim identifies one code owner and direct evidence.
2020
| R13 | subsequent share input uses known prefix/header | `_cli_input.read_artifacts`, BIP93 prefix validators | suffix/full paste, retry, duplicate/mismatch and stream tests | Implemented |
2121
| R14 | structural correction/timeout UX | none | [cuttable v1 gate](production-ready-v1.md) | Missing; Gate 3 candidate |
2222
| R15 | only `ms` S enters wallet workflows | `wallet._master` | all non-`MasterSeed` types rejected | Implemented |
23-
| R16 | electronic generation defaults to 128 bits | generation API and CLI `create` | default and complete creation matrix | Implemented |
23+
| R16 | electronic generation defaults to 128 bits; fresh CLI `ms` is 16/32 bytes while the API remains 16–64 | generation API and CLI `create` | API all-length tests; CLI accepted/rejected/imported-size boundaries | Implemented |
2424
| R17 | worksheet checksum sizes and private residue correction | CLI `checksum`, residue API | ms/cl sizes, short/long and BIP39 residues | Implemented |
2525
| R18 | identifier selection is public metadata | `generation` identifier helpers | k=0 fixture, random defaults, explicit override | Accepted divergence |
2626
| R19 | `cl` custom ID, 32-byte payload, import and generation | `Profile.CL`, `CoreLightningSecret`, `generate_core_lightning_secret` | published examples, import evidence, generation/recovery and padding tests | Implemented |
@@ -33,8 +33,11 @@ Every implemented claim identifies one code owner and direct evidence.
3333
| R26 | explicit account/timestamp, mandatory Core mode, root-xprv warning | wallet API and CLI | deterministic records, public/private separation and warning tests | Implemented |
3434
| R27 | no arbitrary security parser for descriptors | fixed templates in `wallet.py` | module/API absence and template fixtures | Implemented by removal |
3535
| R28 | safe typed installable reference surface | 20-name `__all__`, project script | public abuse tests, mypy, wheel/CLI checks | Implemented |
36+
| R29 | explicit share-index selectors are bounded before copying or normalizing elements | `generation._indices` | oversized string pre-normalization regression across all three public generation APIs | Implemented from standard security scan |
3637

3738
The expanded checksum rule from PR #2258 is the only pending-upstream behavior.
38-
It has direct boundary fixtures and is isolated in one format-layer function.
39-
All remaining production-release work and gate dependencies are recorded in
40-
[the production-ready v1 completion plan](production-ready-v1.md).
39+
Its 44--46-byte compatibility exposure is explicitly accepted, has direct
40+
boundary fixtures, and is isolated in one format-layer function. The controls
41+
and review trigger are in the [accepted-risk register](accepted-risks.md). All
42+
remaining production-release work and gate dependencies are recorded in the
43+
[production-ready v1 completion plan](production-ready-v1.md).

0 commit comments

Comments
 (0)