verifying-crypto-with-lean/chapters/ch09-denotation-bridge.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

190 lines
8.9 KiB
TeX

\chapter{The Denotation Bridge: What Do Five Numbers \emph{Mean}?}
\label{ch:denotation}
\section{Two worlds, one bridge}
We now hold two very different objects. On one side, mathematics:
the field $\Fp$, where elements are abstract clock positions and $+$ means
ideal modular addition. On the other side, the extracted model: arrays of
five \lean{U64} words, shuffled by loads, adds, and stores. The entire
question of verified cryptography is how to say --- precisely --- that the
second \emph{implements} the first.
The answer is a single function, small enough to write on one line and
important enough to carry this whole book. Given limbs
$a = (a_0, a_1, a_2, a_3, a_4)$, define the \textbf{denotation}:
\[
\denote{a} \;=\; a_0 + 2^{51} a_1 + 2^{102} a_2 + 2^{153} a_3 + 2^{204} a_4
\;\in\; \Fp .
\]
Read $\denote{a}$ as ``the field element these limbs \emph{mean}.'' It is
positional notation, nothing more --- base $2^{51}$ instead of base 10, with
the result interpreted on the clock face of $\Fp$. In Lean:
\begin{lstlisting}[language=Lean]
def denote (a : Array U64 5) : ZMod p :=
a[0].val + 2^51 * a[1].val + 2^102 * a[2].val
+ 2^153 * a[3].val + 2^204 * a[4].val
\end{lstlisting}
With the bridge in hand, correctness of an operation becomes a
\emph{commuting square} --- one picture you should internalize until you see
it in your sleep:
\begin{center}
\begin{tikzpicture}[
world/.style={font=\small,align=center},
arr/.style={-{Stealth},thick,ink2},
lbl/.style={font=\small\color{ink2}}
]
\node[world] (tl) at (0,2.6) {$(a, b)$\\ \footnotesize limb arrays};
\node[world] (tr) at (7.2,2.6) {$\mathtt{add}(a,b)$\\ \footnotesize limb array};
\node[world] (bl) at (0,0) {$(\denote{a}, \denote{b})$\\ \footnotesize field elements};
\node[world] (br) at (7.2,0) {$\denote{a} + \denote{b}$\\ \footnotesize field element};
\draw[arr] (tl) -- node[lbl,above] {machine code} (tr);
\draw[arr] (bl) -- node[lbl,below] {ideal math} (br);
\draw[arr] (tl) -- node[lbl,left] {$\denote{\cdot}$} (bl);
\draw[arr] (tr) -- node[lbl,right] {$\denote{\cdot}$} (br);
\node[font=\small\color{accent},align=center] at (3.6,1.3)
{\textbf{the theorem:}\\ both routes agree};
\end{tikzpicture}
\end{center}
\begin{bigidea}
\textbf{The correctness of an implementation is the statement that
denotation commutes with every operation:}
\[
\denote{\mathtt{add}(a,b)} = \denote{a} + \denote{b},
\qquad
\denote{\mathtt{mul}(a,b)} = \denote{a} \cdot \denote{b},
\qquad\dots
\]
The left-hand side lives in the machine world (with its bounds hypotheses
and \lean{Result}s); the right-hand side is pure mathematics. One equation
per operation, and the ugly optimized code is pinned, forever, to the
textbook meaning. Every verified-crypto project you will ever read is this
diagram, instantiated.
\end{bigidea}
\section{Why redundancy is freedom (and where bugs hide)}
A subtlety with consequences: denotation is \textbf{many-to-one}. The limb
arrays $(19, 0, 0, 0, 0)$ and $(p + 19 \bmod 2^{\cdots}, \dots)$ --- or more
mundanely, unreduced sums whose limbs exceed $2^{51}$ --- can denote the
\emph{same} field element. The representation has slack, and the
implementation \emph{exploits} it: the fast \code{add} from
Chapter~\ref{ch:rust} just adds limbs pairwise, letting values drift above
$2^{51}$, and nobody reduces until a cheaper moment. The commuting square
still closes because $\denote{\cdot}$ doesn't care how bloated the limbs are
--- positional value is positional value.
This is also exactly where the carry bugs of Chapter~\ref{ch:why} live: code
that is correct only while the drift stays within headroom, and wrong on the
rare inputs where it spills. In the verified development that danger becomes
a visible, machine-checked pair of clauses attached to every operation:
\begin{lstlisting}[language=Lean]
theorem add_spec (ha : Bnd54 a) (hb : Bnd54 b) :
∃ c, add a b = .ok c
∧ Bnd55 c -- (1) bounds: the envelope holds
∧ denote c = denote a + denote b -- (2) value: the meaning is right
\end{lstlisting}
Clause (2) is the commuting square. Clause (1) feeds the \emph{next}
operation's hypothesis --- correctness composes only because every theorem
hands the following one the envelope it requires. A chain of such specs is
the formal skeleton of ``this sequence of optimized operations computes the
formula we claim.''
\begin{aha}
The denotation idea is vastly older and bigger than cryptography. Compilers
prove ``optimized code means the same as naive code''; databases prove
``this query plan means the same query''; hardware verifies ``this pipelined
circuit means this instruction set.'' The pattern --- map both sides into a
mathematical meaning-space and prove the square commutes --- is called
\emph{denotational semantics}, and you have now used it for real. It is the
single most transferable idea in this book.
\end{aha}
\section{Multiplication: where the bridge earns its keep}
Addition's square closes in an afternoon. Multiplication is the boss fight,
and seeing \emph{why} teaches you what verified arithmetic is really like.
Schoolbook multiplication of two 5-limb numbers produces nine columns of
partial products $\sum_{i+j=k} a_i b_j$; each column then owes a
\emph{carry} to the next; and columns $k \ge 5$ --- weights $2^{255}$ and up
--- must be folded back using the Chapter~\ref{ch:modular} identity
$2^{255} \equiv 19$. The implementation interleaves all three concerns for
speed. The proof must un-interleave them:
\[
\denote{\mathtt{mul}(a,b)}
\;\overset{?}{=}\;
\Big(\textstyle\sum_{k=0}^{8} 2^{51k} \sum_{i+j=k} a_i b_j \Big) \bmod p
\;\overset{?}{=}\; \denote{a} \cdot \denote{b} .
\]
The right equality is algebra --- \lean{ring} territory. The left is a walk
through the extracted code: every intermediate \lean{U128} product bounded
(no overflow --- the $2^{54}$ headroom at work), every carry accounted,
every $\times 19$ fold placed. In the companion projects this is a long
\lean{calc}-and-\lean{have} museum: dozens of small steps, each dispatched
by \lean{omega} or a bound lemma, composed into one commuting square.
One more representational dialect, because you will meet it in the Pasta
repos: \textbf{Montgomery form} stores $x$ as $x \cdot R \bmod p$ (with
$R = 2^{256}$) because it makes reduction after multiplication cheap. The
bridge absorbs the twist without complaint --- define
$\denote{a}_{\mathrm{M}} = (\text{positional value of } a) \cdot R^{-1}$
and the same commuting squares govern everything. Denotation is a
\emph{policy about meaning}, and it bends to fit the representation, not
the other way around.
\begin{pitfall}
When a denotation proof refuses to close, the failure is information ---
read it like a detective, in order: (1) Is the \emph{bound} hypothesis
strong enough for the intermediate products? (Count bits, on paper.)
(2) Is the \emph{denotation} right for this representation --- radix, limb
count, Montgomery factor? (3) Only then suspect the code. In the companion
projects this checklist ran hundreds of times; its order reflects the actual
base rates of what was wrong.
\end{pitfall}
\begin{tryit}
Open \code{exercises/Ch09.lean}. It builds a miniature of the whole story
you can hold in your head: a \emph{2-limb, radix-4} representation of
$\Zmod{15}$ (limbs are values $0$--$3$, denotation $a_0 + 4a_1$, and
$16 \equiv 1$ makes the fold trivial). You will write \lean{denote}, prove
the commuting square for the provided \lean{add} with carry, then for
\lean{mul} with its fold --- every conceptual ingredient of the dalek proof,
at a scale where \lean{decide} can double-check your work.
\end{tryit}
\section*{Exercises}
\exercise{Compute by hand the denotation of the limb arrays $(19,0,0,0,0)$
and $(0,0,0,0,2^{51})$ in the radix-51 system, reducing mod $p = 2^{255}-19$.
Conclude that $\denote{\cdot}$ is not injective by exhibiting the collision.}
\exercise{In the mini-system of the Try It box, find two distinct limb pairs
denoting the same element of $\Zmod{15}$, and check that the provided
\lean{add} treats them interchangeably \emph{as far as denotation goes} ---
compute both sides.}
\exercise{Sketch the multiplication column sums $\sum_{i+j=k} a_i b_j$ for
the 2-limb system and carry out the fold $16 \equiv 1$ by hand for
$a = (3,2)$, $b = (1,3)$. Check against direct computation in $\Zmod{15}$.}
\exercise{(Paper, challenge) For the radix-51 system with limbs bounded by
$2^{54}$: bound one column $\sum_{i+j=4} a_i b_j$ of partial products and
confirm it fits a \lean{U128}. How much headroom remains? This number ---
not elegance --- is why the invariant chose $2^{54}$.}
\begin{checkpoint}
You should now be able to: write the radix-51 denotation from memory; draw
the commuting square and label which side owns bounds and \lean{Result}s;
explain why many-to-one representation is both the performance trick and
the bug habitat; and recognize the two-clause shape (bounds propagation +
value equation) as the universal skeleton of implementation-correctness
theorems.
\end{checkpoint}