


The proof term plays the role of the certificate. Tactics, LLMs, and humans are all search strategies that produce it.

“I would like to effusively thank Leonardo de Moura for writing my favourite computer game…” — Kevin Buzzard (ICM 2022)
Buzzard: ICM 2022, EMS Press. Tao: AMS Notices, Jan 2025. Kontorovich: Quanta, Mar 2026. de Moura: blog, Feb 2026.


leanprover-community.github.io/mathlib_stats.html

Prop is the type of propositions. To prove p, construct a term t : p.(x : A) → B x.B does not depend on x: ordinary function type A → B.A : Prop and B : Prop: implication P → Q.B x is a proposition for each x: universal quantification ∀ x : A, B x.Compare: Rocq/Coq shares this foundation. HOL systems have simple types. Mizar and Metamath use set theory.

All code on these slides has been tested against the Lean 4 type-checker.
Vector α n: a list of length exactly n. Appending gives Vector α (n + m).Fin n: a natural number less than n. Out-of-bounds indexing is a type error, not a runtime crash.SortedList lo: a sorted list with lower bound lo. Sortedness is enforced by the type, not a runtime assertion:-- A sorted list carries its lower bound in the type
inductive SortedList : Nat → Type where
| nil (lo : Nat) : SortedList lo
| cons (x : Nat) {lo : Nat} (h : x ≤ lo) (tail : SortedList lo) : SortedList x
-- [1, 3, 5] with proof that each element ≤ the next
example : SortedList 1 :=
.cons 1 (by omega) (.cons 3 (by omega) (.nil 5))
Side conditions in prose become part of the type, checked automatically.

Complementary ways to check a Lean proof:
leanchecker: replays built declarations through the kernel in a fresh process.comparator: trusted challenge file + sandboxed build + external validation.Analogous to multiple independent implementations of a crypto protocol.

Lean can check that a proof term matches a theorem statement. It cannot decide whether your definitions match your mathematical intent.
-- totalized: deriv = 0 when undefined
#check Real.deriv_rpow_const
-- (x p : ℝ) : deriv (· ^ p) x = p * x ^ (p-1)
-- explicit hypotheses:
#check Real.hasDerivAt_rpow_const
-- {x p : ℝ} (h : x ≠ 0 ∨ 1 ≤ p) :
-- HasDerivAt (· ^ p) (p * x ^ (p-1)) x
Bridges: state core theorems with explicit predicates, review definitions separately from proofs, triangulate with sanity-check lemmas, have a human review the statement.
lean-lang.org/doc/reference/latest/ValidatingProofs/

sorry or custom axioms? Use #print axioms thmName..olean state tricks or core state bugs? Re-check with leanchecker --fresh.comparator: trusted challenge file, sandboxed build, external validation.Lean.trustCompiler in the axiom list; it enlarges the trusted computing base.Different concerns call for different checks, analogous to escalating from honest to malicious adversary models in this course.
Note: AI-generated code may not be explicitly adversarial, but models will find workarounds even when trying to be helpful. The same checks apply.

-- XOR on booleans, by pattern-matching. same → false, different → true.
def bxor : Bool → Bool → Bool
| true, true => false
| false, false => false
| _, _ => true
-- Encrypt a 1-bit message with a 1-bit key.
def enc (m k : Bool) : Bool := bxor m k
def bxor defines a function by pattern matching. Lean checks that all cases are covered.(What happens if we XOR twice with the same key?)

bxor and enc return Bool. But Lean can also express propositions:
def odd (n : Nat) : Prop := ∃ k, n = 2 * k + 1
odd 3 is Prop: a statement that can be true or false.bxor true false. It is a proposition, not a computation.example : odd 3 := ⟨1, by decide⟩
The witness 1 and the proof that 3 = 2 * 1 + 1 together form a term of type odd 3.
Propositions are types.
Proofs are terms.

Theorem proving as a game: you see goals and hypotheses, then apply moves (tactics). Each move transforms the goal until nothing remains.
theorem otp_roundtrip (m k : Bool) : enc (enc m k) k = m := by
cases m <;> cases k <;> rfl
cases m splits the goal into m = true and m = false.<;> applies the next tactic to all remaining goals.rfl: both sides compute to the same value. No goals remain. The kernel checks the resulting proof term.#print axioms otp_roundtrip
Only propext (propositional extensionality). No sorry, no Classical.choice. The trust base is visible.

variable {p q : Prop}
-- Term style: build the proof object directly
theorem and_swap_term (h : p ∧ q) : q ∧ p :=
And.intro h.right h.left
-- Tactic style: tell Lean how to construct the proof
theorem and_swap_tac (h : p ∧ q) : q ∧ p := by
exact ⟨h.right, h.left⟩
-- Declarative: state what you're building
theorem and_swap_decl (h : p ∧ q) : q ∧ p := by
constructor
· exact h.right
· exact h.left
All three produce the same proof term. Lean does not distinguish between them once elaboration is done.

-- Procedural: looks like a recursive program
theorem once_denied_proc :
∀ pre suf : List Decision,
authorize (pre ++ (.deny :: suf)) = .deny
| [], _ => rfl
| .deny :: _, _ => rfl
| .allow :: ds, suf => by
simpa [authorize] using once_denied_proc ds suf
-- Tactic: delegates work to induction and case splitting
theorem once_denied_tac
(pre suf : List Decision) :
authorize (pre ++ (.deny :: suf)) = .deny := by
induction pre with
| nil => rfl
| cons d ds ih =>
cases d <;> simp [authorize, ih]
Same statement, same kernel, different surface style.
(The theorem: if a deny appears anywhere in the list, the overall authorization is deny, regardless of what comes before it.)

theorem once_denied_decl
(pre suf : List Decision) :
authorize (pre ++ (.deny :: suf)) = .deny := by
induction pre with
| nil => rfl
| cons d ds ih =>
cases d with
| deny => rfl
| allow =>
calc
authorize ((.allow :: ds) ++ (.deny :: suf))
= authorize (ds ++ (.deny :: suf)) := by rfl
_ = .deny := ih
calc reads like a short mathematical argument: “rewrite, rewrite, finish.”Procedural exposes recursive structure. Tactic is often fastest to write. Declarative can be easier to read back later.

def OTP (n : Nat) := Fin n → Bool
def encVec {n : Nat} (m k : OTP n) : OTP n :=
fun i => bxor (m i) (k i)
example {n : Nat} (m k : OTP n) (i : Fin n) :
encVec (encVec m k) k i = m i := by
simp only [encVec]
cases m i <;> cases k i <;> rfl
n appears inside the type. OTP n = an n-bit pad, represented as Fin n → Bool.
inductive Decision where
| allow | deny
def authorize : List Decision → Decision
| [] => .deny
| .deny :: _ => .deny
| .allow :: rs => authorize rs
theorem once_denied_always_denied
(pre suf : List Decision) :
authorize (pre ++ (.deny :: suf)) = .deny := by
induction pre with
| nil => rfl
| cons d ds ih =>
cases d <;> simp [authorize, ih]
This is toy code, but the structure (inductive types, recursive authorization, inductive proofs) is the same one used in Cedar’s production Lean model at AWS.

lean4-skills began from patterns and antipatterns noted in my CLAUDE.md file during this project.#print axioms deFinetti → propext, Quot.sound, Classical.choice (the standard three).


AI contributions
lean4-skills + lean-lsp-mcp).lean4-skills to improve future use.sorry-free milestone

Rupert’s property: a convex body can pass a larger copy of itself through a hole cut in the original.
A cube has Rupert’s property. (David Renshaw / Quanta)
The Noperthedron does not. (David Renshaw)
sorry.A massive computation produces a certificate; a small checker validates each piece locally.


Formal proof objects and cryptographic certificates are converging.

Image: Ix / Argument Computer

lean-lsp-mcp: tools for agents to interact with LeanProvides agents with the same tools users have: InfoView, syntax highlighting, documentation exploration.
| Diagnostics & Goals | Navigation & Info | Code Exec. & Verif. | Search |
diagnostic_messagesgoalterm_goal |
hover_infocompletionsdeclaration_filefile_outline |
buildrun_codeverifymulti_attempt |
local_searchleansearchloogleleanfinderstate_searchhammer_premise |
Also: code_actions, get_widgets, get_widget_source, profile_proof

lean4-skills for agentic coding in Lean/prove, /formalize, /learn, /checkpoint, etc.:
lean-lsp-mcp:

lean4-skills /learn: tools for education and exploration > let’s try socratic learning of measurability tactic
Switching gears! Updating Learning Profile: style=socratic | topic=measurability tactic | mode=mathlib | presentation=supporting
Let me do some discovery first.
• lean-lsp – Local Search (MCP)(query: “measurability”)
L { “items”: [] }
• lean-lsp – LeanSearch (MCP)(query: “measurability tactic for proving measurable functions”)
L { “items”: [{ ... +32 lines (ctrl+o to expand)
• lean-lsp – Lean Finder (MCP)(query: “measurability tactic automation for measurable functions”)
L { “items”: [{ ... +29 lines (ctrl+o to expand)
Good discovery results. Here’s what I found:
– measurability is defined in Mathlib.Tactic.Measurability
– Under the hood, it’s aesop with the Measurable rule set
– It handles Measurable f, AEMeasurable f, StronglyMeasurable f, MeasurableSet s, and more
– measurability? suggests an explicit proof script; measurability! is the strict variant

lean4-skills /learn: Socratic interaction Pedagogy: Socratic mode — starting with the user’s prior knowledge to calibrate depth.
Let me begin with a question to figure out where you’re coming from:
What does it mean for a function to be “measurable” between two measurable spaces?
In other words, if someone hands you f : α → β and both types carry a MeasurableSpace instance,
what property must f satisfy?
(If you’d rather skip the math background and jump straight to how the tactic works in Lean, just say so.)

lean4-skills /learn: Socratic interaction (continued) > borel subset of beta has borel preimage?
Pedagogy: User has the right shape of the idea but is over-specializing — nudging toward the general definition.
You’re very close! The shape is right — preimages of “nice” sets should be “nice.”
But you’ve jumped to a specific case. Let me push back gently:
Borel sets are the σ-algebra generated by open sets in a topological space.
But does every measurable space come from a topology?
Consider: Lean’s definition is
def Measurable [MeasurableSpace α] [MeasurableSpace β] (f : α → β) : Prop :=
∀ ⦃t : Set β⦄, MeasurableSet t → MeasurableSet (f ⁻¹' t)
Notice there’s no TopologicalSpace anywhere — just MeasurableSpace.
So what’s the more general statement? And when would your “Borel” version be the right way to think about it?

lean4-skills /learn: Socratic interaction (continued) > oh almost-measurable preimage is almost-measurable?
Pedagogy: User is guessing rather than reading the definition — redirecting attention to what’s actually written.
Not quite — let’s slow down and look at exactly what the definition says.
Forget “almost-measurable” for now (that’s a different concept: AEMeasurable, which means measurable after modifying on a null set).
The definition is staring right at us:
∀ ⦃t : Set β⦄, MeasurableSet t → MeasurableSet (f ⁻¹' t)
Read it out loud in English. There are only two ingredients: MeasurableSet and preimage.
No “almost,” no “Borel,” no topology.
In plain words: what does MeasurableSet t → MeasurableSet (f ⁻¹' t) say?

Projects mentioned today
