fix(phl): emit the non-negativity of the bound as a separate goal in rnd - #1135
Open
Yiping106283 wants to merge 1 commit into
Open
fix(phl): emit the non-negativity of the bound as a separate goal in rnd#1135Yiping106283 wants to merge 1 commit into
rnd#1135Yiping106283 wants to merge 1 commit into
Conversation
…`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, pre => 0%r <= bd` as a last goal. 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. Test: tests/phoare-rnd-neg-bound.ec (explicit and inferred event). The remaining goal `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>
This was referenced Sep 11, 2026
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.
Summary
The pHL
rndtactic acceptedphoare[M.f : true ==> true] <= (-1)%rfor a procedure that diverges before its sampling, from whichfalsefollows withPr[mu_ge0]. Following #1095, the rule now emits the missing premise as a separate goal quantified over all memories, and therndtactic triest_trivialon it. The semantics of phoare judgements are unchanged.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, pre => 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).smtand the relevant non-negativity lemmas on that goal: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.>=and=judgements, and<=with a post-condition independent of the sampled variable (which drops the sampling), are unchanged.Relation to #1105
This is needed independently of #1105: with that PR's head (bb75dba) the reproducer of #1119 is still accepted and still proves
false, because the bound check stays inside the hoare post-condition and never reaches theconseq/bypr/exfalsosites #1105 guards (the same holds for #1102, fixed in #1130). Details and the reproducer are in the comment on #1105.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. One-line script changes that make each project's CI scenario pass with this patch (modulo the SMT failures that already fail on unpatchedmain) are at https://github.com/Yiping106283/easycrypt/tree/pr1105-downstream-fixes/downstream_fixes_rnd_pr, and I can open the corresponding merge requests once this is accepted.Test
tests/phoare-rnd-neg-bound.ec(explicit and inferred event): the remaining goal0%r <= -1%ris introduced withmove=> &hr _(which fails when the goal is not emitted) and asserted unprovable withfail (by smt()).make unitandmake stdlibpass; the adapted example files compile with the fixed binary (examples/ChaChaPoly/chacha_poly.ecwith Alt-Ergo enabled alongside the pinned provers, see CI).