fix(phl): require the invariant on loop entry in the while variant rule - #1132
Open
Yiping106283 wants to merge 1 commit into
Open
fix(phl): require the invariant on loop entry in the while variant rule#1132Yiping106283 wants to merge 1 commit into
while variant rule#1132Yiping106283 wants to merge 1 commit into
Conversation
…rule
Summary: `while I v` accepted `phoare[M.p : true ==> true] = 0%r` for
`while (b) {}` with the invariant `false` (upstream EasyCrypt#1103): the bound was
derived only from the probability of reaching the loop with the invariant
satisfied.
Root cause (src/phl/ecPhlWhile.ml, `t_bdhoare_while_r`): from a memory
satisfying `I /\ G` (the invariant together with the generalized termination
and exit conditions), the loop terminates with probability 1 in a memory
satisfying the post-condition, so `Pr[s; while] >= Pr[s : I /\ G]` always
holds and the `>=` rule is sound. For `<=` and `=`, the runs of `s` that
reach the loop outside `I /\ G` contribute an unknown probability (the loop
may diverge or terminate anywhere), and the rule ignored them.
Fix: for `<=` and `=`, the rule emits the extra goal
`hoare[s : pre ==> I /\ G]` stating that no terminating run of `s` reaches
the loop outside the invariant, except when the bound is syntactically
`1%r` (then `Pr[..] <= 1%r` holds unconditionally, and `= 1%r` follows from
the `>=` direction), so that losslessness proofs keep their two goals. The
`while` tactic (`process_while`) tries `EcPhlAuto.t_pl_trivial` on the extra
goal so trivial cases stay effort-free. examples/MEE-CBC/FunctionalSpec.ec
relied on the unsound step (its invariant holds only when the sampled IV is
the right one): it is restructured with `seq` at the sampling, the `= 1%r`
branch going through the loop rule and the `= 0%r` branch through `hoare`.
Test: tests/phoare-while-vrnt-inv.ec (`fail (by while false 0)` for `=`,
`fail (by while false 0; auto)` for `<=`; `>=` and a `= 1%r` losslessness
proof still accepted).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
strub
force-pushed
the
fix-phoare-while-variant-entry
branch
from
September 11, 2026 19:59
f098bd7 to
b616c7a
Compare
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.
Fixes #1103.
Summary
while I v(strict variant) acceptedphoare[M.p : true ==> true] = 0%rforwhile (b) {}with the invariantfalse: the bound was derived only from the probability of reaching the loop with the invariant satisfied. The rule now additionally requires, for<=and=, that no terminating run of the prefix reaches the loop outside the invariant. The semantics of phoare judgements are unchanged.Root cause (
src/phl/ecPhlWhile.ml,t_bdhoare_while_r)From a memory satisfying
I /\ G(the invariant together with the generalized termination and exit conditions), the loop terminates with probability 1 in a memory satisfying the post-condition, soPr[s; while] >= Pr[s : I /\ G]always holds and the>=rule is sound. For<=and=, the runs ofsthat reach the loop outsideI /\ Gcontribute an unknown probability (the loop may diverge or terminate anywhere), and the rule ignored them.Fix
For
<=and=, the rule emits the extra goalhoare[s : pre ==> I /\ G]stating that no terminating run ofsreaches the loop outside the invariant, except when the bound is syntactically1%r(thenPr[..] <= 1%rholds unconditionally, and= 1%rfollows from the>=direction), so that losslessness proofs keep their two goals. Thewhiletactic (process_while) triesEcPhlAuto.t_pl_trivialon the extra goal so trivial cases stay effort-free; the rule itself emits it unconditionally (#1095).Two design points for review:
1%rexception departs from "always emit, let the caller tryt_trivial". It is redundant there (see above) and it keeps every in-tree losslessness proof throughwhile I vat exactly two goals. The uniform alternative is to always emithoare[s : pre ==> I /\ G](witht_pl_trivialtried on it), at the cost of one extra goal in every losslessness proof whose prefix does not trivially establish the invariant. Happy to switch if you prefer the uniform rule.<=and=with a bound other than1%r, the invariant must now hold on every terminating run of the prefix. A proof that bounds a probability decided before the loop (a sampled value, as in MEE-CBC below) can no longer go throughwhile I vdirectly and needs aseqat the sampling. That is inherent to a hoare-shaped premise (the fully general premise isPr[s : !(I /\ G)] <= bd'withbd + bd', which is whatseqprovides).Impact
while I von a<=or=judgement with a bound other than1%ryields a third goal,hoare[s : pre ==> I /\ G], closed automatically when trivial.>=judgements and= 1%r/<= 1%rlosslessness proofs are unchanged.examples/MEE-CBC/FunctionalSpec.ec(mee_encrypt_correct) relied on the unsound step (its invariant holds only when the sampled IV is the right one; the old script fails atqedon the fixed binary). It is restructured withseqat the sampling: the= 1%rbranch goes through the loop rule, the= 0%rbranch throughhoareand the hoarewhilerule with the negated event added to the invariant. No library file is affected.Test
tests/phoare-while-vrnt-inv.ec:fail (by while false 0)for=,fail (by while false 0; auto)for<=; the>=direction and a= 1%rlosslessness proof are still accepted.make unitandmake stdlibpass;examples/MEE-CBC/FunctionalSpec.eccompiles with the fixed binary.