Land #213, #215, #216, #217 and #218 on main - #219
Merged
Merged
Conversation
The roadmap's sequencing table stopped at 4.5. Everything #210 landed is now a row: the end-to-end review fixes, the FORMAT-V2-DESIGN §4.8 password-and-shares slot, the paper vault's second pass (set code, presets, version-25 symbols, self-contained strips, the printout check), photo and live camera scanning, and RECOVERY.md being executed by recovery_test.py. Phase 8 now says what it waits on: the owner, after the next tag. The Cut table's steganography row records why the audio carrier stays: it is documented as a carrier, not steganography, and claims nothing that row cuts. TEN-X-PLAN.md said Bet 1 (camera scanning) was on hold in three places. It shipped in 22c7cdf and 161e919, and the decoder decision that held it was taken as BarcodeDetector where present and pinned jsqr where not. The plan now says so, and says what from the sketch did not ship. Release prep: package.json and the lockfile go to 2.3.0, a minor version because the changelog carries an additive format change (slot type 0x03), and the changelog's Unreleased section becomes the v2.3.0 heading. SECURITY.md's supported-versions table names only "latest release" and needs no change. Gates run on this tree: test:release-notes, test:release-gate and test:release-recipe all pass. The tag itself is the owner's to push: git tag -a v2.3.0 and git push origin v2.3.0.
v2 §8 and v3 §7 both deferred the length leak: a container's length determined its plaintext's byte for byte, so a backup's size said whether it held a password, a 12-word seed or a 24-word one. docs/FORMAT-V4-DESIGN.md is the padding scheme on its own, as both deferrals asked for. v4 is a delta on v3 that changes one thing. The payload seals a stream of an 8-byte length prefix, the plaintext and zeros, padded to 256 bytes for anything up to 248 and by Padmé above that, so the overhead stays under 7% and the length reveals only the bucket. Header, container_id, slot table, MAC, chunking, nonces and AAD are v3's, and the version byte sits inside every AAD, so a relabelled container opens in neither direction. The reader verifies the prefix, the bucket and every padding byte, and every failure is the generic one. Written in the house order. The section first; reference/keym2.py from the section alone (encrypt --pad, inspect reporting padded bytes rather than a plaintext length, and a self-test section of 71 checks including the published vector); then the TypeScript core, the app's dispatch, the inspector and the self-extract profile; then seven frozen fixtures, six at the floor and one past it; then crosstest2.py comparing the emitted bytes across both KDFs, three ciphers and every stream boundary, holding both to the vector, and requiring the TypeScript to refuse every stream the reference refuses. RECOVERY.md's commands claim v4 and recovery_test.py runs them against v4 containers the app wrote. Writers MAY emit v4; the default stays v3, because the cost lands on the writer's medium and on paper bytes are symbols. The self-extracting page keeps its v3 container and its writer refuses v4. The app opens a v4 backup today; the switch to write one is separate. Negative controls on the reference: the zero check removed fails the three step-5 checks, the bucket check removed fails the two step-4 checks, and a 512-byte floor fails the pinned table and the vector.
…ance Beside the Sigstore signature, not in place of it. The signature over SHA256SUMS is what docs/VERIFYING.md teaches and what the notes print; the attestation is a second, independent statement about the same bytes, SLSA provenance signed through GitHub's Sigstore instance by the same run, that `gh attestation verify` checks with nothing but GitHub's CLI. The sign job gains `attestations: write` and one pinned step, placed after the unpacked archive has passed the sha256sum check so a broken package is never attested. VERIFYING.md documents the command and why --signer-workflow is not optional, and the changelog records it. The three release gates pass.
encryptor-tool.tsx had grown to 6,459 lines. Move its code, unchanged, into src/components/encryptor/: shared.ts (types, constants, pure helpers), shared-ui.tsx (LockWarning, FileSelector, RevealableQr, InfoTip), use-encryptor-state.ts (the state and handlers more than one tab reads or writes, exposed through EncryptorContext), secret-form.tsx (the Encrypt and Decrypt tabs, which turn out to be one shared render function rather than two), workspace-tab.tsx, recovery-tab.tsx, shares-dialog.tsx and recovery-kit-dialog.tsx. encryptor-tool.tsx stays the entry point, now composing these modules; it drops to a few hundred lines of Tabs/header/footer chrome. No behaviour change: every string, id, data-testid, and handler moved rather than being rewritten.
KeymakerOptions gains `padded`, off by default. encryptContainer and encryptContainerWithSharesRequired pass it to the keym-v2 writer as the version, so the worker and its no-worker fallback, which both call them, write the same thing. The dispatch test pins it: padded writes v4 and opens through decryptData, and without it the app still writes v3.
Two things the format work left for the app, on the split's modules. The switch. v4 §6 makes padding the owner's choice and does not move the default, because the cost lands on paper: more bytes are more symbols. So "Hide the size of what is inside" sits under Advanced on the Encrypt tab, off until turned on, with the cost beside it, and passes `padded` to the writer. The browser spec shows a one-byte and a 200-byte secret sealing to the same length as v4, a padded backup opening here with the padding gone, and the default unchanged. The re-seal offer. v3 §6: moving a v2 backup to v3 means decrypting and re-encrypting it, and it is the owner's decision. The Decrypt tab now makes the offer with the backup open, for v2, v1 and IttyBitz files, and says what is not carried over. It is not a one-click re-encrypt: the password is cleared on success (U13) and the plaintext erased, so the button carries the recovered text to the Encrypt tab as its input (a file, downloaded and not kept, is asked for) and the ordinary seal writes today's format with a password the owner types. A v3 or v4 backup gets no offer. Both specs were written first and failed against the tree without the UI.
…ing' into claude/serene-carson-0739mv
…t' into claude/serene-carson-0739mv # Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why this PR exists
#213, #215, #216, #217 and #218 were merged this morning, but each merged into the branch below it in the stack rather than into
main, because their bases were never retargeted after #212 merged.mainhas #212 and #214 and nothing else from the stack. Checked againstmain's tree, not the merge status: none of the five heads is an ancestor ofmain.This branch is the union. It merges
claude/serene-carson-0739mv-padding(which carries #213, #215, #216, #217) andclaude/serene-carson-0739mv-split(which carries #218) into the designated branch. All five heads are ancestors of this branch's tip, and it merges intomainwith no conflicts.What it carries
package.jsonto 2.3.0,## Unreleasedclosed as## Keymaker v2.3.0.The one hand-made change
CHANGELOG.mdconflicted where #216 and #218 both added entries underUnreleased. Resolved by keeping both sets of entries, and dropping the sentence "a switch to write one is separate" that the split side had already removed, since #218 adds that switch. No other file needed a hand merge.Gates on this tree
npm run typecheck,test:release-notes,test:release-gate,test:secret-erase-coreandtest:keym2-dispatchpass. Each constituent PR passed all 15 CI checks on its own head; this PR's CI run covers the union.Before tagging
The changelog on this branch has
## Unreleased(v4, attestation, the switch, the re-seal) above## Keymaker v2.3.0. Av2.3.0tag onmainafter this merges would ship v4 without its notes. Either fold the Unreleased section into the v2.3.0 heading before tagging, or tag as v2.4.0. Decide that before the tag, not after.Merging
Merge this into
maindirectly. Nothing else is stacked on it.Generated by Claude Code