Repository navigation
Conversation
|
Good to see the combined-seed degenerate-value guard here (all-0x00/all-0xFF rejection) — that case is missing from #738 and is easy to overlook. One thing I couldn't confirm from the diff: parts entered under a non-English wordlist pass per-part validation, but combine_mnemonics_with_xor() and Seed(mnemonic=...) appear to parse with the English default — so finalize could fail with a generic "Invalid mnemonic" after 72+ typed words. Is language handling on the finalize path something you've already looked at, or am I missing where it gets normalized? |
|
Thanks for the close read — and good instinct to trace the language handling through to finalize. You're right that there's an asymmetry in the code: per-part entry validation resolves the wordlist via In practice it can't be triggered today: upstream wordlist support is English-only. That said, the fix is cheap future-proofing: both functions already accept |
f3adb12 to
c903165
Compare
|
Added f3adb12: fix discard-by-value in the SeedXOR finalize options (found in a second red-team pass). Bug: Fix: discard by identity, matching the comment:
Tests: 3 new regression tests in
Full suite: 259/259 pass. |
|
Hardware-validated f3adb12 on Pi Zero v1.3 (image built from seedxor-entry tip, SHA256 442c17bcb0fa...). Test 1 — typed duplicate of a pre-existing seed survives discard:
Test 2 — "Use loaded seed" parts are legitimately discarded:
Both directions behave per the intended identity semantics: typed/scanned parts never touch pre-existing storage; explicitly selected loaded seeds are discarded as chosen. |
|
Can you help me out?
Is this true? I cannot find the function to split a seed into xor-shares, that you claim already exists in seedsigner. Thanks! |
|
You’re right—SeedSigner does not currently provide a Seed XOR splitting flow. That claim in my description was incorrect; thanks for catching it. This PR adds reconstruction from existing XOR parts only, not splitting. I’ve corrected the description. |
|
Fixed F3: canceling after calculating a combined Seed XOR seed no longer leaves that seed accessible through
Verification: 265 tracked-suite tests pass, including 15 new cases. The three bug-catching cases fail on the previous tip and pass with the fix. The local suite including audit PoCs passes 274 tests; the old F3 bug-presence PoC was updated locally and remains untracked. Independent pre-commit review passed with no security or logic findings. Pushed as 7001ad6. No hardware retest or image rebuild for this change yet. F4 is not changed. |
Port of PR SeedSigner#738 (Advaitgaur004) / PR SeedSigner#885 (berlinxray) onto current dev, implemented natively against the Seed-object paradigm (65e312c) rather than the removed seed_num indexing. Feature (rebuild only; splitting out of scope): - Tools/Load A Seed -> 'Rebuild SeedXOR' (behind Advanced->Seed XOR setting, default disabled). - Load parts one at a time via SeedQR scan, 12/24-word entry, or a seed already in memory; each part's fingerprint is shown for confirmation. - Validation rejects passphrases, mnemonic-length mismatches, duplicates, and binary-inversion parts. Finalize requires >= 2 parts. - Finalize combines parts via XOR (combine_mnemonics_with_xor) and offers keep/discard parts + optional BIP-39 passphrase. Design decisions carried over from the issue thread: - Terminology is 'part' (Coldcard seed-xor.md), not 'shard'. - Returning Home wipes in-progress XOR parts (wipe-everything-except- finalized-Seeds guarantee), via clear_rebuild_seedxor_data in the MainMenuView branch of Controller.start(). Co-Authored-By: Advaitgaur004 <advaitgaur004@users.noreply.github.com> Co-Authored-By: berlinxray <berlinxray@users.noreply.github.com>
resume_main_flow was set to FLOW__REBUILD_SEEDXOR when loading a part but only cleared on return to MainMenuView. After finalizing (which lands on SeedOptionsView, not Home), a subsequent normal seed load in the same session would be misrouted from SeedMnemonicEntryView into RebuildSeedXORShowFingerprintView instead of SeedFinalizeView. Clear resume_main_flow when a part is consumed (fingerprint confirm/cancel) and on both finalize branches. Adds a flow regression test that finalizes a 2-part XOR then loads an unrelated seed and asserts it reaches SeedFinalizeView (proven to fail without the fix).
Passphrases are already rejected on XOR parts by SeedXORValidator; offering one on the combined seed contradicted that policy and bypassed the keep/discard-parts choice.
Complement parts are not 'cancelled out' (that's identical parts, already blocked by the duplicate check). The real hazard: A XOR ~A = all-0xFF, a known seed with no entropy. Reword the validator message and test docstring to say that.
…k, test coverage
- validate_part: pass Seed.wordlist to bip39.mnemonic_to_bytes instead of
relying on English default; wrap in try/except so a wordlist mismatch
produces a clean error dict instead of an unhandled exception.
- Add SeedXORValidator.validate_combined_seed: rejects degenerate XOR
results (all-zero / all-ones entropy) that pass BIP39 checksum but are
known, worthless seeds. Wired into RebuildSeedXORFinalizeView.
- Add 16 new tests (211 -> 227):
- Flow tests: DISCARD_PARTS finalize branch, remove-part flow, keep-part
backflow, back-button routing (LoadPart/Manage/Finalize/FinalizeOptions),
cancel-seed-xor flow, Finalize-hidden-with-<2-parts.
- Unit tests: validate_part wordlist forwarding, unparseable mnemonic
handling, validate_combined_seed (valid pass, zero-entropy reject,
all-ones reject).
- Sabotage checks confirm all new tests bite.
Add ScreenshotConfig entries for the Rebuild Seed XOR views (issue SeedSigner#738): load/manage/view/remove parts, fingerprint confirmation, finalize + finalize options, cancel, and the zero-entropy XOR Error view. The feature is gated behind the Advanced setting SETTING__SEED_XOR (default DISABLED), so entries enable it via mock context managers. Reuses the EXAMPLE_12_A/B test mnemonics from tests/seedxor_test_vectors.py.
Three leak sites let a stale FLOW__REBUILD_SEEDXOR flag misroute later unrelated seed loads into RebuildSeedXORShowFingerprintView: 1. RebuildSeedXORShowFingerprintView error branch: clear the flag when a part is rejected (duplicate/inverse/invalid). Previously only the BACK/CANCEL and success branches cleared it. 2. SeedMnemonicEntryView word-0 BACK: disarm the flag when abandoning an XOR part entry so a later normal 'Enter seed' flow is not captured. 3. LoadSeedView REBUILD_SEED_XOR entry: clear any stale flag when starting a fresh rebuild flow (defense in depth). Adds tests/test_redteam_seedxor.py with flow regressions for the typed and scanned leak paths (assert routing to SeedFinalizeView, not the XOR view).
combine_mnemonics_with_xor() and Seed() were silently defaulting to English at finalize while every other mnemonic parsing site honors SETTING__WORDLIST_LANGUAGE. No behavior change today (the app is English-only: the setting is VISIBILITY__HIDDEN and Seed.get_wordlist() raises for non-English codes), but this closes the mixed-language failure mode flagged in review: if upstream ever enables other BIP-39 wordlists, finalize now uses the same language the parts were entered in, instead of failing with a generic "Invalid mnemonic" after 72+ typed words. Adds TestSeedXORWordlistLanguage: a spy test asserting both calls receive the settings value, and a fails-closed test asserting an unrecognized language routes to ErrorView without storing a combined seed.
DISCARD_PARTS used 'part in storage.seeds' and discard_seed -> list.remove, both riding Seed.__eq__ (seed_bytes equality). A part TYPED or SCANNED fresh whose mnemonic equals a seed loaded before the flow is a distinct object equal by value; discard silently deleted that pre-existing user seed - unwarned, possibly the user's only in-memory copy. - seed_views.py: FinalizeOptionsView DISCARD_PARTS now checks identity (any(part is s ...)) before discarding, matching the existing code comment that already claimed identity semantics. - controller.py: discard_seed() removes by identity (s is seed) instead of list.remove(seed); silent no-op when the seed object is not in storage. Regression tests (tests/test_redteam_seedxor.py::TestDiscardPartsByIdentity): - typed duplicate of a pre-existing seed survives DISCARD_PARTS (flow test) - a 'Use loaded seed' part IS the same object and is legitimately discarded - discard_seed does not remove an equal-but-distinct seed (unit) Both flow tests fail on the unfixed code and pass with the fix. Full suite: 259/259 pass.
7001ad6 to
2f24e29
Compare
|
Rebased onto current Fixed F4: abandoned Seed XOR parts no longer silently carry into a new rebuild.
Verification: 382 tracked tests pass; 391 including the nine local audit PoCs. Independent pre-commit review found no security or logic issues. @kdmukai could you review when you have time? The latest F3/F4 changes still need hardware retesting; I will report that separately. |
|
Built a SeedSigner OS SHA256: The image build completed successfully; hands-on hardware retesting of F3/F4 is still pending. |
Description
Problem or Issue being addressed
Implements the long-requested Seed XOR rebuild flow from issue #738.
Seed XOR (documented by Coldcard in seed-xor.md) lets users split a seed into N parts that are combined by bitwise XOR. SeedSigner does not currently provide a Seed XOR splitting or reconstruction flow. This PR adds on-device reconstruction of a seed from existing Seed XOR parts, so users can restore their wallet without first combining the parts on another system.
This is a port of PR #738 (Advaitgaur004) / PR #885 (berlinxray) onto current
dev, implemented natively against the Seed-object paradigm (65e312c) rather than the removedseed_numindexing.Solution
Rebuild only; creating XOR parts from an existing seed is outside this PR’s scope.
Seed XOR(default disabled; hidden in the General settings UI).helpers/seed_xor_validator.py) rejects: passphrases on parts, mnemonic-length mismatches between parts, duplicate parts, and binary-inversion parts (XORing a part with its complement is a footgun). Finalize requires >= 2 parts.combine_mnemonics_with_xorand shows the resulting fingerprint, then offers keep/discard of the loaded parts. A degenerate all-zero-entropy result (the publicly-known "abandon…about" seed) is detected and rejected with an explicit error screen instead of silently loading a worthless wallet.clear_rebuild_seedxor_datain the MainMenuView branch ofController.start().Also extends
tests/screenshot_generator/generator.pywith the full set of Rebuild Seed XOR screens so the flow is covered by the standard per-locale screenshot generation.Additional Information
tests/test_seedxor.py(unit: XOR combination + validators),tests/test_flow_seedxor.py(FlowTests for the full rebuild flow),tests/test_redteam_seedxor.py(security and state-lifecycle regressions),tests/seedxor_test_vectors.py(shared vectors). Full tracked suite: 382 passed; 57 Seed XOR tests pass.Screenshots
All 240x240, generated with
tests/screenshot_generator/generator.py --locale en(the generator entries are part of this PR):Load A Seed with "Rebuild SeedXOR" entry visible (Advanced setting enabled):
Manage parts — empty / one / two loaded:
Load a part / fingerprint confirmation / select an already-loaded seed:
View / remove loaded parts:
Finalize (combined fingerprint) / finalize options / cancel:
Degenerate-result guard (zero-entropy XOR is rejected):
This pull request is categorized as a:
Checklist
I ran
pytestlocallypytest tests/ --ignore=tests/screenshot_generator: 382 passed (Python 3.11, deps per requirements.txt; l10n catalogs compiled as in CI). The 57 Seed XOR tests all pass.tests/screenshot_generator/generator.py --locale en: 1 passed (used to produce the screenshots above).I included screenshots of any new or modified screens
Should be part of the PR description above.
I added or updated tests
Any new or altered functionality should be covered in a unit test. Any new or updated sequences require FlowTests.
I tested this PR hands-on on the following platform(s):
I have reviewed these notes:
Thank you! Please join our Devs' Telegram group to get more involved.