Repository navigation
errors: Move friendly recovery messages into library - #158
BenWestgate wants to merge 1 commit into
Conversation
Move friendly recovery and checksum diagnostics from the CLI exception map to the underlying library errors. Keep all exception types and validation decisions unchanged so callers of the public API and interactive commands see the same message. Distinguish an invalid threshold-zero recovery input from two valid shares with mismatched thresholds, and assert the messages in the existing malformed-checksum and share-set tests. Refs #156
BenWestgate
left a comment
There was a problem hiding this comment.
Security diff review — PR #158 at 9404991 (base 50cbc19).
Result: no actionable security findings in the scoped patch. The completed Codex Security diff scan reviewed the two changed source files against the repository security invariants, including checksum-validation order, incompatible-share rejection, correction confirmation, and diagnostic confidentiality.
bip93.py: the new text changes only the arguments to existing exception types; checksum verification, threshold tests, share-set validation, interpolation, and output reparse are unmodified. A threshold-0 recovery input now gets the correct distinct diagnostic._cli_input.py: removal of_FRIENDLY_SET_ERRORSdelegates rendering to library exceptions; CLI rejection and retry branches, parse-error handling, correction eligibility, and exact-string confirmation are unchanged.- Data exposure: messages contain no seeds, share contents, private keys, or application prefix values.
- Regression evidence: direct checksum and sharing tests now assert the exception messages; 89 focused tests passed locally. Ruff lint and format checks pass. Nine GitHub Actions jobs passed at review time; the Ubuntu/Python 3.13 optimized-mode job was still running.
The scan is a focused diff review, not an audit of unrelated code. Keep this PR draft until the full matrix is green and the stacked #155 → #157 base PRs are merged/rebased. PR #146 includes the earlier B3 implementation; deduplicate that overlap before integrating both branches.
Security report: completed scan b8ed210a-5db6-4492-98ff-779f20a44e89 (0 findings).
BenWestgate
left a comment
There was a problem hiding this comment.
cACK
a few questions about making the messages more dynamic and relevant.
| Header._from_symbols(body[:6]) | ||
| if not bech32_verify_checksum(hrp, encoded, checksum): | ||
| raise InvalidChecksum(f"invalid {checksum.kind} checksum") | ||
| raise InvalidChecksum("The checksum does not match.") |
There was a problem hiding this comment.
should we keep {checksum.kind} or not?
There was a problem hiding this comment.
Drop it. Which checksum applies follows from the string's length, and naming it ("invalid Long codex32 checksum") gives the user nothing to act on. #161 drops it too, so both codex32 and bech32 checksum failures read "The checksum does not match." The kind field stays because tests/test_crc.py reads it.
Generated by Claude Code
| for item in copied[1:]: | ||
| if item.hrp != first.hrp: | ||
| raise MismatchedHrp(f"{first.hrp} and {item.hrp} cannot be combined") | ||
| raise MismatchedHrp("These strings are for different applications.") |
There was a problem hiding this comment.
did you like showing the mismatch or is the new string best?
There was a problem hiding this comment.
this diff uses strings repeatedly but sometimes a more user friendly message may say shares when the provided data was all non-S
There was a problem hiding this comment.
Showing the mismatch is better. #161 keeps the library's "ms and bip39_12w cannot be combined", which you said seemed fine earlier today. On shares versus strings: #161 (10c8f9b) now says "shares" when every input is an ordinary share and "strings" when a secret is among them.
Generated by Claude Code
| raise MismatchedThreshold("These strings require different numbers of shares.") | ||
| if item.header.identifier != first.header.identifier: | ||
| raise MismatchedIdentifier("share identifiers do not match") | ||
| raise MismatchedIdentifier("These strings have different identifiers.") |
There was a problem hiding this comment.
this is a situation where strings or shares could be correct. i nit slightly prefer to use the one our set is.
There was a problem hiding this comment.
Done in #161 (10c8f9b). The threshold, identifier and length mismatches all pick the noun from the set itself. "These strings require different numbers of shares" also became "These shares have different thresholds" (or "These strings have…") so that it reads well with either noun.
Generated by Claude Code
| ) | ||
| if item_shape != (len(first.payload_symbols), checksum_length, encoded_length): | ||
| raise MismatchedPayloadLength("share payload/checksum lengths do not match") | ||
| raise MismatchedPayloadLength("These strings have different lengths.") |
There was a problem hiding this comment.
same shares sometimes, strings other times.
There was a problem hiding this comment.
| @@ -342,7 +342,7 @@ def recover_secret(shares: Sequence[Share]) -> Secret: | |||
| """Recover S from exactly k compatible, distinct ordinary shares.""" | |||
| share_set = _validate_share_set(shares) | |||
| if any(isinstance(item, Secret) for item in share_set.artifacts): | |||
| raise SecretInRecoverySet("recovery accepts ordinary shares only") | |||
| raise SecretInRecoverySet("Enter ordinary shares rather than the shared secret.") | |||
There was a problem hiding this comment.
why are we duplicating this logic?
There was a problem hiding this comment.
We shouldn't. In #161 (10c8f9b), _validate_recovery_prefix takes require_exact and returns the share set, and recover_secret calls it, so the secret-in-recovery check lives in one place. The order of checks is unchanged. Full suite passes (910, normal and -O).
Generated by Claude Code
Say "shares" when every input is an ordinary share and "strings" when a secret is among them, and word the threshold mismatch as "different thresholds" so it reads well either way. recover_secret now reuses _validate_recovery_prefix instead of repeating its secret check. Addresses Ben's review of the duplicate #158. Claude-Session: https://claude.ai/code/session_01Q1TNm1YeYW8K9W2eBvxmCD
|
Closing as superseded by #161 (head #161 received a focused security review of the exact |
Move the CLI's
_FRIENDLY_SET_ERRORSmessages into the library exceptions so interactive recovery, redirected input, and direct API users receive the same diagnostic. This makes the library the single source of truth for share-set and checksum errors, as decided in #156.What changes
Security review scope
Review error handling in
bip93.pyand_cli_input.py: ensure all invalid inputs remain rejected, no wrong share sets can be combined, error messages do not expose backup data, and interactive recovery never substitutes diagnostics for confirmation. The diff does not change predicates, branch conditions, interpolation, or correction behavior.Stacking
Draft PR stacked on #157 (itself stacked on #155); retarget after upstream merges. PR #146 contains an earlier similar B3 cleanup and will need deduplication/rebase before that work is merged.
Refs #156
Verification (22 October 2026)
test_bip93.py,test_cli_lengths.py); Ruff lint and formatting passed.50cbc19..9404991: complete, 0 findings (scanb8ed210a-5db6-4492-98ff-779f20a44e89); review 5612446714.