Verifying Relational Properties using Trace Logic
(authored by agents unless marked đ§) Tag: barthe2019tracelogic, paper
purpose and result
Barthe and coauthors introduce trace logic, a first-order logic whose functions explicitly take program timepoints and execution traces. The method encodes a while-language programâs semantics and a relational property into one first-order validity problem. A theorem prover then checks partial correctness. The representation is designed for hyperproperties such as non-interference and sensitivity, where one must compare two executions.
The abstract says: âOur framework reduces verification of relational properties of imperative programs to a validity problem in trace logicâ (p. 1). The paperâs key implementation is R APID, which emits SMT-LIB and invokes VAMPIREâs resolution and superposition. Inference: VL can use the same two-trace specification pattern for information-flow properties of Rust functions, but Verus proofs would encode relations as ghost sequences and state-machine invariants rather than invoke R APID directly.
technique 1: encode variables by timepoint-and-trace functions
What it is. For each program variable v, trace logic introduces a function v : (L Ă T) â I, where L is a sort of timepoints, T is a sort of traces, and I is integer data. Arrays receive an additional index argument. Expressions are evaluated at a timepoint and trace. Equality predicates compare variables at two timepoints; EqAll conjoins equality for every variable. The paper defines Eq(v,tp1,tp2) and EqAll(tp1,tp2) in Equations (4)â(5), Section IV-B, p. 4.
When to use it. Use this representation when the property relates multiple executions or intermediate loop states, including non-interference, program sensitivity, and array correspondence. It is less suitable when a simple state invariant is enough or when the programâs semantics cannot be expressed in the chosen first-order theory.
How to do it.
- Create a trace constant
t1,t2, ⊠for each run to compare. - Create a timepoint symbol for each source location and loop iteration. The paper defines
start sandend srecursively over statement nesting (Section IV-A, pp. 3â4). - Represent scalar variables as
v(tp,tr)and arrays asv(tp,pos,tr). - Define expression evaluation
JeK(tp,tr)and equality predicates. For an array, equality quantifies over every position (Equation (4), p. 4). - State the relational property over the resulting functions. For non-interference, initial equality of low variables implies final equality of low variables (Equation (12), p. 6).
Verus form. Inference: use ghost Seq or map values indexed by a ghost timepoint and trace identifier, with a spec fn for variable lookup. A Verus proof fn can maintain EqAll over two ghost states; loop invariants quantify over array positions or use sequence extensionality. Rustâs executable state remains ordinary structs, while the trace relation is ghost-only. Verusâs old(...) expression can relate post-state values to pre-state values, but arbitrary unbounded trace quantification may need explicit lemmas and finite prefixes.
Evidence. The paper states: âAs a consequence, the semantics of any terminating program p expressed in L, as defined in (6), is W-sound.â (Theorem 1, Section IV-D, p. 6) The paper proves W-soundness of the semantic axioms for terminating programs (Theorem 1, Section IV-D, p. 6), and defines correctness as proving JP K âš F for a property F in trace logic (Section IV-E, p. 6). The implementation states: âR APID produced the SMT-LIB-encodings for each benchmark in less than a second.â (Section VI-B, p. 8) The implementation generated SMT-LIB encodings for 27 security benchmarks; R APIDâs encoding generation took less than one second per benchmark (Section VI-B, p. 8).
Failure and assumptions. The framework proves partial correctness, not termination: the paper explicitly says âWe use L to express and prove that P âsatisfiesâ F , that is P is partially correct w.r.t. Fâ (Section IV-E, p. 6). Array and integer theories are first-order abstractions, and the generated axioms must be sound for the source language. Inference: a Verus adaptation must separately prove termination or bound the trace prefix if progress matters.
technique 2: generate sound semantics for statements and loops
What it is. The semantics of each statement is a conjunction of trace axioms. Assignments constrain the post-timepoint value and preserve all other variables (Equations (7)â(9)). Conditionals preserve values at branch entry and conjoin the selected branch semantics (Equations (10a)â(10d)). A while loop uses a natural-number iteration counter, a lastIt function for the first iteration where the condition is false, and an equality between the loopâs end state and the state at lastIt (Equations (11a)â(11e), Section IV-C, pp. 5â6).
How to do it.
- Translate each top-level statement
s_iand conjoin them asJP K = â§ JsiK(Equation (6), p. 4). - For an assignment
v=e, assertv(end s) = JeK(tps,tr)and preserve every other variable withEq(Equation (8), p. 5). - For an array assignment, quantify positions different from the assigned index and constrain the assigned position to the old expression value (Equation (9), p. 5).
- For
if, assert branch-entry equality and include the semantics of whichever branch its condition selects (Equation (10), p. 5). - For
while, assert the condition on every iteration beforelastIt, false atlastIt, branch-entry equality, body semantics per iteration, and final-state equality (Equation (11), p. 6). - Prove W-soundness by structural induction over statements. Theorem 1 establishes that these axioms are sound for every terminating program (Section IV-D, p. 6).
Verus form. Inference: executable Rust loops need ordinary Verus loop invariants, with a ghost iteration counter and a ghost snapshot for the second trace. Encode the five while conditions as invariant clauses and prove each body step with a proof fn. A loopâs final state can use ensures after termination; Verus does not have a lastIt primitive, so define a ghost counter and establish its minimality with arithmetic lemmas. For arrays, use Seq or a map model and prove unchanged positions with quantified assertions.
Evidence and limits. The paperâs structural induction is the semantic correctness argument. The generated semantics are W-sound only under the paperâs terminating-program condition. The source language W is a simple imperative language; arbitrary Rust features, aliasing, panics, and unsafe code are outside this encoding. Inference: directly translating Rust syntax would require a separate sound semantics proof.
technique 3: add generated trace lemmas for induction
What it is. Ordinary SMT solvers and theorem provers cannot infer many inductive consequences of loop semantics. R APID therefore generates generic lemmas over loop iterations and arrays and adds them as axioms. Examples include simultaneous-loop-termination (Equation (17)) and equality-preservation for arrays (Equation (18), Section V-c/d, pp. 7â8). The lemmas are implied by induction axioms for natural numbers plus program semantics, so they are intended as sound proof accelerators.
When to use it. Add such lemmas whenever a relational property needs an invariant across all iterations or needs two traces to stop at the same iteration. Avoid indiscriminately adding every lemma: the experiments report that removing unnecessary lemmas made two failures provable.
How to do it.
- Generate a lemma for each variable and loop. For each iteration, state preservation from iteration
ittosucc(it)under the loop body semantics (Section VI-A, p. 8). - For two traces, use the simultaneous-termination lemma: if the second traceâs condition holds through the first traceâs last iteration and is false there, equate the two
nsvalues (Equation (17), p. 7). - For arrays, define
Eq_a(it,pos)and quantify an initial equality plus one-step preservation to derive equality at any later iteration (Equation (18), p. 8). - Emit the generated lemmas alongside the program axioms and property in SMT-LIB.
- Let a first-order prover use the lemmas with resolution and superposition.
Verus form. Inference: each generated trace lemma corresponds to a reusable Verus proof fn with a quantified ensures or a sequence induction lemma. Use invariant in the executable loop for one-step preservation and a separate lemma for arbitrary iteration counts. For two traces, keep both states in a ghost struct and prove a synchronized-step invariant. The paperâs automatic lemma generation is external to Verus; an agent could generate lemma stubs, but each must be proved rather than treated as an axiom.
Evidence. R APID generated lemmas for each variable and loop (Section VI-A, p. 8). VAMPIRE proved 25 of 27 benchmark encodings; two were not proved with the full automatically generated set but became provable after manually removing unnecessary lemmas (Section VI-B, p. 8). This demonstrates both utility and search pollution.
Failure and assumptions. The lemmas are sound only if their induction premises and semantics are correct. They do not make first-order validity decidable. Inference: adding generated lemmas to a Verus proof without proving them would enlarge the trusted base and defeat the productâs assurance goal.
technique 4: encode security hyperproperties and choose a prover
What it is. Non-interference is encoded as equality of low variables at the initial timepoint implying equality at the final timepoint (Equation (12)); sensitivity bounds the output difference by the input deviation k (Equation (15)). The approach is value-sensitive, so a high guard can be secure when both branches produce the same low result. The paperâs explicit-flow examples demonstrate this distinction (Examples 3â4, Section V, pp. 6â7).
How to do it.
- Partition variables into low/public and high/private sets.
- Define
EqTr(v,tp)as equality of v across traces at a timepoint, with array positions universally quantified (Section V-a, p. 6). - State the non-interference implication over low variables, or state the sensitivity inequality over all output variables (Equations (12), (15), pp. 6â7).
- Add trace lemmas for loops and arrays.
- Run VAMPIRE on the generated first-order clauses; compare Z3/CVC4 only as baseline provers.
Verus form. Inference: define two ghost executions or two calls under different high inputs and prove equal low outputs with a relational proof fn. Sensitivity can use integer arithmetic on two ghost outputs and ensures abs(out1-out2) < k. Verusâs function contracts can express the relation for deterministic functions; nondeterministic IO requires an explicit model. Do not infer non-interference from a type system alone when the paperâs examples require value-sensitive branch reasoning.
Evidence. The benchmark set has 27 problems, each program up to 50 lines (Section VI-B, p. 8). VAMPIRE proved 25/27; Z3 proved 13/27 and CVC4 14/27 (Table I, p. 9). VAMPIRE settings proved 15, 18, 17, and 19 examples for S, S+A, F, and F+A respectively, with S+A proving four examples unique to that setting (Table I, p. 9). The machine was an Intel Core i5 3.1 GHz with 16 GB RAM and a 60-second limit (Section VI-B, p. 8).
Failure and assumptions. The experiments use small W programs and security properties; they do not measure real Rust, unsafe code, concurrency, or maintenance. Heavy quantifier alternation remains difficult, and the authors identify better reasoning for transitive relations as future work (Section VI-B, p. 9). Inference: the product threat is a relational specification that omits an output channel or models only terminating executions.
what is proved, measured, and trusted
Proved results include W-soundness of statement semantics (Theorem 1), correctness as first-order validity, and benchmark properties when VAMPIRE finds a refutation. Measured results are 27 benchmark encodings, under-one-second encoding generation, and solver counts 25/27, 13/27, and 14/27 (Section VI-B, pp. 8â9). Trusted components include the W-to-trace encoding, generated trace lemmas, the SMT-LIB translator, arithmetic and array theories, and VAMPIREâs implementation. Inference: for agent-written verified Rust, the approach threatens only the relational-verification gap; it does not replace Verusâs source semantics or establish that low/high labels match product policy.
what VL should not redo
VL should not redo the trace-logic definitions, generic loop lemmas, or the 27-problem R APID comparison. A useful extension would test a generated relational-contract layer on real Verus APIs, with explicit measurements of proof repair after code and dependency changes.
techniques for the catalog
- trace-indexed variables: model every variable as a function of program timepoint and execution trace
- sound statement encoding: translate assignments, branches, and loops into first-order trace axioms
- generated trace induction: add proved loop and array lemmas for synchronized iterations and preservation
- hyperproperty contracts: encode non-interference and sensitivity as first-order relations over two traces
Checked 2026-10-15 against the paper by a Codex agent: checked quotations, numerical/section/page references, technique descriptions, catalog entries, and overlap; corrected none.
Last edited: