We told you the verifier had been asking the right question all along. It was — and then it threw the answer away.
In the last chapter the story had a clean shape. The issuer — the code that mints a live-crossing token — had been reaching for a held key that no longer meant anything, and we moved it back onto structural ground: identity composed against state, no stored secret required. And we said something reassuring about the other half of the system. The verifier — the code that decides, at the moment of use, whether a token still resolves — had been doing it right the whole time. Only the issuer had been confused.
That was true. It was not the whole truth.
The verifier did re-derive both channels. Channel A, identity — who, which assembly, which token. Channel B, state — the live control-variable, snapshot, and master-table hashes. It computed them honestly, from what was true at that instant. And then it asked a question that could only ever have one answer.
It asked whether a signature had come out.
A signature always comes out. The routine that combines the two channels reports signed whenever both channels are present, and both channels are always present — they are pure derivations of whatever inputs you hand them. Feed it an operator, it signs. Feed it an imposter, it signs. Feed it a principal named system that isn't anyone at all, it signs. So the check passed. Every time. For everyone.
The one thing it never did was compare the answer it had just computed to the answer the token was carrying.
That is a strange kind of failure, because from the outside it looks like diligence. There is code that reads the live state. There is code that derives two independent authorities. There is a function named for exactly the thing you want it to guarantee. It runs on every crossing. It just doesn't finish the sentence. It re-derives the truth and then declines to check the truth against the claim.
The imposter who was always let in
Here is the test that makes it concrete. Someone lifts a valid token — copies it off the wire, out of a log, however — and presents it as a different caller. This is the exact attack the whole design exists to stop.
Before this week, the verifier re-derived Channel A from the imposter's identity, produced a perfectly valid signature — of course it did — saw that a signature existed, and opened the gate. Identity, the entire content of Channel A, never entered the decision. The only thing standing between a copied token and the protected state was the state half: the snapshot hashes and Channel B. If the world hadn't moved much since the token was born, the copy resolved.
The token knew who it belonged to. The verifier had that fact in its hand, freshly recomputed. And it looked past it.
The fix is smaller than the bug
We did not add a new mechanism. The comparison already existed — a function that re-derives both channels and checks each against what the token stored, returning not a yes/no but a list of exactly what drifted: identity, state, or the combined seal. It had been written. It had never been wired into the gate. The gate was calling its decorative cousin.
So the change was to call the real one, and to open the gate on its verdict instead of on the mere existence of a signature. Identity drift now refuses. State drift now refuses. And because a token minted before the change carries no stored signatures to compare against, those older tokens fall through to the previous behavior rather than being killed on sight — the new rule binds the new crossings and leaves the in-flight ones alone.
Then we ran the imposter again. Copied token, different caller. The verifier re-derived the identity channel, compared it to the one the token was born with, and they did not match. The result was not a shrug and an open gate. It was INACCESSIBLE, with the reason named out loud: the identity no longer corresponds to the token. The state channel behaves the same way now — move a governed value the token was minted against, and the crossing goes inaccessible with that reason named instead. The check finally checks.
The honest part: turning it on broke something first
The moment we made the comparison real, a legitimate token failed. Not an attacker — a clean, current, unchanged crossing, refused for state drift that hadn't happened.
The cause is the kind of thing that only surfaces when two halves of a system are finally forced to agree. The issuer, minting the state channel, tagged it with the state's version as it computed the version — a small string, v1. The verifier, re-deriving the same channel, reconstructed that tag from a different fallback and produced the number 1. Nothing had drifted. The two sides were describing the same state with two different words for its edition, and the comparison — now that a comparison actually happened — saw two different words and called it drift.
This is worth telling because it is the whole thesis of the series in miniature. For as long as the check wasn't checking, this disagreement was invisible; both sides were wrong in private and it never mattered. The instant the check became real, the disagreement became a failure. The bug didn't appear when we wrote it. It appeared when we started looking. We taught the verifier to take that version tag from the token itself — the edition is a label fixed at birth, while genuine drift in the state is still caught by the live hashes — and the legitimate crossing resolved.
Why a check that never compares is the dangerous kind
A missing check announces itself. Sooner or later something gets through that shouldn't, and you go find the gap.
A check that runs but never compares announces the opposite. It reports success. It shows up green in every trace. It has a confident name. It gives you a reason to stop looking precisely where you most need to keep looking — because the guarantee you think you have is being computed, faithfully, and then discarded one line before it would have meant anything.
Authentication isn't one question, which is where this series began. But there is a companion to that lesson, and this is it: a question the system asks but never listens to the answer of is worse than a question it never asks. The first is a gap. The second is a gap wearing the uniform of the thing that was supposed to close it.
The gate compares now. The imposter is turned away by name.
Next: the single crossing we keep calling "one question" is actually three readers — and the checklist the operator kept reciting turns out to be the gate itself.
0 comments