mirror of
https://github.com/saymrwulf/verifying-crypto-with-lean.git
synced 2026-09-04 20:03:41 +00:00
- 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>
183 lines
8.7 KiB
TeX
183 lines
8.7 KiB
TeX
\chapter{Modular Arithmetic: The Number System Cryptography Lives In}
|
|
\label{ch:modular}
|
|
|
|
\section{Clock arithmetic, taken seriously}
|
|
|
|
You already compute modulo twelve every day: four hours after ten o'clock is
|
|
two o'clock. Wrap-around arithmetic --- add, overflow the dial, keep the
|
|
remainder --- is the entire idea of \emph{modular arithmetic}. Cryptography's
|
|
only twist is the size of the clock: Ed25519's dial has
|
|
\[
|
|
p = 2^{255} - 19
|
|
\]
|
|
hours on it --- a 77-digit prime. Everything else --- the wrap-around, the
|
|
remainder-taking, the algebra --- is the twelve-hour clock you already know.
|
|
|
|
Formally, we write $a \equiv b \pmod{n}$ when $n$ divides $a - b$, and we
|
|
collect all integers with the same remainder into one object: the world
|
|
$\Zmod{n}$ has exactly $n$ elements, $\{0, 1, \dots, n-1\}$, with addition
|
|
and multiplication that wrap. In Lean/Mathlib this world is a first-class
|
|
type, and computation in it is exactly what you would hope:
|
|
|
|
\begin{lstlisting}[language=Lean]
|
|
#eval (7 + 8 : ZMod 12) -- 3 (fifteen o'clock is three o'clock)
|
|
#eval (5 * 9 : ZMod 12) -- 9
|
|
#eval (3 - 7 : ZMod 12) -- 8 (subtraction wraps -- no truncation!)
|
|
|
|
def p : Nat := 2^255 - 19
|
|
#eval (2^255 : ZMod p) -- 19 (of course: 2^255 = p + 19)
|
|
\end{lstlisting}
|
|
|
|
Note the third line with relief: unlike \lean{Nat}, subtraction in
|
|
\lean{ZMod n} is a total, well-behaved inverse of addition. Every element has
|
|
a negative. In algebra terms, $\Zmod{n}$ is a \emph{commutative ring} --- and
|
|
Lean knows it, so the \lean{ring} tactic from Chapter~\ref{ch:automation}
|
|
works there natively.
|
|
|
|
\section{Division: where primes enter}
|
|
|
|
Rings give us $+$, $-$, $\times$. Cryptography also needs $\div$ --- and
|
|
division is where the modulus stops being a size parameter and starts being a
|
|
design decision. Dividing by $a$ means multiplying by some $a^{-1}$ with
|
|
$a \cdot a^{-1} = 1$. Does such an inverse exist? On the twelve-hour clock,
|
|
try to invert $4$: the multiples of $4$ are $4, 8, 0, 4, 8, 0, \dots$ ---
|
|
the value $1$ never appears. $4$ has no inverse mod $12$, because $4$ and
|
|
$12$ share the factor~$4$.
|
|
|
|
\begin{bigidea}
|
|
In $\Zmod{n}$, the element $a$ is invertible exactly when
|
|
$\gcd(a, n) = 1$. So if $n = p$ is \textbf{prime}, \emph{every} nonzero
|
|
element is invertible: you can divide freely, and $\Zmod{p}$ earns the title
|
|
of \textbf{field}, written $\Fp$. Fields are the arithmetic paradise where
|
|
linear algebra, polynomial factoring, and elliptic-curve geometry all work.
|
|
This --- and only this --- is why cryptographic moduli are prime: primality
|
|
is the entry ticket to division.
|
|
\end{bigidea}
|
|
|
|
\begin{center}
|
|
\begin{tikzpicture}[scale=0.62,every node/.style={font=\small}]
|
|
% Z/12 clock: multiples of 4 stuck in a cycle
|
|
\begin{scope}
|
|
\draw[ink2,thick] (0,0) circle (2.2);
|
|
\foreach \i in {0,...,11} \node[color=ink] at ({90-\i*30}:1.8) {\i};
|
|
\foreach \s/\t in {0/4, 4/8, 8/0}
|
|
\draw[-{Stealth},accent,thick] ({90-\s*30}:2.55) arc ({90-\s*30}:{90-\t*30+8}:2.55);
|
|
\node[color=accent,align=center] at (0,-3.4) {$\Zmod{12}$: stepping by $4$\\ visits only $\{0,4,8\}$ --- never $1$};
|
|
\end{scope}
|
|
% Z/11 clock: multiples of 4 reach 1
|
|
\begin{scope}[xshift=9.5cm]
|
|
\draw[ink2,thick] (0,0) circle (2.2);
|
|
\foreach \i in {0,...,10} \node[color=ink] at ({90-\i*32.72}:1.8) {\i};
|
|
\foreach \s/\t in {0/4, 4/8, 8/1}
|
|
\draw[-{Stealth},proven,thick] ({90-\s*32.72}:2.55) arc ({90-\s*32.72}:{90-\t*32.72+8}:2.55);
|
|
\node[color=proven,align=center] at (0,-3.4) {$\Zmod{11}$: stepping by $4$\\ reaches $1$ in three steps: $4^{-1}=3$};
|
|
\end{scope}
|
|
\end{tikzpicture}
|
|
\end{center}
|
|
|
|
How do you \emph{find} $a^{-1}$ mod $p$? Two classical answers, both of which
|
|
appear in real Ed25519 code: the extended Euclidean algorithm, and Fermat's
|
|
little theorem, which says $a^{p-1} \equiv 1 \pmod p$ for $a \not\equiv 0$
|
|
--- hence $a^{p-2}$ \emph{is} the inverse. The dalek library computes
|
|
inverses as $a^{p-2}$ with a hand-crafted chain of $254$ squarings and $11$
|
|
multiplications; one of the proofs you will meet in Chapter~\ref{ch:field}
|
|
verifies precisely that this chain computes what Fermat promises.
|
|
|
|
\section{\texorpdfstring{Why $2^{255}-19$?}{Why 2**255-19?} A prime chosen for machines}
|
|
|
|
Any large prime makes a field. Why this one? Because arithmetic mod $p$ is
|
|
computed by \emph{machines with 64-bit words}, and $p = 2^{255}-19$ is shaped
|
|
for them:
|
|
|
|
\begin{itemize}[leftmargin=1.4em]
|
|
\item \textbf{Reduction is a multiply-by-19.} Numbers at or beyond $2^{255}$
|
|
overflow the dial; but since $2^{255} \equiv 19 \pmod p$, any overflow bit
|
|
re-enters the sum carrying a factor of just $19$. Reducing mod $p$ costs
|
|
one small multiplication --- no long division, ever.
|
|
\item \textbf{255 bits split evenly into five limbs of 51.} A 64-bit word
|
|
holding a 51-bit limb leaves 13 bits of headroom for carries to accumulate
|
|
lazily --- the delayed-carry style from Chapter~\ref{ch:why}, now by design
|
|
rather than accident. The bound $a_i < 2^{51+\varepsilon}$ will follow us
|
|
through Chapters~\ref{ch:denotation} and~\ref{ch:field}.
|
|
\item \textbf{It sits just below a power of two}, so 255-bit values fit in 32
|
|
bytes with one bit to spare --- and Ed25519 spends that spare bit storing a
|
|
point's sign. Engineering all the way down.
|
|
\end{itemize}
|
|
|
|
The Pasta curves verified in the companion projects (Pallas/Vesta, used in
|
|
zero-knowledge proof systems) choose their $\approx 2^{254}$ primes by a
|
|
different criterion --- friendliness to fast Fourier transforms --- and
|
|
represent elements in \emph{Montgomery form}, a representation trick we will
|
|
touch in Chapter~\ref{ch:denotation}. Different shapes, same theory: pick a
|
|
prime whose field your machine can love.
|
|
|
|
\section{Modular arithmetic in Lean, hands on}
|
|
|
|
Statements about $\Fp$ are ordinary Lean theorems, and the automation you
|
|
already own applies:
|
|
|
|
\begin{lstlisting}[language=Lean]
|
|
-- ring identities hold verbatim in ZMod n:
|
|
example (x y : ZMod 12) : (x + y)^2 = x^2 + 2*x*y + y^2 := by ring
|
|
|
|
-- small finite worlds: check every case
|
|
example : ∀ x : ZMod 12, 4 * x ≠ 1 := by decide
|
|
|
|
-- inverses exist in prime fields (Mathlib knows ZMod 11 is a field):
|
|
example (a : ZMod 11) (h : a ≠ 0) : a * a⁻¹ = 1 :=
|
|
ZMod.mul_inv_cancel_of_ne_zero h
|
|
\end{lstlisting}
|
|
|
|
What about the crown fact, $2^{255} \equiv 19$? For \emph{concrete numeric}
|
|
statements at 77-digit scale, tactic choice suddenly matters: \lean{decide}
|
|
would ask the kernel to grind case analysis it cannot afford, while
|
|
\lean{norm_num} and reflexivity-by-computation on efficient numerals handle
|
|
it comfortably. Verifying real cryptography is partly a \emph{performance
|
|
engineering} discipline --- Chapter~\ref{ch:prime} turns this observation
|
|
into a principle when the question becomes primality itself.
|
|
|
|
\begin{pitfall}
|
|
In \lean{ZMod n}, the numeral \lean{57896044618658...} and the numeral
|
|
\lean{19} can be \emph{the same element}. Equality of elements is not
|
|
equality of the numerals you typed. When a surprising \lean{rfl} succeeds ---
|
|
or two ``different'' constants turn out equal --- remember you are on the
|
|
clock face, not the number line. This is a feature: the entire verification
|
|
story of Chapter~\ref{ch:denotation} consists of choosing, deliberately, when
|
|
to view a pile of bytes as an integer and when as a clock position.
|
|
\end{pitfall}
|
|
|
|
\begin{tryit}
|
|
Open \code{exercises/Ch06.lean}. You will: compute your first inverses with
|
|
\lean{\#eval}; expand $(x+y)^3$ in \lean{ZMod 7} with one tactic; show $4$
|
|
has no inverse in \lean{ZMod 12} by \lean{decide}; and prove the reduction
|
|
identity $2^{255} = 19$ in \lean{ZMod p}.
|
|
\end{tryit}
|
|
|
|
\section*{Exercises}
|
|
|
|
\exercise{Compute by hand (yes, hand): $3^{-1}$ in $\Zmod{7}$, and check that
|
|
stepping by $3$ on a 7-hour clock visits every position. Then confirm both
|
|
with \lean{\#eval}.}
|
|
|
|
\exercise{Prove in Lean: \lean{∀ x : ZMod 12, 4 * x ≠ 1}, then change $12$
|
|
to $13$ and watch the same tactic refuse --- find the witness it is
|
|
implicitly pointing at.}
|
|
|
|
\exercise{Fermat's little theorem is \lean{ZMod.pow_card_sub_one_eq_one} in
|
|
Mathlib. Use it to prove that in \lean{ZMod 11}, \lean{a\textasciicircum{}9 * a = 1} whenever
|
|
\lean{a ≠ 0}.}
|
|
|
|
\exercise{(Paper) The Ed25519 code reduces a 256-bit value $x$ by writing
|
|
$x = x_{\mathrm{low}} + 2^{255}\, x_{\mathrm{high}}$ and returning
|
|
$x_{\mathrm{low}} + 19\, x_{\mathrm{high}}$. Prove informally that this
|
|
preserves the value mod $p$, and bound how much smaller the result is. You
|
|
have just re-derived the reduction step you will verify formally in
|
|
Chapter~\ref{ch:field}.}
|
|
|
|
\begin{checkpoint}
|
|
You should now be able to: compute in $\Zmod{n}$ and explain the notation;
|
|
state exactly when division works and why primality guarantees it; give two
|
|
independent reasons the constant $19$ appears throughout curve25519
|
|
codebases; and prove small modular facts in Lean with \lean{decide},
|
|
\lean{ring}, and a Mathlib lemma found by name.
|
|
\end{checkpoint}
|