verifying-crypto-with-lean/chapters/ch04-tactics.tex
saymrwulf 45048d4898 Verifying Cryptography with Lean 4: complete 12-chapter curriculum
- 53-page LaTeX/TikZ book (main.pdf + full sources): from zero background
  to reading the real Ed25519/Pasta verification projects
- runnable exercises with sorry-holes + complete solutions for chapters
  2-7, 9, 12; every solution file compiles clean (zero errors, no sorry)
  against Lean v4.30.0-rc2 + Mathlib 5450b53e
- lake project pinned to the same toolchain/Mathlib the solutions were
  verified with; students fetch the Mathlib cache, never build it
- honesty ledger in README: what was machine-checked and how

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 09:44:40 +02:00

194 lines
8.7 KiB
TeX
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

\chapter{Tactics: Proving as a Dialogue}
\label{ch:tactics}
\section{From programs to conversations}
Writing proofs as raw programs, as in Chapter~\ref{ch:pat}, is honest work,
but it scales badly: a real correctness proof for field multiplication would
be a program the size of a small compiler. Nobody writes those by hand.
Instead, Lean offers \emph{tactic mode}: an interactive dialogue where you
issue commands and Lean builds the proof program for you, step by step,
showing you the remaining work after each move.
You enter the dialogue with the keyword \lean{by}:
\begin{lstlisting}[language=Lean]
theorem and_swap (P Q : Prop) : P ∧ Q → Q ∧ P := by
intro h
constructor
· exact h.2
· exact h.1
\end{lstlisting}
Place your cursor after \lean{by} in the editor and Lean shows the
\textbf{goal state} --- the exact logical situation at that point:
\begin{lstlisting}
P Q : Prop
⊢ P ∧ Q → Q ∧ P
\end{lstlisting}
Everything above the turnstile \(\vdash\) is what you \emph{have} (the
context); the line after it is what you \emph{owe} (the goal). Every tactic
transforms this picture. After \lean{intro h}, the hypothesis moves above the
line; after \lean{constructor}, the goal splits in two. Proving becomes a
game whose board you can always see.
\begin{bigidea}
A tactic proof is a \textbf{recorded conversation with the goal state}. The
skill of proving is not memorizing tactic names --- it is learning to
\emph{read the goal state} and recognize which of a handful of moves makes it
simpler. Below is the core vocabulary; it covers the vast majority of every
proof in the real Ed25519 development.
\end{bigidea}
\begin{center}
\begin{tabular}{@{}lll@{}}
\toprule
\textbf{Tactic} & \textbf{When the goal looks like...} & \textbf{Effect} \\
\midrule
\lean{intro h} & \lean{P → Q}, \ \lean{∀ x, P x} & assume it; name the evidence \\
\lean{exact e} & anything & finish: \lean{e} is a proof of the goal \\
\lean{apply f} & \lean{Q}, given \lean{f : P → Q} & reduce the goal to \lean{P} \\
\lean{constructor} & \lean{P ∧ Q}, \lean{P ↔ Q}, ... & split into pieces \\
\lean{cases h} & have \lean{h : P Q} (or \(\wedge\), \lean{}) & case analysis on \lean{h} \\
\lean{rw [eq]} & contains a rewritable subterm & replace using equation \lean{eq} \\
\lean{simp} & simplifiable clutter & rewrite with a lemma database \\
\lean{induction n} & \lean{∀ n : Nat, ...} & base case + inductive step \\
\lean{rfl} & \lean{a = a} after computation & close by computation \\
\bottomrule
\end{tabular}
\end{center}
\section{Rewriting: equality as a tool}
The workhorse tactic of equational reasoning is \lean{rw} (rewrite). Given a
proven equation, it replaces one side by the other inside your goal:
\begin{lstlisting}[language=Lean]
example (a b : Nat) (h : a = b) : a + a = b + b := by
rw [h] -- goal becomes: b + b = b + b, closed by rfl automatically
\end{lstlisting}
Chains of rewrites read like the two-column proofs of school geometry,
except a machine checks every line. Here is commutativity-and-associativity
shuffling, Mathlib lemmas by name:
\begin{lstlisting}[language=Lean]
example (a b c : Nat) : a + b + c = c + b + a := by
rw [Nat.add_comm a b] -- b + a + c = c + b + a
rw [Nat.add_assoc] -- b + (a + c) = c + b + a
rw [Nat.add_comm a c] -- b + (c + a) = c + b + a
rw [← Nat.add_assoc] -- b + c + a = c + b + a
rw [Nat.add_comm b c] -- done
\end{lstlisting}
The arrow \(\leftarrow\) rewrites right-to-left. Nobody enjoys writing five-line
shuffles like this, which is exactly why Chapter~\ref{ch:automation}
introduces \lean{ring} --- but you must \emph{once} feel the manual version to
understand what the automation is doing on your behalf.
\section{Induction: the tactic that conquers infinity}
Remember the embarrassment of Chapter~\ref{ch:pat}: \lean{0 + n = n} does not
hold by computation. Now we can prove it --- by induction, the proof
technique that inductive types were born for:
\begin{lstlisting}[language=Lean]
theorem zero_add (n : Nat) : 0 + n = n := by
induction n with
| zero => rfl -- 0 + 0 = 0: computes
| succ k ih => rw [Nat.add_succ, ih]
\end{lstlisting}
The \lean{induction} tactic converts a statement about \emph{all} naturals
into two finite obligations: the statement for \lean{zero}, and the statement
for \lean{succ k} \emph{assuming it for} \lean{k} (the induction hypothesis
\lean{ih}). Because every natural number is built from those two
constructors, the two cases cover infinity.
\begin{aha}
Induction is not a new axiom to swallow --- it falls out of the inductive
definition of \lean{Nat} itself. ``Every \lean{Nat} is \lean{zero} or a
\lean{succ}'' \emph{is} the license to do case analysis; recursion on the
structure \emph{is} the induction. Data and proof principle are two views of
the same declaration. This is Curry--Howard paying rent again.
\end{aha}
\begin{pitfall}
When a proof gets stuck, resist the urge to try random tactics --- the
formal-methods equivalent of mashing buttons. The goal state is telling you
something. Three honest questions unstick most situations: (1)~Is the
statement actually true as written --- check a small example with
\lean{\#eval}! (2)~Am I missing a hypothesis --- is there an unstated bound or
nonzero condition? (3)~Is my induction on the right variable? In the real
projects behind this book, ``the proof is stuck'' was, more often than not,
the \emph{statement} being subtly wrong --- a truncated subtraction, a missing
bound. The proof assistant was the messenger.
\end{pitfall}
\section{Structuring real proofs: \texttt{have} and \texttt{calc}}
Big proofs are not flat lists of tactics; they are structured arguments with
named intermediate results. The \lean{have} tactic states and proves a
stepping stone; \lean{calc} lays out a chain of equalities or inequalities
the way you would on a whiteboard:
\begin{lstlisting}[language=Lean]
example (a b : Nat) (h : a = 2 * b) : a + a = 4 * b := by
have h2 : a + a = 2 * a := by rw [Nat.two_mul]
calc a + a = 2 * a := h2
_ = 2 * (2 * b) := by rw [h]
_ = 4 * b := by rw [← Nat.mul_assoc]
\end{lstlisting}
This style is not cosmetic. In the verified field arithmetic you will read
later, a single multiplication correctness proof is a \lean{calc} chain
tracking limb products through carries --- dozens of steps, each trivial,
whose \emph{composition} is the theorem. \lean{have} and \lean{calc} are how
proofs stay readable at that scale; they are also how they stay
\emph{maintainable}, because a broken step localizes the damage to one line.
There is one more structuring fact worth knowing early, because it saved the
real project from a crash-course (literally --- see
Chapter~\ref{ch:honesty}): breaking a proof into small named \lean{have}
steps also controls the proof assistant's \emph{memory appetite}. A monolithic
``figure it all out at once'' tactic call over a huge context can consume
gigabytes; ten targeted steps, pennies each, prove the same thing. Structure
is not just style --- it is engineering.
\begin{tryit}
Open \code{exercises/Ch04.lean}. It sets up each theorem with the goal state
drawn in a comment, then asks you to: prove \lean{and_swap} in tactic mode;
prove \lean{zero_add} \emph{without} peeking above; and repair a broken
\lean{calc} chain in which exactly one step is wrong. The third
exercise is secretly the most realistic job training in this book.
\end{tryit}
\section*{Exercises}
\exercise{Prove by induction: \lean{∀ n : Nat, n + 0 = n} and
\lean{∀ n m : Nat, n + succ m = succ (n + m)}. (These are the mirror images
of the definitional equations --- the ones computation gives you for free ---
and together they yield commutativity.)}
\exercise{Using the previous exercise, prove
\lean{∀ n m : Nat, n + m = m + n} by induction on \lean{m}. Write out, in
one prose sentence per case, what each branch of your proof says.}
\exercise{Prove \lean{∀ n : Nat, 2 * n = n + n} twice: once with
\lean{induction}, once with a single \lean{rw} using a Mathlib lemma you find
yourself (search hint: \lean{exact?} asks Lean to search for you).}
\exercise{(Reading) In the goal state
\lean{h : a < 2\textasciicircum{}51 ⊢ a * 19 < 2\textasciicircum{}56}, no induction is needed --- this is pure
arithmetic. Which tactic from the table would you \emph{guess} handles it?
(Answer next chapter; your guess is the point.)}
\begin{checkpoint}
You should now be able to: read a goal state (context, turnstile, goal);
drive the core tactics \lean{intro}, \lean{exact}, \lean{apply},
\lean{cases}, \lean{rw}, \lean{induction}; structure a multi-step argument
with \lean{have} and \lean{calc}; and --- most importantly --- when stuck,
interrogate the \emph{statement} before blaming the proof.
\end{checkpoint}