escrow: a recovery code can no longer contain a hyphenated word (v0.93.0)
The EFF large list has exactly 4 entries containing the join separator (drop-down, felt-tip, t-shirt, yo-yo). Drawing one made a code read as 11 words instead of 10 - ambiguous to transcribe in precisely the situation R exists for. Filter them at init; the draw space goes 7776 -> 7772 and the 10-word code goes 129.248 -> 129.241 bits, still well over the 128 floor. Generation-only: already-issued codes stay valid, R is verified as a whole passphrase and never re-split. Also fixes the ~1/5 flake this same defect caused: the test counted words by splitting the joined string. It now counts what the generator drew and asserts segmentation separately, plus a deterministic red-proof fixture.
This commit is contained in:
@@ -1,3 +1,28 @@
|
||||
## v0.93.0 — a recovery code can no longer contain a hyphenated word (2026-07-21)
|
||||
|
||||
**Generation-only change. Every recovery code already issued remains valid** — R is consumed as a
|
||||
whole passphrase by the PBS scrypt KDF (`Wrap`/`Unwrap`) and is never re-split, so nothing about
|
||||
verification moves. Nothing in the KDF/consume path, the word count, or the joiner changed.
|
||||
|
||||
The EFF large wordlist contains exactly four entries that themselves contain the hyphen we join
|
||||
words with: `drop-down`, `felt-tip`, `t-shirt`, `yo-yo`. Drawing one produced a code that reads as
|
||||
11 words rather than 10 — ambiguous to transcribe in exactly the situation R exists for, a customer
|
||||
reading a code back during a disaster. The generator now draws from the list filtered of those four
|
||||
(`joinSafe`), so a code always segments back into exactly `RecoveryCodeWords`.
|
||||
|
||||
- **Entropy floor holds, with the numbers asserted in the tests:** the draw space goes 7776 → 7772,
|
||||
so a 10-word code goes 129.248 → 129.241 bits. The cost is 0.007 bits against a 128-bit floor.
|
||||
- **The long-standing ~1/5 test flake was this defect, not a flaky test.**
|
||||
`TestGenerateRecoveryCode_EntropyAndFormat` counted words by splitting the joined string, which
|
||||
conflates "how many words were drawn" with "how many segments the code has". It now counts what
|
||||
the generator drew, and asserts the segmentation property separately — the property `joinSafe`
|
||||
actually buys. (`felhom.eu/REPORT.md` §6 item 3 recorded it at 3/8 in one session.)
|
||||
- **Deterministic red-proof, in-tree:** `TestGeneratedCodeSegments_FilteredVsUnfiltered` drives the
|
||||
generator against a fixture list where every word is hyphenated, so the pre-fix defect reproduces
|
||||
with probability 1 instead of ~1/5, and shows the same list through `joinSafe` refuses to generate.
|
||||
- New for audit: `WordlistFilteredOut()`, `RecoveryCodeSep`. `WordlistSize()` now reports the
|
||||
effective (filtered) draw space, 7772.
|
||||
|
||||
## v0.92.1 — ship the guestnet sudoers grant with the binary (supersedes v0.92.0) (2026-07-21)
|
||||
|
||||
**Supersedes v0.92.0; that artifact is materially incomplete — do not vouch it.** It was published
|
||||
|
||||
Reference in New Issue
Block a user