Repository navigation
fix: verify AP keystream guesses instead of picking one - #27
Merged
Merged
Conversation
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
opensqlanywhere-docs | 2f07e47 | Oct 25 2026, 08:19 AM |
bv and per-sector steps are chosen deterministically from the page and accepted only with confirming evidence: a trailer row-count match, two or more agreeing exact AP sectors, or a calibrated page in the same block. Otherwise the decode errors, or under Leniency::KEYSTREAM is flagged with warnings. learn reports NoCalibration and NotObfuscated, counts lowercase pure pages, and the model is tied to its store. Signed-off-by: Nathan Riley <git@nathanriley.com>
…and in for confirmation A trailer row-count match needs a count of four or more and yields to exact sectors or a block anchor that point at another bv (AmbiguousCalibration). A block anchor alone is Unverified, and exact sectors that disagree are never resolved by it. deobfuscate_with_bv refuses an unsupported key in strict mode. An all-zero page is returned as unallocated instead of being guessed. BvSource variants are renamed so Verified means only the trailer check. Signed-off-by: Nathan Riley <git@nathanriley.com>
Signed-off-by: Nathan Riley <git@nathanriley.com>
Nathan-D-R
force-pushed
the
fix/ap-deobfuscation
branch
from
October 25, 2026 07:48
24d21e8 to
8944b1a
Compare
This was referenced Oct 25, 2026
Confidence::Unallocated replaces Verified for a page that was never written, and DeobfuscatedPage::bv() returns Option<u8> so an unkeyed page does not report bv 0. Signed-off-by: Nathan Riley <git@nathanriley.com>
…s Corroborated The three-way agreement matched the trailer answer on 2,186 of 2,186 checkable corpus pages and recovers pages (including 211 I pages) that nothing else confirms. It is its own tier below Calibrated, with its own BvSource, and strict mode accepts it. Signed-off-by: Nathan Riley <git@nathanriley.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Stacked on #26. Fixes the AP deobfuscation findings (silent-failure audit S1 1, 2, 3, 4, 5 and S3 17), and the review findings B1, B3, S1, S2, S3 and nits N1, N2, N5.
The decoder no longer picks a
bvor step and hopes. Both are chosen deterministically (ties go to the lowest value, no hash-ordered container decides anything) and accepted only when something independent confirms them:Confidence::Verified): the row count in a slotted page's prelude must equal the plaintext trailer's count byte at0xFF0. The trailer is not obfuscated, so this is the one check that confirmsbvon pages whose padding is not zero. It needs a count of four or more (the pair(1, 0)/(2, 0)is among the commonest words in page data: a match was wrong on 5 of 887 pages with count 1). When two or more exact sectors, or a block anchor on the histogram top, point at anotherbv, the page isAmbiguousCalibration, not Verified.Confidence::Calibrated): two or more sectors are exact AP progressions and agree onbv, with no exact sector disagreeing. This assumes the constant plaintext is zero, which is why it is notVerified. Disagreeing exact sectors areAmbiguousCalibration; nothing, including a block anchor, breaks the tie in strict mode, and lenient mode takes the majority, never the minority.A calibrated page elsewhere in the 16-page block with the same
bvis reported (Evidence::BlockAnchor) but is not confirmation: about a third of blocks (32 % of 28,225, in 93 of 112 files) hold more than onebv. A page that only has a block anchor isUnverified.Confidence::Corroborated, its own tier belowCalibrated): exactly one exact sector, the histogram top and a block anchor all give the samebv, and no other candidate has any support. The re-review measured it against the trailer answer on 2,186 of 2,186 checkable pages (0 wrong; 730 of 730 with count 10 or more); my guard run found 0 disagreements in 724 pages at this PR's tip and 0 in 2,073 at the stack tip. It admits 5,972 pages nothing else confirms, including the 211Ipages OpenQBW's sysindex reads. The checkable pages exclude the dense ones, so the tier is separate and the code comment andSPECIFICATION.mdcite the measurement as a bound for pages like the checked ones.Anything else is
Unverified:KeystreamUnverifiedunlessLeniency::KEYSTREAMis set, in which case the result carries a warning.deobfuscate_with_bvapplies the same rule to a supplied key. Each sector's step needs a peak of 24 and a lead of 12 over the runner-up (random sectors reach 9 to 13 on this corpus), elseLowConfidenceKeystreamor a per-sector warning. An all-zero page was never written:deobfuscatereturns it as it is withConfidence::Unallocatedandbv() == None(DeobfuscatedPage::unallocated()), not through the guesser.learnreturnsNoCalibration/NotObfuscated, counts lowercase pure pages, and the model is tied to its store (StoreMismatch, keyed by page count and the CRCs of the first and last page).CRC does not help here: it covers the stored (obfuscated) bytes, so it checks page integrity, not a keystream guess.
Review findings
AmbiguousCalibration. Regression tests build the reviewer's cases:a_count_of_one_does_not_verify_a_key_that_is_off_by_one(a0x01-filler page with a01 00prelude at the wrong key, count 1; it fails with the old threshold) anda_trailer_match_that_exact_sectors_contradict_is_ambiguous(count 5, three exact sectors on the right key).ApModel::deobfuscate_with_bvstrict mode errors on an unsupported key (supplied_bv_reports_its_support).a_block_anchor_alone_is_not_confirmation).dissenting_exact_sectors_are_never_resolved_by_an_anchor(five sectors vote 86, one votes 88, the block has 88).a_zero_page_is_unallocated_not_guessed.BvSourceisTrailerRowCount,ExactSectors,Histogram,Supplied,Unallocated, so "Verified" has one meaning. N2: no made-upbvon the impossible branch. N5: the fingerprint includes the last page.Corpus delta (112 QBW files, 456,521 pages, strict)
learnsucceeds on all 112 files (4,510 of 4,649 pure pages calibrate). Strictdeobfuscateof every page, now against my earlier numbers:The M, H, C, @, G row in the first version of this description (4,326 / 14 / 0 / 30) was wrong; this is the measured row, and its cells sum to the 4,745 pages of those types.
The shift from the first version is the review's findings applied: block-anchor-only pages (36,481
E, 11,397Abefore) areUnverifiedunless the single-exact-sector tier applies, count-1/2 trailer matches no longer verify, and the ambiguous group fell because the anchor no longer creates conflicts. The sums of decodes plus errors are the page counts of the types.Reading the groups:
Leniency::KEYSTREAMreturns these pages and lists every affected sector (sectors(),warnings()).Not resolved: a global model of the per-sector steps. Within a block they follow a sliding window of 12 values, repeated in pairs and shifted one sector per page; leave-one-out on strong sectors predicts correctly when unanimous (99.5 %), but the window shifts phase in a third of blocks, so it is not used. Recorded in
SPECIFICATION.mdsection 6.API breaks
On top of #26:
BvSourcevariants changed;learncan fail;deobfuscatecan fail with the new errors;deobfuscate_with_bvcan now fail in strict mode.DeobfuscatedPage::bv()returnsOption<u8>(Nonefor an unallocated page, which hasConfidence::Unallocated; own commit). New public items:Confidence(Unverified,Corroborated,Calibrated,Verified,Unallocated),Evidence,SectorFit::{exact, confident},DeobfuscatedPage::unallocated(),Page::is_unallocated().Tests changed
deobfuscate_uses_per_page_bv_not_block_bvassertsConfidence::Calibrated.random_plaintext_is_low_confidence_not_garbagenow seesKeystreamUnverifiedfirst in strict mode (nothing supports the key); a new test covers the dense-sector error with a supported key.trailer_row_count_rescues_a_space_padded_pagehas noise in the space sectors so no exact sector contradicts the trailer; the all-space version isspace_padded_page_with_a_zero_region_is_ambiguous. The old private-helper tests went with the helpers they covered.Docs-site edits needed (release PR)
docs/docs/ap-deobfuscation.md: replace "always succeeds" and "stays correct either way" with the evidence ladder,Confidence,Leniency::KEYSTREAM.docs/docs/specification.md: section 6 additions.OpenQBW follow-up
Treat
LowConfidenceKeystream/KeystreamUnverified/AmbiguousCalibrationper page: skip the page, or opt in withReadOptions::lenient()and drop rows that fall in a sector whoseconfident()is false. Itsbv_recoverymodule (recover_bv_qb_data,recover_bv_any) guesses a key the same way; route it throughApModel::deobfuscate_with_bvand readconfidence(). Its C.36 anchor is the same idea asEvidence::TrailerRowCount, and the same small-count caution applies.Checklist
git commit -s