Skip to content

Commit f098bd7

Browse files
Yiping Maclaude
authored andcommitted
fix(phl): require the invariant on loop entry in the while variant rule
Summary: `while I v` accepted `phoare[M.p : true ==> true] = 0%r` for `while (b) {}` with the invariant `false` (upstream #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>
1 parent 311ba5d commit f098bd7

3 files changed

Lines changed: 101 additions & 23 deletions

File tree

‎examples/MEE-CBC/FunctionalSpec.ec‎

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -469,39 +469,59 @@ proof.
469469
rewrite dmap1E /pred1 /=.
470470
proc; inline MAC.tag PRPc.PseudoRP.f.
471471
swap 6 -5 => //=; alias 2 iv = s.
472+
seq 2 : (iv :: mee_enc AES hmac_sha256 _ek _mk iv _p = _c)
473+
(mu dblock ((fun (x0 : block list) => x0 = _c) \o fun (iv0 : block) => iv0 :: mee_enc AES hmac_sha256 _ek _mk iv0 _p))
474+
1%r 1%r 0%r
475+
(key = (_ek, _mk) /\ p = _p /\ iv = s)=> //.
476+
+ by auto.
477+
+ by wp; rnd.
478+
+ while ( 0 <= i <= size (pad _p (hmac_sha256 _mk _p))
479+
/\ ek = _ek
480+
/\ p' = pad _p (hmac_sha256 _mk _p)
481+
/\ s = nth witness c i
482+
/\ size c = 1 + i
483+
/\ c = iv :: cbc_enc AES _ek iv (take i (pad _p (hmac_sha256 _mk _p))))
484+
(size (pad _p (hmac_sha256 _mk _p)) - i).
485+
auto=> /> &hr le0_i _ /addzI szcbc_eq_i lti_szpadded.
486+
split; last by smt ().
487+
split; first by smt().
488+
split; last first.
489+
split; first by rewrite size_cat /= szcbc_eq_i.
490+
rewrite (take_nth witness) //= -cbc_enc_rcons -cats1 /=.
491+
by rewrite size_take // lti_szpadded.
492+
have -> /=: i{hr} + 1 <> 0 by smt ().
493+
by rewrite cats1 nth_rcons size_cbc_enc size_take // lti_szpadded /=.
494+
wp=> //=.
495+
conseq (_: _ ==> s :: mee_enc AES hmac_sha256 _ek _mk s _p = _c)=> //=.
496+
move=> &m [#] ->> ->> ->> <<- //=.
497+
split=> [|c n s0]; 1:by split; [rewrite size_ge0|rewrite take0].
498+
split=> [[[le0_n le_n_size] [s0_is_nth [size_c]]] c_is_enc|].
499+
by rewrite StdOrder.IntOrder.ler_subl_addr add0z=> /StdOrder.IntOrder.ler_gtF.
500+
rewrite -lezNgt=> le_size_n [[le0_n le_n_size]] [_] [_] ->.
501+
have [_ ->] //:= eqz_leq n (size (pad _p (hmac_sha256 _mk _p))).
502+
by rewrite take_size.
503+
(* the sampled IV is wrong: the loop cannot produce _c *)
504+
hoare.
472505
while ( 0 <= i <= size (pad _p (hmac_sha256 _mk _p))
473506
/\ ek = _ek
474507
/\ p' = pad _p (hmac_sha256 _mk _p)
475508
/\ s = nth witness c i
476509
/\ size c = 1 + i
477-
/\ c = iv :: cbc_enc AES _ek iv (take i (pad _p (hmac_sha256 _mk _p))))
478-
(size (pad _p (hmac_sha256 _mk _p)) - i).
479-
auto=> /> &hr le0_i _ /addzI szcbc_eq_i lti_szpadded.
480-
split; last by smt ().
510+
/\ c = iv :: cbc_enc AES _ek iv (take i (pad _p (hmac_sha256 _mk _p)))
511+
/\ iv :: mee_enc AES hmac_sha256 _ek _mk iv _p <> _c).
512+
auto=> /> &hr le0_i _ /addzI szcbc_eq_i c_neq lti_szpadded.
481513
split; first by smt().
482514
split; last first.
483515
split; first by rewrite size_cat /= szcbc_eq_i.
484516
rewrite (take_nth witness) //= -cbc_enc_rcons -cats1 /=.
485517
by rewrite size_take // lti_szpadded.
486518
have -> /=: i{hr} + 1 <> 0 by smt ().
487519
by rewrite cats1 nth_rcons size_cbc_enc size_take // lti_szpadded /=.
488-
wp=> //=.
489-
conseq (_: _ ==> s :: mee_enc AES hmac_sha256 _ek _mk s _p = _c)=> //=.
490-
move=> &m [->>] ->> iv //=; split=> [[[le0_size _] h]|<<-].
491-
have -> //=:= h (iv :: mee_enc AES hmac_sha256 _ek _mk iv _p)
492-
(size (pad _p (hmac_sha256 _mk _p)))
493-
(nth witness (iv :: mee_enc AES hmac_sha256 _ek _mk iv _p)
494-
(size (pad _p (hmac_sha256 _mk _p)))).
495-
split=> //=.
496-
split; 1:by rewrite /mee_enc /= size_cbc_enc addzC.
497-
by rewrite take_size.
498-
split=> [|c n s0]; 1:by split; [rewrite size_ge0|rewrite take0].
499-
split=> [[[le0_n le_n_size] [s0_is_nth [size_c]]] c_is_enc|].
500-
by rewrite StdOrder.IntOrder.ler_subl_addr add0z=> /StdOrder.IntOrder.ler_gtF.
501-
rewrite -lezNgt=> le_size_n [[le0_n le_n_size]] [_] [_] ->.
502-
have [_ ->] //:= eqz_leq n (size (pad _p (hmac_sha256 _mk _p))).
503-
by rewrite take_size.
504-
by rnd.
520+
wp; skip=> &m [#] ->> ->> ->> c_neq /=.
521+
split=> [|c n s0]; 1:by rewrite size_ge0 take0 /= c_neq.
522+
rewrite -lezNgt=> le_size_n [#] le0_n le_n_size _ _ ->.
523+
have [_ ->] //:= eqz_leq n (size (pad _p (hmac_sha256 _mk _p))).
524+
by rewrite take_size /mee_enc.
505525
qed.
506526

507527
phoare mee_decrypt_correct _mk _ek _c:

‎src/phl/ecPhlWhile.ml‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,23 @@ let t_bdhoare_while_r inv vrnt tc =
149149
let post = generalize_mod_ss_inv env modi post in
150150
let post = map_ss_inv2 f_and_simpl inv post in
151151
let concl = f_bdHoareS mt (bhs_pr bhs) s post bhs.bhs_cmp (bhs_bd bhs) in
152+
(* From a memory satisfying [post] (invariant + generalized termination
153+
and exit conditions), the loop terminates with probability 1 in a
154+
memory satisfying the post-condition, so [Pr[s; while] >= Pr[s : post]]
155+
always holds. For [<=] and [=], the runs of [s] that reach the loop
156+
OUTSIDE [post] contribute an unknown probability (the loop may then
157+
diverge, or terminate anywhere), so we additionally require that no
158+
terminating run of [s] does so -- except when the bound is [1%r], as
159+
[Pr[s; while] <= 1%r] holds unconditionally. *)
160+
let inv_concl =
161+
match bhs.bhs_cmp with
162+
| FHge -> []
163+
| FHle | FHeq ->
164+
if f_equal (bhs_bd bhs).inv f_r1
165+
then []
166+
else [f_hoareS mt (bhs_pr bhs) s (POE.lift post)] in
152167

153-
FApi.xmutate1 tc `While [b_concl; concl]
168+
FApi.xmutate1 tc `While ([b_concl; concl] @ inv_concl)
154169

155170
(* -------------------------------------------------------------------- *)
156171
(* Rule for <= *)
@@ -569,7 +584,12 @@ let process_while side winfos tc =
569584
| Some vrnt, None ->
570585
let _, phi = TTC.tc1_process_Xhl_formula tc phi in
571586
let _, vrnt = TTC.tc1_process_Xhl_form tc tint vrnt in
572-
t_bdhoare_while phi vrnt tc
587+
(* [t_bdhoare_while] emits, for [<=] and [=], the extra hoare goal
588+
stating that the prefix establishes the invariant; try to close
589+
it automatically so trivial cases stay effort-free. *)
590+
FApi.t_onalli
591+
(function 2 -> FApi.t_try EcPhlAuto.t_pl_trivial | _ -> t_id)
592+
(t_bdhoare_while phi vrnt tc)
573593

574594
| Some vrnt, Some (`Bd (k, eps)) ->
575595
let _, phi = TTC.tc1_process_Xhl_formula tc phi in

‎tests/phoare-while-vrnt-inv.ec‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
(* pHL `while I v` (strict variant), upstream #1103: for `<=` and `=`, the
2+
rule must require the invariant (with the termination and exit
3+
conditions) to hold on every terminating run of the statements preceding
4+
the loop, `hoare[s : P ==> I /\ ...]`. With `I = false` that goal cannot
5+
be proved; `>=` needs no such goal; a bound `1%r` needs none either. *)
6+
require import AllCore Real.
7+
8+
op b : bool.
9+
10+
module M = { proc p() = { while (b) {} } }.
11+
12+
lemma bad : phoare[M.p : true ==> true] = 0%r.
13+
proof.
14+
proc.
15+
fail (by while false 0).
16+
abort.
17+
18+
lemma bad' : phoare[M.p : true ==> true] <= 0%r.
19+
proof.
20+
proc.
21+
fail (by while false 0; auto).
22+
abort.
23+
24+
lemma ok : phoare[M.p : true ==> true] >= 0%r.
25+
proof.
26+
proc.
27+
by while false 0.
28+
qed.
29+
30+
module N = { proc q() = { var i : int; i <- 0; while (i < 3) { i <- i + 1; } } }.
31+
32+
lemma ok' : phoare[N.q : true ==> true] = 1%r.
33+
proof.
34+
proc.
35+
while (true) (3 - i).
36+
+ by auto=> /#.
37+
by auto=> /#.
38+
qed.

0 commit comments

Comments
 (0)