fix(phl): emit the non-negativity of the bound as a separate goal in rnd (on #1105) - #1135
Open
Yiping106283 wants to merge 1 commit into
Open
Yiping106283 wants to merge 1 commit into
Yiping106283 wants to merge 1 commit into
Conversation
This was referenced Sep 11, 2026
Yiping106283
force-pushed
the
fix-phoare-rnd-nonneg
branch
from
September 13, 2026 02:56
22f5ad0 to
b7fa0d8
Compare
rndrnd (on #1105)
…`rnd` Summary: the pHL `rnd` tactic accepted `phoare[M.f : true ==> true] <= (-1)%r` for a procedure that diverges before its sampling (upstream EasyCrypt#1119), from which `false` follows with `Pr[mu_ge0]`. Root cause (src/phl/ecPhlRnd.ml, `Core.t_bdhoare_rnd_r`): for an upper bound, the rule lifts the bound check `mu d E <= bd` into the post-condition of a hoare judgment on the statements preceding the sampling. A hoare post-condition only has to hold on terminating runs, so a diverging prefix discharges it vacuously. The check is sound for the mass of the terminating runs, but the non-terminating runs contribute probability 0 to the conclusion, which is bounded by `bd` only if `0%r <= bd`: that premise was missing from the rule. Fix: the two `<=` branches that emit the hoare goal (explicit event, and event inferred from a post-condition depending on the sampled variable) now also emit `forall &hr, 0%r <= bd` as a last goal. Based on EasyCrypt#1105: under its semantics the bound must be non-negative in every memory, so the goal is unconditional (restricting it to memories satisfying `pre` would still let the rule prove a judgment whose bound is negative outside `pre`). The rule always emits it; the `rnd` tactic (`process_rnd`) tries `t_trivial` on it, so trivially non-negative bounds stay effort-free and a genuinely negative bound is left as an unprovable goal. `auto` now only recurses into the program-logic sub-goals produced by `rnd` (src/phl/ecPhlAuto.ml), so it keeps applying `rnd` when the extra goal is present, and its own final `t_trivial` closes the goal when it is trivial. The documented rule in doc/tactics/rnd.rst is updated accordingly. Scripts that discharged the old post-condition with `rnd; skip`, `rnd; auto` or `rnd=> //` now reach the extra goal through `;`: theories/crypto/ {Birthday.eca, PROM.ec, RndExcept.eca, prp_prf/Strong_RP_RF.eca} and examples/{PRG.ec, ChaChaPoly/chacha_poly.ec, cramer-shoup/cramer_shoup.ec, global-hybrid/GlobalHybridExamp1.ec, prg-tutorial/PRGc.ec} close it with `smt` and the relevant non-negativity lemmas (since the goal no longer carries the pre-condition, facts such as `0 <= fsize m` come from the library lemmas instead). In examples/prg-tutorial/PRGc.ec the `fel` bound `(i + 1)%r * pr_dstate` is negative for `i < -1`, so under EasyCrypt#1105 the per-query judgment is false for such counter values; the bound becomes `(max 0 i + 1)%r * pr_dstate` (as EasyCrypt#1105 does in Strong_RP_RF.eca) and the sum is rewritten back with `eq_big_seq`. Test: tests/phoare-rnd-neg-bound.ec (explicit event, inferred event, and a loop-free variant). The remaining goal `forall &hr, 0%r <= -1%r` is introduced with `move=> &hr` (which fails when the goal is not emitted) and asserted unprovable with `fail (by smt())`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Yiping106283
force-pushed
the
fix-phoare-rnd-nonneg
branch
from
September 13, 2026 03:00
b7fa0d8 to
20a2653
Compare
lyonel2017
requested changes
Sep 14, 2026
| [bd] only if [bd] is non-negative. Moreover a pHL judgment is false as | ||
| soon as its bound is negative in some memory (whether or not that | ||
| memory satisfies the pre-condition): emit [0 <= bd] as a separate | ||
| (last) goal, quantified over all memories, unconditionally. *) |
Contributor
There was a problem hiding this comment.
I would remove the above comment. It is at the wrong place and not clear. A comment can be added at line 242 and 268 to explain why bound check are added (a short clear one liner is ok).
| | (PNoRndParams | PSingleRndParam _), FHle -> t_side | ||
| | _ -> t_id in | ||
| FApi.t_last t_side (t_bdhoare_rnd tac_info tc) | ||
|
|
Contributor
There was a problem hiding this comment.
This could be done directly inside the t_bdhoare_rnd_r function, inside the existing pattern matching. Would avoiding doing the two pattern matching here, and, if t_bdhoare_rnd is called multiple time, duplicating this code (cf. the changes in ecPhlAuto.ml and ecPhlHiAuto.ml).
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 #1119. Based on #1105 (
negative-phoare-false, head bb75dba); this replaces the earlier version of this PR, which was based onmain.Summary
The pHL
rndtactic acceptedphoare[M.f : true ==> true] <= (-1)%rfor a procedure that diverges before its sampling, from whichfalsefollows withPr[mu_ge0]. #1105 does not reach this: the bound check stays inside the hoare post-condition and never passes throughconseq,byprorexfalso, so with bb75dba the reproducer is still accepted and still provesfalse. The rule now emits the missing premise as a separate goal quantified over all memories, and therndtactic triest_trivialon it.Under #1105's semantics a pHL judgement is false as soon as its bound is negative in some memory, whether or not that memory satisfies the pre-condition; the side goal is therefore unconditional (
forall &hr, 0%r <= bd) because a goal restricted topre, as in #1135, would still let the rule prove a judgement whose bound is negative outsidepre.Root cause (
src/phl/ecPhlRnd.ml,Core.t_bdhoare_rnd_r)For an upper bound, the rule lifts the bound check
mu d E <= bdinto the post-condition of a hoare judgment on the statementsspreceding the sampling. A hoare post-condition only has to hold on terminating runs, so a diverging prefix discharges it vacuously. The check is sound for the mass of the terminating runs (Pr[s; x <$ d : Q] = sum_m' Pr[s](m') * mu d(m') Q <= Pr[s : true] * bd), but the non-terminating runs contribute probability 0 to the conclusion, which is bounded bybdonly if0%r <= bd: that premise was missing from the rule.Fix
The two
<=branches that emit the hoare goal (explicit event, and event inferred from a post-condition depending on the sampled variable) now also emitforall &hr, 0%r <= bdas a last goal. The rule always emits it; therndtactic (process_rnd) triest_trivialon it, so trivially non-negative bounds stay effort-free and a genuinely negative bound is left as an unprovable goal.autonow only recurses into the program-logic sub-goals produced byrnd(src/phl/ecPhlAuto.ml,t_auto_phl_rnd_r), so it keeps applyingrndwhen the extra goal is present, and its own finalt_trivialcloses the goal when it is trivial. The documented rule indoc/tactics/rnd.rstis updated accordingly (its example proof already does not check onmain:by smt(dbool1E)does not prove the1%r/2%rbound with current provers; only the goal structure is updated here).Impact
rnd; skip,rnd; autoorrnd=> //now reach the extra goal through;and must close it when it is not trivial (a symbolic bound without0%r <= bdin context). Since the goal does not carry the pre-condition, non-negativity facts that used to come from it (0 <= fsize m,0 <= qF, ...) must come from the library lemmas.smtand the relevant non-negativity lemmas on that goal, on top of Change pHL to prevent negative probabilities #1105's own script changes to the same files:theories/crypto/Birthday.eca,theories/crypto/PROM.ec,theories/crypto/RndExcept.eca,theories/crypto/prp_prf/Strong_RP_RF.eca,examples/PRG.ec,examples/ChaChaPoly/chacha_poly.ec,examples/cramer-shoup/cramer_shoup.ec,examples/global-hybrid/GlobalHybridExamp1.ec,examples/prg-tutorial/PRGc.ec. None of them proves the same side condition twice: Change pHL to prevent negative probabilities #1105 adds0%r <= bdatconseq/bypr/exfalso, this PR at therndrule.Strong_RP_RF.eca: afelbound function must be non-negative for every counter value, not only for0 <= c < q. InPRGc.ecthe bound(i + 1)%r * pr_dstateis negative fori < -1, so the per-query judgement is false under the new semantics; the bound becomes(max 0 i + 1)%r * pr_dstateand the sum is rewritten back witheq_big_seq(one line). The same pattern is what the downstream sites will need.>=and=judgements, and<=with a post-condition independent of the sampled variable (which drops the sampling), are unchanged.Downstream
The extra goal reaches the CI external projects at symbolic-bound
rndsites: cryptobox (2), sha3 (16), xmss-security (3); sphincsplus and xsalsa20 are unaffected. Those sites need script changes on top of the ones #1105 already requires (the unconditional goal also drops the pre-condition facts the #1135 versions of those changes used); diffs to follow once the base is settled, as merge requests on each project.Test
tests/phoare-rnd-neg-bound.ec(explicit event, inferred event, and a loop-free varianty <$ dnull; x <$ dunit 0): the remaining goalforall &hr, 0%r <= -1%ris introduced withmove=> &hr(which fails with "all goals are closed" on the Change pHL to prevent negative probabilities #1105 head, where the lemma goes through) and asserted unprovable withfail (by smt()).make unit96/96,make stdlib128/128,make examples49/49 (ECJOBS=3, local prover set). Reproducers: pHL seq permits negative probabilities #1100 and pHL call permits negative probabilities #1101 are rejected by Change pHL to prevent negative probabilities #1105; both pHLrnd Esoundness #1119 variants (diverging prefix, loop-free) by this commit; pHL while permits negative probabilities #1102 is out of scope here (separate PR).Note on CI: the external-project checks fail here for the same reason as on #1105 itself — CI looks for a
merge-<this branch>branch on each downstream repository and otherwise checks out their default branch, which does not have the #1105 script changes yet.