Skip to content

fix(quagmire3): default alignment to keyed alphabet's first letter - #3

Merged
0xdiid merged 3 commits into
mainfrom
fix/quagmire3-keyed-alignment
Jul 20, 2026
Merged

fix(quagmire3): default alignment to keyed alphabet's first letter#3
0xdiid merged 3 commits into
mainfrom
fix/quagmire3-keyed-alignment

Conversation

@0xdiid

@0xdiid 0xdiid commented Jul 15, 2026

Copy link
Copy Markdown
Owner

No description provided.

0xdiid and others added 3 commits July 14, 2026 12:31
QuagmireIII.decode/encode defaulted the ACA alignment letter to a hardcoded
straight-'A', which silently returned garbage for any keyed alphabet whose
first letter is not 'A'. The only existing test vector (AUTOMOBILE/HIGHWAY)
cannot catch this because there header[0] == 'A', so the two conventions coincide.

The codebase's own solver already documents and emits the correct convention
(`# Q3: ... align = first keyed letter`, keys formatted as
KEYWORD/INDICATOR/<first-keyed-letter>), so decode was inconsistent with crack.

Default the alignment to the keyed alphabet's first letter (header[0]) — i.e.
a plain Vigenere in the keyed alphabet. AUTOMOBILE/HIGHWAY is unchanged; an
explicit third key field (/A, /E, ...) still forces any alignment. Adds a
regression test with a non-'A' keyword (MONARCHY) that the AUTOMOBILE vector
cannot cover.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ty test

Two general primitives distilled from a cryptanalysis session.

ciphers/progressive_key: ProgressiveKey.crack now recovers with NO keyword hint.
The progression is recovered first — undoing the per-group progression re-aligns
every group onto one keyword cipher, snapping the de-drifted columns from flat back
to peaked, rankable by column IoC alone (no dictionary word). The top few
(period, base, progression) triples are then fully solved (keyword by per-column
English fit) and scored. Previously crack returned [] blind; it now returns keyless
candidates (meta.keyless=True). Reliable at adequate length (~240+ letters for
period 7); returns an honest junk-scored best-of below that. Tunable via
max_period / prefilter.

analysis.heldout_stationarity: a held-out transfer test for whether a period-p
additive key is stationary. Fit the per-coset shifts on the first half, measure how
well they pool the held-out remainder, z-scored against a within-coset-shuffle null
that preserves each coset's multiset (hence any flattener's marginal). This is the
flattener-matched control a naive comparison lacks — without it a strong flattener
alone reads as spurious non-stationarity. Power scales with length, so at short n
the test is honestly inconclusive rather than false-positive. Complements ioc_decay.

Tests, CHANGELOG updated; ruff + mypy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJjjwhsWqwb5GRvD4QiA5i
…gnment

c3d0f79 changed QuagmireIII's default alignment from straight-'A' to the keyed
alphabet's first letter (the canonical Quagmire-III convention) but left the
hand-tuned INDICATOR in test_quagmire_longkey keyed to the OLD 'A'-aligned
ciphertext. Under the new (correct) alignment the resulting ciphertext differs, so
the deterministic cold 1-opt -> 2-opt path traps at a near-miss instead of an exact
recovery, and test_cold_one_opt_traps_but_two_opt_recovers failed in CI. The solver
itself is unaffected (the dictionary-attack round-trip test still passes).

Re-tune the fixture to a new random 40-letter INDICATOR that, under the current
canonical alignment (no explicit third key field), still traps the cold 1-opt pass
and is then recovered exactly by the 2-opt finisher -- preserving the test's intent
without reverting to the non-canonical alignment. Note added so it is re-tuned if
the default alignment ever changes again. Full `-m "not slow"` suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJjjwhsWqwb5GRvD4QiA5i
@0xdiid
0xdiid merged commit cf0a6c2 into main Jul 20, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant