Tactic: circuit
The circuit tactic discharges or simplifies goals over finite
types by translating them into boolean circuits and reasoning about
the resulting bit-level representation. It applies to three goal
shapes — first-order propositions, Hoare triples, and program
equivalences — and has two modes, circuit and circuit
simplify.
circuit attempts to close the current goal by translating it into
a boolean circuit and checking that the circuit is identically true.
circuit simplify performs the same translation but does not close
the goal: instead, it rewrites the postcondition using bit-level
equalities derived from the circuit, leaving a simpler residual goal
to be discharged by other tactics.
Important
The circuit family of tactics relies entirely on the
bind family of commands to know how EasyCrypt types and
operators correspond to their bit-level counterparts. Every type
appearing in the goal must be bind bitstring- or bind
array-bound; every operator must be bind op- or bind
circuit-bound, or definable in terms of bound operators; and
every program statement must be an assignment whose right-hand
side translates to a circuit.
See Command: bind for the syntax and semantics of the bind
commands and the catalog of supported operator names — the
examples below all begin with bind declarations, whose side
conditions (the realize lines) come from that catalog.
Variant: circuit (FOL)
When the goal is a first-order proposition (i.e., not a Hoare or
equivalence judgement), circuit translates the formula directly
into a boolean circuit and checks that it is a tautology.
The equality w1 +^ w2 = w2 +^ w1 is translated into a boolean
circuit parameterised by the bits of w1 and w2 and equal to
true exactly when the two sides agree. The tactic closes the goal
by checking that this circuit is identically true — in effect, a
case-analysis over all assignments of the input bits, but performed
symbolically on the circuit structure.
The goal must contain no free type variables (the FOL case requires a ground context).
Variant: circuit (HL)
When the goal is a Hoare triple, circuit translates the
precondition, the program, and the postcondition into circuits and
checks that the postcondition circuit is implied by the precondition
on every initial state.
The execution proceeds in three phases:
Precondition processing. The precondition is split along its top-level conjunctions. Each equation of the form
prog_var = expr(in either direction) is treated as a definition: it is added to the symbolic state and used to specialise the construction of all later circuits. Each remaining clause that translates to a boolean circuit is recorded as an antecedent; clauses that do not translate are silently dropped.Program translation. The body is processed instruction by instruction, maintaining a mapping from each program variable to the circuit that computes its current value from the program’s inputs. The inputs are the program variables (treated as universally quantified bit-vectors) together with any logical variables constrained by the precondition.
Postcondition discharge. The postcondition is split along its conjuncts; each conjunct is translated into a boolean circuit using the input-to-output map computed in phase 2. The tactic then checks that, on every input satisfying the precondition antecedents, every postcondition circuit is true.
When the goal is an equality, the postcondition is decomposed bit-by-bit and the tactic looks for structurally identical sub-conditions across the bits, sharing them so that each input bit is examined only once.
Variant: circuit (rHL)
When the goal is a program equivalence (equiv[M.f1 ~ M.f2 : pre
==> post]), the tactic produces a separate input-to-output map for
each program, then checks that the postcondition relating the two
sides holds on every joint initial state satisfying the precondition.
Variant: circuit simplify
circuit simplify runs the same translation as circuit (HL) but
does not attempt to close the goal. Instead, it rewrites the
postcondition using the bit-level equalities derived from the circuit
translation of the program, then normalises by callbyvalue reduction.
The result is a new Hoare triple with the same precondition and
program but a simplified postcondition, which can then be discharged
by ordinary tactics.
Here the original postcondition res = a_ +^ b_ becomes true
after the circuit-level simplification, which trivial then closes.
More generally, circuit simplify is useful as a preprocessing step
when the full circuit translation would succeed only on part of
the postcondition and other reasoning is needed for the rest.
circuit simplify only applies to Hoare triples.
Failure modes
Both circuit and circuit simplify may fail in the following
ways:
failed to verify postconditionThe translation succeeded but the resulting circuit is not a tautology — i.e., the postcondition is genuinely false on some input satisfying the precondition, or its translation was too weak to capture the property.
exception(s) not supportedThe Hoare triple’s postcondition includes an exception-monad invariant component; the circuit translation handles only the pure (
inv-empty) part of the goal.circuit solve failed with error: …(orCircuit simplify failed with error: …)A
CircErrorwas raised during translation. The most common causes are: an operator with nobind oporbind circuitbinding; a type with nobind bitstringorbind arraybinding; a program statement that is not a translatable assignment.Wrong goal shapecircuit simplifywas invoked on a goal that is not a Hoare triple.
Limitations
Program statements must be assignments whose right-hand sides translate into circuits. Control flow (
if,while,match) and module/procedure calls are not handled — they must be eliminated (e.g. via unrolling or inlining) beforecircuitis invoked.Sampling statements (
<$) and exception-raising statements are not supported.Every type appearing in the goal must be either
bind bitstring-bound to a concrete (non-abstract) size, orbind array-bound with a bound element type.The FOL variant additionally requires that the goal has no free type variables.
The cost of the equivalence check grows with the bit-width of the variables and the depth of the resulting circuit; goals over large bitstrings or with many independent inputs may be infeasible to check directly.
circuit simplifyand theextenstactical (see Tactical: extens) are the usual escape hatches.
Tactic: proc change circuit
proc change circuit rewrites a contiguous run of program
statements into an alternative block, using circuit equivalence as the
soundness check. Unlike the regular proc change — which generates
a separate proof obligation for the equivalence of the two fragments
— proc change circuit discharges that obligation automatically
through the same machinery as the circuit tactic.
Syntax
proc change circuit [bindings]? cpos + N { stmt } .
cpos is the code position at which to begin rewriting.
N is the number of source instructions to replace, starting at cpos.
stmt is the replacement block, enclosed in braces.
[bindings] (optional) introduces fresh local variables visible only inside the replacement block, using the standard
[x : ty, …]syntax. This lets the new code use intermediate names that did not exist in the original procedure.
The equivalence check considers only those variables that are live at the end of the rewritten region — i.e., read by the rest of the procedure body or by the postcondition. Variables written by the replacement block but never read again may freely differ.
The single instruction at code position 1 is replaced by
c <- b +^ a; the circuit-equivalence checker establishes that the
two fragments agree on the value of c, which is the only variable
read downstream.
The [d : W8] binding introduces a fresh local that exists only
inside the replacement block; the original procedure body has no such
variable.
Failure modes (proc change circuit)
statements are not circuit-equivalentBoth fragments translated into circuits, but the equivalence check failed. This means the rewrite is genuinely unsound on at least one live output. (Use
fail proc change circuit …to assert that a rewrite is rejected — seetests/proc-change-circuit.ecfor an example.)circuit-equivalence checker error: …Translation of one of the fragments raised an exception. The typical cause is the same as for
circuit: an unbound operator, an unbound type, or a non-assignment instruction inside the region being replaced.exceptions not supportedThe postcondition’s exception-monad invariant is non-empty.