Chapter 6 — Ghost reasoning and lemmas

Some facts are true but not part of the executable algorithm. Ghost (or proof-only) code exists only to help the verifier — lemmas, case splits, and internal assertions.

Proof obligations inside a function

While proving a function, you may need an intermediate fact:

// We know a <= b and b <= c from earlier lines
// We need a <= c before the return

A proof step (contract_assert in CppVerify) asks the verifier to show the formula at that point. It is not a runtime check.

Lemmas as separate functions

Large proofs factor into lemma functions:

lemma monotonic(i, j):
  requires i <= j
  ensures f(i) <= f(j)

The lemma is verified once; call sites use its contract without repeating the induction.

Ghost variables and code

Ghost variables track proof-only state (counters in a proof, snapshots). Ghost blocks group steps that must not appear in compiled binaries.

Zero runtime cost is non-negotiable for production verification: CppVerify strips ghost constructs in CodeGen (Part II).

Axioms vs executable definitions

Spec (mathematical) functions define symbols used in contracts — like fibo(n) as a recurrence. Their bodies are definitions for the verifier, not instructions the CPU runs.

Proof functions are ghost programs whose only job is to establish pre ==> post.

Opacity and fuel

A recursive spec’s defining axiom — ∀n. fibo(n) = fibo(n-1) + fibo(n-2) — is dangerous to hand an SMT solver unrestricted: matching the right-hand side reintroduces the symbol and the instantiation never stops (a matching loop). The remedy is to keep recursive specs opaque by default and unfold them only as far as a proof needs:

  • fuel n — unfold the definition n times along this obligation;

  • reveal / hide — turn a spec transparent or opaque for a region of a proof.

This is a knob on automation, not on truth: revealing more never changes what is provable, only how hard the solver works. Part II maps these to reveal_with_fuel, reveal, and hide.