Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/security/model.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ output is an untrusted proposal, never authenticated recovery material.
|---|---|
| Context | The syntactically present HRP, separator, and program-supplied context are immutable and outside the correction domain. Unknown HRPs are never corrected or ranked toward registered namespaces. |
| Target lengths | `ms` searches only 48, 54, 61, 67, 74, or 127 characters; an accepted first string fixes the length of later strings in that recovery set. |
| Character alignment | `A = I + O + AT + 2*T <= 4`; the public API retains its `A<=2` required baseline. CLI scheduling prioritizes single structural mistakes, paired indels, then other combinations. |
| Character alignment | `A = I + O + AT + 2*T <= 4`; the public API retains its `A<=2` required baseline. CLI scheduling prioritizes single structural mistakes and bounded two-group erasure probes, then paired indels and other combinations. |
| Group alignment | `G = GI + GO + GS + GAT + 2*GT <= 2`; only complete displayed four-character groups participate, and character/group families never mix. |
| Fixed erasures | BCH repairs substitutions and explicit/generated erasures after alignment, with `E + 2S <= 8`; fixed consecutive erasures retain the 13/15-symbol linear path. |
| False reconstruction | All target lengths and all admitted fixed/character/group layers share cumulative mass at or below `1`, including fixed consecutive erasures. |
Expand Down
3 changes: 2 additions & 1 deletion src/codex32/_competitors.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ def _check_deadline(deadline: float) -> None:


def _tier(shape: _StructuralClass) -> int:
if len(shape.operations) == 1:
# Two complete groups take only a bounded set of eight-erasure BCH probes.
if len(shape.operations) == 1 or (shape.unit == 4 and shape.corrupted == 2):
return 0
return 1 if shape.inserted == shape.omitted == 1 and len(shape.operations) == 2 else 2

Expand Down
20 changes: 19 additions & 1 deletion tests/test_correction_indel.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
from dataclasses import replace
from itertools import combinations
from math import comb
from time import monotonic
from unittest.mock import patch

import pytest
from data.bip93_vectors import VECTOR_1, VECTOR_5, VECTOR_6
from test_bip39 import BIP39_12W_ZERO, BIP39_24W_ZERO

from codex32 import CorrectionContext, MasterSeed, Profile, correct
from codex32._cli_input import _correction_plan, _fingerprint_matcher
from codex32._cli_input import _correction_candidates, _correction_plan, _fingerprint_matcher
from codex32.correction import CorrectionCandidate, _best, _capture_volume, _erasure_state, _primary
from codex32.generation import _fingerprint_identifier
from codex32.indel import (
Expand Down Expand Up @@ -171,6 +172,23 @@ def test_automatic_secondary_search_recovers_two_group_indels(
assert [candidate.artifact.text for candidate in candidates] == [source]


def test_cli_probes_two_corrupted_groups_within_search_deadline() -> None:
source = MasterSeed.from_seed(bytes(range(16)), identifier="test").text
damaged = source[:12] + "pppp" + source[16:24] + "qqqq" + source[28:]

candidates, _complete, _deadline = _correction_candidates(
damaged,
Profile.MS,
None,
"ms1",
target=len(source),
deadline=monotonic() + 3,
)

assert [candidate.artifact.text for candidate in candidates] == [source]
assert candidates[0].erasures_filled == 8


def test_group_class_set_and_first_share_counts_are_exact() -> None:
assert {(s.inserted, s.omitted, s.corrupted, s.adjacent, s.distant) for s in _GROUP_CLASSES} == {
(i, o, gs, at, t)
Expand Down
Loading