Skip to content

Commit 28b06c6

Browse files
committed
cli: Remove pre-review dead code
Remove unreachable creation guards and the permanently false correction ambiguity field, align the CLI test Core stub with production, and move reference-only correction helpers out of the installed package.\n\nSecurity: fail-closed correction and wallet behavior are unchanged.\n\nRefs #38.
1 parent eb7cfdd commit 28b06c6

8 files changed

Lines changed: 71 additions & 86 deletions

File tree

‎src/codex32/_cli_input.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ def _correction_candidates(
452452
deadline: float | None = None,
453453
capture_layers: list[tuple[int, int]] | None = None,
454454
fingerprint_match: Callable[[CorrectionCandidate], bool | None] | None = None,
455-
) -> tuple[tuple[CorrectionCandidate, ...], bool, float | None, bool]:
455+
) -> tuple[tuple[CorrectionCandidate, ...], bool, float | None]:
456456
count = len(value.replace(" ", ""))
457457
targets, primary, reduced, _timed = _correction_plan(profile, byte_length, count, target)
458458
deadline = monotonic() + 10 if deadline is None else deadline
@@ -478,7 +478,7 @@ def _correction_candidates(
478478
if len(candidates) == 1 and not candidates[0].search_complete
479479
else ()
480480
)
481-
return results, complete, deadline, False
481+
return results, complete, deadline
482482

483483

484484
def _fingerprint_matcher(

‎src/codex32/cli.py‎

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -426,17 +426,13 @@ def _create(
426426
raise _UsageError("--bytes applies only to a new random seed.")
427427
if not (sys.stdin.isatty() and sys.stdout.isatty()):
428428
raise _UsageError("Bitcoin backup creation requires an interactive terminal.")
429-
if threshold and not sys.stdin.isatty():
430-
raise _UsageError("Shared creation requires an interactive terminal.")
431429
if threshold and shares is None and indices is None:
432430
if threshold in (2, 3):
433431
shares = {2: 3, 3: 5}[threshold]
434432
else:
435433
raise _UsageError("For thresholds 4 through 9, choose --shares or --indices.")
436434
core = _connected_core()
437435
source = _creation_source(profile, core.fingerprint) if existing else None
438-
if not existing and not sys.stdin.isatty() and _text("", optional=True):
439-
raise _UsageError("Use --existing when supplying a seed or secret.")
440436
if isinstance(source, (Share, Secret)) and not isinstance(source, MasterSeed):
441437
raise _UsageError(f"Enter one {_profile_rules(profile).label}, not a share or another backup type.")
442438
try:
@@ -450,13 +446,8 @@ def _create(
450446
else:
451447
secret = _generated_secret(source, byte_length, identifier, core.fingerprint_seed)
452448
_emit(secret, False, fingerprint=core.fingerprint)
453-
if sys.stdin.isatty():
454-
_confirm_card(secret)
455-
return (
456-
_initialize_wallet(core, secret, timestamp=0 if existing else "now", fresh=not existing)
457-
if core is not None
458-
else 0
459-
)
449+
_confirm_card(secret)
450+
return _initialize_wallet(core, secret, timestamp=0 if existing else "now", fresh=not existing)
460451
if isinstance(source, MasterSeed):
461452
ceremony = CreationCeremony.from_secret(
462453
source,
@@ -494,10 +485,7 @@ def _create(
494485
_print(f"Recovery card {position + 1} of {output_count} confirmed.", err=True)
495486
finished = ceremony.finish()
496487
assert isinstance(finished, MasterSeed)
497-
if core is not None:
498-
return _initialize_wallet(core, finished, timestamp=0 if existing else "now", fresh=not existing)
499-
_print("\nEvery recovery card was confirmed from its re-entered text.", err=True)
500-
return 0
488+
return _initialize_wallet(core, finished, timestamp=0 if existing else "now", fresh=not existing)
501489

502490

503491
def _correct(
@@ -573,17 +561,17 @@ def _correct(
573561
capture_layers: list[tuple[int, int]] = []
574562
candidates: tuple[CorrectionCandidate, ...]
575563
if candidate is not None:
576-
candidates, complete, deadline, ambiguous = (candidate,), True, None, False
564+
candidates, complete, deadline = (candidate,), True, None
577565
else:
578-
candidates, complete, deadline, ambiguous = _correction_candidates(
566+
candidates, complete, deadline = _correction_candidates(
579567
search_value,
580568
hrp,
581569
byte_length,
582570
immutable,
583571
capture_layers=capture_layers,
584572
)
585573
if complete and not candidates and erased != search_value:
586-
candidates, complete, deadline, ambiguous = _correction_candidates(
574+
candidates, complete, deadline = _correction_candidates(
587575
erased,
588576
hrp,
589577
byte_length,
@@ -593,8 +581,6 @@ def _correct(
593581
)
594582
if not complete and not candidates:
595583
raise _CommandError("The correction search did not complete within ten seconds.")
596-
if ambiguous:
597-
raise _CommandError("More than one correction is possible; none was selected.")
598584
if not candidates:
599585
raise _CommandError("No valid correction found. Check the original backup.")
600586
if context.master_seed and len(candidates) > 1:

‎src/codex32/indel.py‎

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,6 @@ def _classes(unit: int, depth: int) -> tuple[_StructuralClass, ...]:
8888
_CHARACTER_CLASSES = _classes(1, 4)
8989
_GROUP_CLASSES = _classes(4, 2)
9090
_CLASSES = (_FIXED, *_CHARACTER_CLASSES, *_GROUP_CLASSES)
91-
# Compatibility for older offline tools; public search uses the full class set.
92-
_REDUCED_CLASSES = tuple(shape for shape in _CLASSES if shape.unit == 4 or shape.distance <= 3)
9391

9492

9593
def _group_boundary(immutable_length: int) -> int:
@@ -161,44 +159,6 @@ def _alignment_counts(
161159
return counts
162160

163161

164-
def _alignment_count(
165-
shape: _StructuralClass,
166-
observed_length: int,
167-
target_length: int,
168-
immutable_length: int,
169-
) -> int:
170-
return sum(_alignment_counts(shape, "q" * observed_length, target_length, immutable_length).values())
171-
172-
173-
def _reductions(
174-
values: tuple[int, ...],
175-
characters: str,
176-
count: int,
177-
offset: int,
178-
) -> Iterator[tuple[tuple[int, ...], tuple[tuple[int, str], ...]]]:
179-
for deleted in combinations(range(len(values)), count):
180-
removed = frozenset(deleted)
181-
kept = tuple(index for index in range(len(values)) if index not in removed)
182-
retained = tuple(values[index] for index in kept)
183-
position = 0
184-
for kept_index in kept:
185-
while values[position] != values[kept_index]:
186-
position += 1
187-
if position != kept_index:
188-
break
189-
position += 1
190-
else:
191-
edits = tuple((offset + index, characters[index]) for index in deleted)
192-
yield retained, edits
193-
194-
195-
def _variants(
196-
text: str, target: int, shape: _StructuralClass, immutable: int, prefix_length: int = 3
197-
) -> Iterator[_Variant]:
198-
for view in _views(text, target, shape, immutable, prefix_length):
199-
yield _view_variant(view, text, prefix_length)
200-
201-
202162
def _views(text: str, target: int, shape: _StructuralClass, immutable: int, base: int) -> Iterator[_View]:
203163
source = tuple(CHARSET.find(char.lower()) for char in text[base:])
204164
initial = _View(source, ((0, len(source)),), len(source))

‎tests/test_cli.py‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ class _FakeBitcoinCore:
9393
chain: str = "main"
9494
version: int = 320000
9595
imported: MasterSeed | None = None
96-
private: bool | None = None
9796
account: int | None = None
9897
timestamp: int | str | None = None
9998

@@ -109,12 +108,11 @@ def initialize(
109108
_ask: Callable[[str], str],
110109
_tell: Callable[[str], None],
111110
*,
112-
private: bool = True,
113111
account: int = 0,
114112
timestamp: int | str = "now",
115113
) -> str:
116114
self.imported = secret
117-
self.private, self.account, self.timestamp = private, account, timestamp
115+
self.account, self.timestamp = account, timestamp
118116
return "test-wallet"
119117

120118

@@ -1763,7 +1761,7 @@ def test_correction_bytes_rejects_an_unsupported_ms_size() -> None:
17631761
def test_cli_never_accepts_an_incomplete_structural_search() -> None:
17641762
original = VECTOR_1["secret_s"]
17651763
damaged = original[:19] + original[20:]
1766-
with patch("codex32.cli._correction_candidates", return_value=((), False, 0.0, False)):
1764+
with patch("codex32.cli._correction_candidates", return_value=((), False, 0.0)):
17671765
result = _invoke(["correct"], damaged)
17681766

17691767
assert result.exit_code == 3
@@ -1891,7 +1889,6 @@ def test_wallet_commands_initialize_selected_master_seed_destinations() -> None:
18911889
assert xprv.stderr.endswith("Keep it secret.\n\n")
18921890
assert private.stdout == ""
18931891
assert private_core.imported == parse_codex32(VECTOR_1["secret_s"])
1894-
assert private_core.private is True
18951892
assert "Warning: This imports private descriptors that can spend funds." in private.stderr
18961893
assert "Use only the intended encrypted wallet" not in private.stderr
18971894
assert "\x1b[" not in private.stderr + private.stdout
@@ -2760,7 +2757,7 @@ def test_incomplete_candidate_has_no_search_warning_and_is_never_accepted_automa
27602757
candidate = _correct_fixed(source, suspected_profile=Profile.MS)
27612758
assert candidate is not None
27622759
candidate = replace(candidate, search_complete=False)
2763-
with patch("codex32.cli._correction_candidates", return_value=((candidate,), False, 0.0, False)):
2760+
with patch("codex32.cli._correction_candidates", return_value=((candidate,), False, 0.0)):
27642761
result = _invoke(["correct"], source[:-1] + "?")
27652762
assert result.exit_code == 1 and result.stdout == ""
27662763
assert "Search incomplete" not in result.stderr

‎tests/test_correction_disclosure.py‎

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def test_noninteractive_gate_emits_only_operational_error(entrypoint, plain):
100100
patch.object(sys, "stdin", io.StringIO(VECTOR_1["secret_s"][:-1] + "?")),
101101
contextlib.redirect_stdout(stdout),
102102
contextlib.redirect_stderr(stderr),
103-
patch.object(cli, "_correction_candidates", return_value=((_candidate(),), True, None, False)),
103+
patch.object(cli, "_correction_candidates", return_value=((_candidate(),), True, None)),
104104
):
105105
status = entrypoint(["correct", *(["--plain"] if plain else [])])
106106
prog = "codex32" if entrypoint is cli.main else "ms32"
@@ -127,9 +127,7 @@ def respond(prompt, prefill=""):
127127
monkeypatch.setattr(_cli_input, "_editable_input", respond)
128128
monkeypatch.setattr(_cli_input, "_suggestions", lambda *args, **kwargs: (candidate,))
129129
monkeypatch.setattr(cli, "_suggestions", lambda *args, **kwargs: (candidate,))
130-
monkeypatch.setattr(
131-
cli, "_correction_candidates", lambda *args, **kwargs: ((candidate,), True, None, False)
132-
)
130+
monkeypatch.setattr(cli, "_correction_candidates", lambda *args, **kwargs: ((candidate,), True, None))
133131
core = _FakeBitcoinCore()
134132
monkeypatch.setattr(cli.BitcoinCore, "connect", lambda *args: core)
135133
stdout, stderr = _TTYOutput(), _TTYOutput()
@@ -163,9 +161,7 @@ def respond(prompt, prefill=""):
163161
return next(responses)
164162

165163
monkeypatch.setattr(_cli_input, "_editable_input", respond)
166-
monkeypatch.setattr(
167-
cli, "_correction_candidates", lambda *args, **kwargs: ((candidate,), True, None, False)
168-
)
164+
monkeypatch.setattr(cli, "_correction_candidates", lambda *args, **kwargs: ((candidate,), True, None))
169165
with (
170166
patch.object(sys, "stdin", _TTYInput()),
171167
contextlib.redirect_stdout(stdout),
@@ -181,9 +177,7 @@ def test_redirected_stderr_blocks_low_discrimination_disclosure(monkeypatch):
181177
candidate = _candidate(source)
182178
responses = iter((source[:-1] + "?",))
183179
monkeypatch.setattr(_cli_input, "_editable_input", lambda *args, **kwargs: next(responses))
184-
monkeypatch.setattr(
185-
cli, "_correction_candidates", lambda *args, **kwargs: ((candidate,), True, None, False)
186-
)
180+
monkeypatch.setattr(cli, "_correction_candidates", lambda *args, **kwargs: ((candidate,), True, None))
187181
stdout, stderr = io.StringIO(), io.StringIO()
188182
with (
189183
patch.object(sys, "stdin", _TTYInput()),
@@ -218,9 +212,7 @@ def confirm(prompt):
218212
return "YES"
219213

220214
monkeypatch.setattr(_cli_input, "_confirmation_input", confirm)
221-
monkeypatch.setattr(
222-
cli, "_correction_candidates", lambda *args, **kwargs: ((candidate,), True, None, False)
223-
)
215+
monkeypatch.setattr(cli, "_correction_candidates", lambda *args, **kwargs: ((candidate,), True, None))
224216
stdout, stderr = io.StringIO(), _TTYOutput()
225217
with (
226218
patch.object(sys, "stdin", io.StringIO(source[:-1] + "?")),

‎tests/test_correction_indel.py‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,19 @@
1919
_CLASSES,
2020
_FIXED,
2121
_GROUP_CLASSES,
22-
_REDUCED_CLASSES,
23-
_alignment_count,
2422
_alignment_counts,
2523
_capacities,
2624
_frontier,
2725
_keep,
2826
_prepare,
29-
_reductions,
3027
_required_header_substitutions,
3128
_search_many,
3229
_search_target,
3330
)
3431
from codex32.profiles.ms32 import TEXT_LENGTHS
3532
from tools._wallet_reference import fingerprint_seed
3633
from tools.correction_capture import cross_length_classes
34+
from tools.correction_reference import _REDUCED_CLASSES, _alignment_count, _reductions
3735

3836
SOURCE = VECTOR_1["secret_s"]
3937
CONTEXT = CorrectionContext(Profile.MS, expected_length=len(SOURCE))

‎tools/correction_benchmark.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,12 @@
1515
from codex32.correction import CorrectionContext
1616
from codex32.indel import (
1717
_CHARACTER_CLASSES,
18-
_alignment_count,
1918
_required_header_substitutions,
2019
_search_many,
21-
_variants,
2220
)
2321
from codex32.profiles import Profile
2422
from codex32.profiles.ms32 import TEXT_LENGTHS
23+
from tools.correction_reference import _alignment_count, _variants
2524

2625
DELTAS = (-8, -4, -3, -2, -1, 0, 1, 2, 3, 4, 8)
2726
AUTOMATIC_48_COUNTS = (40, *range(44, 53), 56)

‎tools/correction_reference.py‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""Reference-only structural helpers used by tests and correction benchmarks."""
2+
3+
from collections.abc import Iterator
4+
from itertools import combinations
5+
6+
from codex32.indel import (
7+
_CLASSES,
8+
_alignment_counts,
9+
_StructuralClass,
10+
_Variant,
11+
_view_variant,
12+
_views,
13+
)
14+
15+
_REDUCED_CLASSES = tuple(shape for shape in _CLASSES if shape.unit == 4 or shape.distance <= 3)
16+
17+
18+
def _alignment_count(
19+
shape: _StructuralClass,
20+
observed_length: int,
21+
target_length: int,
22+
immutable_length: int,
23+
) -> int:
24+
return sum(_alignment_counts(shape, "q" * observed_length, target_length, immutable_length).values())
25+
26+
27+
def _reductions(
28+
values: tuple[int, ...],
29+
characters: str,
30+
count: int,
31+
offset: int,
32+
) -> Iterator[tuple[tuple[int, ...], tuple[tuple[int, str], ...]]]:
33+
for deleted in combinations(range(len(values)), count):
34+
removed = frozenset(deleted)
35+
kept = tuple(index for index in range(len(values)) if index not in removed)
36+
retained = tuple(values[index] for index in kept)
37+
position = 0
38+
for kept_index in kept:
39+
while values[position] != values[kept_index]:
40+
position += 1
41+
if position != kept_index:
42+
break
43+
position += 1
44+
else:
45+
edits = tuple((offset + index, characters[index]) for index in deleted)
46+
yield retained, edits
47+
48+
49+
def _variants(
50+
text: str, target: int, shape: _StructuralClass, immutable: int, prefix_length: int = 3
51+
) -> Iterator[_Variant]:
52+
for view in _views(text, target, shape, immutable, prefix_length):
53+
yield _view_variant(view, text, prefix_length)

0 commit comments

Comments
 (0)