Summary
smt raises an internal assertion, rather than failing or erroring, on the
subgoal fel produces when the target Pr[...] event mentions res:
[critical] anomaly: File "src/ecSmt.ml", line 909, characters 5-11: Assertion failed
Reproduced on main at 87b6f8b (2026-07-25), built from a clean clone with
opam install ./easycrypt.opam --deps-only && dune build. Plain module type
throughout — no quantum extensions involved.
Reproducer
require import AllCore Distr List FelTactic.
require import StdBigop StdOrder.
(*---*) import Bigreal.BRA StdOrder.RealOrder.
type T.
op [lossless] d : T distr.
op q : int.
axiom q_ge0 : 0 <= q.
module type O = { proc f(x : T) : bool }.
module type A (P : O) = { proc go() : unit { P.f } }.
module G (Adv : A) = {
var bad : bool
var c : int
module P : O = {
proc f(x : T) : bool = {
var r;
if (c < q) {
c <- c + 1;
r <$ d;
if (r = witness) { bad <- true; }
}
return bad;
}
}
proc main() : bool = {
bad <- false;
c <- 0;
Adv(P).go();
return bad;
}
}.
section.
declare module Adv <: A { -G }.
lemma L &m : Pr[G(Adv).main() @ &m : res] <= q%r * mu1 d witness.
proof.
fel 2 G.c (fun _ => mu1 d witness) q G.bad [G(Adv).P.f : (G.c < q)] (G.c <= q).
admit.
smt(). (* <-- anomaly *)
admit.
admit.
admit.
admit.
qed.
end section.
What isolates it
| variation |
result |
| as above |
anomaly |
Pr[... : res] → Pr[... : G.bad], one token, nothing else changed |
accepted |
smt() → admit. |
accepted |
So the six goals fel produces are well-formed — the failure is in the SMT
translation of one of them — and the trigger is specifically res, not fel
and not the surrounding development. (fel's first goal is the arithmetic sum
bound and smt discharges it without complaint.)
It also reproduces identically on the older deploy-quantum-upgrade branch
(3cef12a), where the same assertion is at ecSmt.ml:900.
Diagnosis
src/phl/ecPhlFel.ml builds the second subgoal as
forall &m0, ev{m0} => inv{m0} => bad{m0} /\ cntr{m0} <= q
and quantifies the memory using the memtype of the ambient memory of the
Pr:
let pr_m = oget (Memory.byid pr.pr_mem env) in
...
let m = (EcIdent.create "&hr", snd pr_m) in
That memtype has no locals. When ev mentions res — a local — the
translation reaches trans_mem's assert has_locals (src/ecSmt.ml:909) and
aborts.
Substituting the Pr memtype, which declares res and which trans_pr
already uses for exactly this purpose, removes the anomaly:
- let m = (EcIdent.create "&hr", snd pr_m) in
+ let m = (EcIdent.create "&hr",
+ snd (EcEnv.Fun.prF_memenv (EcIdent.create "&hr") f env)) in
Built against 87b6f8b, the reproducer then fails with an ordinary
cannot prove goal (strict) instead of the assertion.
What the right fix might be instead
I am not proposing that diff as a fix — it is offered as evidence for the
diagnosis. Even with it, the goal is not provable when the event is res: in
the quantified memory res is an unconstrained local, so nothing links it to
bad. fel appears to want a global bad event, which is what every use
in examples/ does (examples/Upto.ec, examples/ChaChaPoly,
examples/prg-tutorial). If that is the intended contract, rejecting the
input up front with a message to that effect would be more useful than either
the assertion or an unprovable subgoal — the assertion is what cost the time
here, because an internal error reads as "the tactic is broken" rather than
"the goal is stated wrongly".
Workaround
State the bad event over module globals. The same bound then goes through with
all six goals discharged.
Summary
smtraises an internal assertion, rather than failing or erroring, on thesubgoal
felproduces when the targetPr[...]event mentionsres:Reproduced on
mainat 87b6f8b (2026-07-25), built from a clean clone withopam install ./easycrypt.opam --deps-only && dune build. Plainmodule typethroughout — no quantum extensions involved.
Reproducer
What isolates it
Pr[... : res]→Pr[... : G.bad], one token, nothing else changedsmt()→admit.So the six goals
felproduces are well-formed — the failure is in the SMTtranslation of one of them — and the trigger is specifically
res, notfeland not the surrounding development. (
fel's first goal is the arithmetic sumbound and
smtdischarges it without complaint.)It also reproduces identically on the older
deploy-quantum-upgradebranch(3cef12a), where the same assertion is at
ecSmt.ml:900.Diagnosis
src/phl/ecPhlFel.mlbuilds the second subgoal asand quantifies the memory using the memtype of the ambient memory of the
Pr:That memtype has no locals. When
evmentionsres— a local — thetranslation reaches
trans_mem'sassert has_locals(src/ecSmt.ml:909) andaborts.
Substituting the
Prmemtype, which declaresresand whichtrans_pralready uses for exactly this purpose, removes the anomaly:
Built against 87b6f8b, the reproducer then fails with an ordinary
cannot prove goal (strict)instead of the assertion.What the right fix might be instead
I am not proposing that diff as a fix — it is offered as evidence for the
diagnosis. Even with it, the goal is not provable when the event is
res: inthe quantified memory
resis an unconstrained local, so nothing links it tobad.felappears to want a global bad event, which is what every usein
examples/does (examples/Upto.ec,examples/ChaChaPoly,examples/prg-tutorial). If that is the intended contract, rejecting theinput up front with a message to that effect would be more useful than either
the assertion or an unprovable subgoal — the assertion is what cost the time
here, because an internal error reads as "the tactic is broken" rather than
"the goal is stated wrongly".
Workaround
State the bad event over module globals. The same bound then goes through with
all six goals discharged.