MathLabs

Foundations of mathematics

Formal proof with proof assistants (Lean)

A proof assistant like Lean checks a mathematical proof the way a compiler checks a program: it reduces "is this proof correct?" to "does this Γ⊢t:P\Gamma \vdash t : P typing judgment reduce", verified by a small trusted kernel.

IntuitionWhy trust a proof you cannot fully read?

Mathlib, the Lean 4 mathematical library, now has millions of lines of proof — far more than any one person can read line by line. Yet mathematicians trust it, for the same reason we trust an elevator inspector's stamp rather than personally re-welding every cable: a small, fixed checking procedure (the kernel) has verified every single step, and the kernel itself is short enough to audit by hand.

Interactive graph showing lemmas as nodes and their logical dependencies as directed edges.
The dependency graph of a checked proof: each node is a lemma, each edge a use of an earlier result; the kernel walks every edge.

UndergraduateThe Calculus of Inductive Constructions

Definition: Terms, types, and the typing judgment

Lean's core logic is the Calculus of Inductive Constructions (CIC): a dependent type theory where types can depend on values (dependent function type Π (x:A) B(x)\Pi\,(x:A)\,B(x), read "for every x:Ax:A, a value of type B(x)B(x)"), and new types are built by inductive declarations (natural numbers, lists, the propositional equality type). Every object is classified by the typing judgment Γ⊢t:P\Gamma \vdash t : P: "in context Γ\Gamma, the term tt has type PP".

Γ⊢t:P\Gamma \vdash t : P

Under the Curry–Howard correspondence, a proposition PP is itself a type, and a proof of PP is a term tt of that type — proving is programming. This is why type-checking, an algorithm that already had to exist to compile programs, is enough to check proofs.

⊢t:P  ⟹  P\vdash t : P \;\Longrightarrow\; P
Two layers of a proof assistant
LayerJobTrust
Elaborator / tacticsSearch for a proof term tt from high-level tactic scriptsUntrusted: bugs here only waste time
KernelRe-check that tt really has type PPTrusted: this is the de Bruijn criterion

AdvancedThe de Bruijn criterion and equality via Eq.rec

The de Bruijn criterion says a proof assistant is trustworthy when a small, independently-auditable kernel checks every proof term, so ∣kernel∣≪∣tactic engine∣|\text{kernel}| \ll |\text{tactic engine}|: soundness rests on a few hundred lines of code, not on the tactic engine that produced the term. Inductive equality a=ba = b is itself defined by one constructor refl:∀ a, a=a\mathrm{refl} : \forall\, a,\ a = a, and its eliminator Eq.rec\mathsf{Eq.rec} (the J-rule) is the single primitive the kernel needs to derive every other property of equality.

Eq.rec:{C:∀ y, a=y→Sort}→C a (refl a)→∀ y (h:a=y), C y h\mathsf{Eq.rec} : \{C : \forall\, y,\ a=y \to \mathrm{Sort}\} \to C\,a\,(\mathrm{refl}\,a) \to \forall\, y\,(h:a=y),\, C\,y\,h

There is a term Eq.symm:a=b→b=a\mathrm{Eq.symm} : a = b \to b = a built only from refla:a=a\mathrm{refl}_a : a = a and Eq.rec\mathsf{Eq.rec}; symmetry is a theorem of CIC, not an extra axiom.

Why is it true?

If equality needed a separate axiom for symmetry, transitivity, congruence, and so on, the trusted kernel would grow with every new fact about ==. Deriving them all from one eliminator keeps the kernel tiny.

Proof

Fix aa. We want a function taking h:a=bh : a = b (for arbitrary bb) to a proof of b=ab = a. Apply Eq.rec\mathsf{Eq.rec} with the motive C(y,h):=(y=a)C(y, h) := (y = a) — a family of propositions indexed by the point yy and the proof hh connecting aa to yy.

The eliminator asks for a proof of the base case C(a,refl a)C(a, \mathrm{refl}\,a), i.e. of a=aa = a; supply refl a\mathrm{refl}\,a itself. This is legal because the base case is always indexed by refl\mathrm{refl} at the starting point aa.

Eq.rec\mathsf{Eq.rec} then returns, for every yy and every h:a=yh : a = y, a proof of C(y,h)=(y=a)C(y,h) = (y = a). Instantiate y:=b, h:=hy := b,\ h := h: we obtain exactly a proof of b=ab = a.

Packaging this as Eq.symm h:=Eq.rec (C:=λy h, y=a) (refl a) b h\mathrm{Eq.symm}\,h := \mathsf{Eq.rec}\,(C := \lambda y\, h,\, y = a)\,(\mathrm{refl}\,a)\,b\,h gives the term of type Eq.symm:a=b→b=a\mathrm{Eq.symm} : a = b \to b = a. The kernel accepts it purely by matching this application against the type signature of Eq.rec\mathsf{Eq.rec} — no reasoning about "symmetry" as a concept is needed at the kernel level.

There is a term Eq.trans:a=b→b=c→a=c\mathrm{Eq.trans} : a = b \to b = c \to a = c built only from Eq.rec\mathsf{Eq.rec}.

Why is it true?

Chaining equalities (a=b=ca=b=c therefore a=ca=c) is used in essentially every proof; if it were an axiom instead of a derived term, the kernel would have to trust that axiom rather than verify it by computation.

Proof

Fix a,ba,b and h1:a=bh_1 : a = b. Given h2:b=ch_2 : b = c for arbitrary cc, we want a proof of a=ca = c. Apply Eq.rec\mathsf{Eq.rec} to h2h_2 with motive C(y,h):=(a=y)C(y, h) := (a = y), indexed by the point yy and the proof hh connecting bb to yy.

The base case required is C(b,refl b)C(b, \mathrm{refl}\,b), i.e. a=ba = b — exactly h1h_1. So h1h_1 is the base-case proof supplied to Eq.rec\mathsf{Eq.rec}.

Eq.rec\mathsf{Eq.rec} then delivers, for every cc and every h2:b=ch_2 : b = c, a proof of C(c,h2)=(a=c)C(c, h_2) = (a = c), which is what we wanted.

So Eq.trans h1 h2:=Eq.rec (C:=λy h, a=y) h1 c h2\mathrm{Eq.trans}\,h_1\,h_2 := \mathsf{Eq.rec}\,(C := \lambda y\, h,\, a = y)\,h_1\,c\,h_2 has type Eq.trans:a=b→b=c→a=c\mathrm{Eq.trans} : a = b \to b = c \to a = c. Notice the pattern shared with symmetry: both proofs "transport" a fact along an equality by choosing the right motive CC, then let the kernel's fixed reduction rule for Eq.rec\mathsf{Eq.rec} on refl\mathrm{refl} do the actual work.

UndergraduateReal-world applications: from compilers to number theory

Formal proof is no longer a laboratory curiosity. CompCert, a C compiler formally verified in Coq, has a machine-checked proof that its generated assembly always behaves like the source program — a guarantee no test suite can give. AWS and Signal use formally verified cryptographic code (in tools like AWS's s2n TLS library, HACL and F) to remove entire classes of memory-safety and side-channel bugs. And in pure mathematics, Lean's Liquid Tensor Experiment (2020–2022) formalized a hard theorem of Peter Scholze in condensed mathematics, and Terence Tao's team formalized the Polynomial Freiman–Ruzsa (PFR) conjecture's proof in Lean within months of the human proof appearing in 2023.

Example: Type-checking a proof of n+0=nn+0=n

Show, at the level of the kernel, why the term built by induction for ∀ n:N, n+0=n\forall\, n : \mathbb{N},\ n + 0 = n is accepted.

Solution

Addition on N\mathbb{N} is defined by recursion on the second argument: n+0:=nn + 0 := n and n+succ m:=succ (n+m)n + \mathrm{succ}\,m := \mathrm{succ}\,(n+m). So the base case of the goal n+0=nn + 0 = n reduces by computation to n=nn = n, which is refl n\mathrm{refl}\,n — the kernel accepts it with no logical work at all, just unfolding a definition.

For a general theorem ∀n, n+0=n\forall n,\ n + 0 = n this base case is trivial by the definition above, so no induction is even needed for this particular statement (unlike ∀n, 0+n=n\forall n,\ 0 + n = n, which does need induction since recursion is on the second argument).

The kernel's role is simply to unfold ++ according to its recursive definition on the literal term n+0n+0, arrive at nn, and then check that the two sides of the goal are definitionally equal — reducing "is this proof correct" to a terminating computation, exactly the de Bruijn criterion in action.

Example: Verified compilation as a typed equality

CompCert states its correctness theorem as a semantic preservation property. Explain what "the proof is a term of that type" buys engineers over ordinary compiler testing.

Solution

CompCert's theorem has the shape "for every source program SS that compiles to assembly AA, every observable behavior of AA is a behavior of SS" — a universally quantified statement over all source programs, not just the ones in a test suite.

A test suite checks finitely many (S,A)(S,A) pairs and can never rule out a miscompilation on program #4,000,001. A Coq proof term tt of the universally quantified statement is instead type-checked once, for the whole quantifier, by the kernel — the same kernel used for every other CompCert lemma, so no new trust is introduced per test case.

Because the proof term is total and the theorem is about the compiler's actual Coq source code (extracted to OCaml), any change to the compiler that breaks the guarantee simply fails to type-check, catching regressions that a finite regression-test suite would miss entirely — this is precisely why CompCert-generated code has had zero miscompilation bugs found by fuzzing campaigns that found hundreds in GCC and Clang.

What does the typing judgment Γ⊢t:P\Gamma \vdash t : P assert?

The de Bruijn criterion is best summarized as:

In the proof of Eq.symm:a=b→b=a\mathrm{Eq.symm} : a = b \to b = a, what proposition is supplied for the base case C(a,refl a)C(a, \mathrm{refl}\,a)?

Which real-world system relies on a machine-checked proof that generated assembly always behaves like the source program?

References

  1. Leonardo de Moura, Sebastian Ullrich (2021). The Lean 4 Theorem Prover and Programming Language
  2. Xavier Leroy (2009). Formal verification of a realistic compiler
  3. Terence Tao, et al. (2023). A Proof of the Polynomial Freiman-Ruzsa Conjecture · arXiv:2311.05762