The Verification Venue · pointed at the boundary where a cipher stops having one answer
Twenty-Eight Letters to Certainty
Every demonstration of a cryptogram solver uses a hundred letters or more, where any reasonable solver succeeds and you learn only that the cipher is weak. The interesting fact is where solvability begins, and it begins sharply: information theory says a substitution cipher over 26 letters becomes determined once the ciphertext outlives log₂(26!) / R, the key's entropy over the language's redundancy. This page builds its own language so that R is known exactly, predicts the threshold, then attacks thousands of ciphertexts of every length, live, to find where solving actually starts.
Press Intercept and a fresh key is drawn, a fresh message is generated from the language, and a solver attacks it in front of you, letter by letter. Short texts fail entertainingly and land on confident nonsense. Then press Launch the full experiment: thousands of trials at every length, on every core you have, and the measured threshold emerges against the exact prediction.
Three languages, built from one seed, each with its own exact redundancy
These are languages constructed for this page, not English, and no figure for English's redundancy appears anywhere on it. Each button re-targets every experiment below.
Solver's current reading
press Intercept to begin
no case yet
The case
length -, language -
curves appear as the full experiment runs
Drag below the dotted threshold of the selected language and watch the solver become confidently wrong rather than merely unsure.
Intercept runs one attack on a fresh case. Rival readings collects the distinct keys a short text admits. Launch runs the full grid across all cores; Stop halts it and reports the totals as partial.
The prediction
A key is a permutation of 26 letters, so it carries log₂(26!) bits, computed on this page by summation. The language carries R = log₂(26) − H redundant bits per letter, where H is the entropy rate of its transition matrix, computed from the matrix itself, not estimated from a sample. Their quotient is the unicity distance: the length beyond which only one key remains consistent with the text. The construction makes every transition probability a power of two over 1024, so every log-probability is an integer, the solver scores in exact integer arithmetic, and no floating-point library ever has to agree with another:
The prediction is a statement about existence of a unique consistent key, not about any algorithm's ability to find it. The measured crossing below belongs to this hill climber, and the gap between the two lines is reported as the searcher's inefficiency, not wished away.
The full experiment
Every cell is many independent trials: fresh key, fresh message, fresh attack, deterministic in its trial index. The run panel shows the honest counters: chunks done, inner-loop steps actually executed as returned by the engine, wall time, and how many workers ran and why that number.
| language | entropy rate (bits/letter) | redundancy R | predicted n₀ | measured crossing |
|---|---|---|---|---|
| run the experiment to measure | ||||
The check: every number recomputed in front of you
Before anything is displayed, both implementations (the compiled C engine and a slow, obvious JavaScript transcription written from the same contract) run over a battery of small instances in your browser, and their fingerprints must match on every row. The fingerprints bind the final scores, the success bytes and the full key each trial converged to. The rows must also differ from each other, because a battery that always agrees proves nothing.
| battery row | engine fingerprint | reference fingerprint | agree |
|---|---|---|---|
| running… | |||
The heavier checks run offline, where there is time to be thorough: node research/twenty-eight-letters-to-certainty/verify-twenty-eight-letters-to-certainty.mjs re-runs the full-size grid under two different chunkings and asserts every record byte is identical, re-runs the planted controls, judging the identity plant from the key the engine emits rather than from its own success byte, sweeps all 720 keys of a six-letter analogue language against the exact posterior, and rebuilds the binary when the pinned compiler is available.
About the six-letter analogue sweep: an earlier draft of it asserted that at 1.4 times the predicted unicity length the exact posterior picks the true key at least 80% of the time. That 80% was an invented threshold, and it has been removed. The unicity distance promises that the expected number of spurious keys falls below one (about 0.07 at that length for the analogue language), not that any particular fraction of draws is won, and no closed-form win rate follows from n₀. The sweep now asserts what the theory does support: below n₀ the true key wins at most half the draws, above it more than half, and the measured 50% crossing sits within a factor of two of the prediction, a bracket fixed before any measurement. The measured win rate at 1.4 n₀, 67.1%, is reported as a finding about this language's score geometry, not as a confirmation of an invented constant. A rotated-key control shows the comparator is not vacuous.
What none of this rules out: that the two decompositions agree rules out a boundary mistake and very little else; the battery proves the C and JavaScript transcriptions match, not that the shared algorithm deserves trust; and the measured crossing is a fact about this hill climber, not about cryptanalysis or about real English, which this page never quantifies.
What it cost
What's exactly true here, and what's a model
Exactly true. The transition matrices, their dyadic structure (every row is 26 powers of two summing to 1024, asserted live), the stationary distributions, the entropy rates, the redundancies and the unicity predictions are computed from the stated seed by the page in front of you. Per-trial outcomes are deterministic functions of the trial index, so the two-decomposition check compares them with exact equality. The scoring is exact integer arithmetic.
Idealised. The language is order-1 Markov with a uniform start letter, built by splitting 1024 into 26 powers of two. Real languages have longer memory, punctuation and word boundaries. The solver is variable-neighbourhood hill climbing: it sweeps all 325 transpositions until no swap improves the exact integer score, then scans all 5,200 directed 3-cycles, and alternates the two until neither move helps. A swap alone cannot repair a cyclic misassignment of three letters without crossing a score valley; the 3-cycle move reaches it directly, and language A, which concentrates almost all transition mass on 8 of its 26 columns, produces exactly such cycles. Restarts are banded by length at 64, 40, 48 or 100 (short texts are cheap to descend and richest in wrong basins, so they get the most; the top band is cheap in expectation because a trial that succeeds stops at its first winning descent).
Representative, not universal. The measured crossing sits above the prediction because search is imperfect; its distance is this solver's inefficiency. A better algorithm would narrow it. Nothing here would move the prediction itself, which belongs to the language alone, which is the point of the second result: change the language's redundancy and the threshold slides in proportion to one over R.