Every interface phase is done. What is left is the wire — a shell on a node, a file transfer, a screen you can drive — and all four of those sit on one module of arithmetic that has not been written yet.
module src/crypto.rsabout 2,000 linesdependencies 0unlocks 4 phasesrisk lowest in the plan
Where we are
The console works. It just cannot talk to anything yet.
Phases 0 through 3 and phase 9 are landed: a native window on macOS and on Wayland, one immediate-mode toolkit, the museum interface with selection and per-node results, navigation, and the SD-card ledger with a real pty behind it.
What exists · 202 tests on macOS, 223 on Linux
Module
Does
Lines
State
surface.rs
the platform seam — Input, Surface, Offscreen
~200
done
ui.rs
immediate-mode widgets, tested with no compositor
~750
done
mac.rs
AppKit, no runtime Objective-C classes
~800
done
wl.rs
Wayland by hand, now with seat, pointer, keyboard
~1,500
done
keymap.rs
the keyboard, baked in — no libxkbcommon
~400
done
lab.rs
the museum interface, selection, results pane
~1,100
done
media.rs
the card ledger, the sequence checks
~700
done
pty.rs
a child on a real terminal — and phase 5's shell
~250
done
profile.rs
the crypto whitelist, versioned
~250
done
crypto.rs
the primitives
~2,000
phase 4
Six buttons in the verb bar are absent rather than greyed — Observe, Control, Terminal, Exchange, Send, Message — because their transports do not exist. The status line says 6 not built rather than offering something that would fail. Phase 4 is the first step toward that number going down.
The argument for doing it now
SSH and TLS want the same arithmetic
This is the structural fact that makes the rest of the plan tractable. Write the primitives once and the second protocol is a wire format rather than a cryptosystem.
Four of the six absent verbs land on this tree. rfb.rs — already written and tested — is the other path to Control, and the reason phase 8 can fail without taking the plan with it.
One primitive, two callers
Primitive
SSH-2
TLS 1.3
What it is for
SHA-256
✓
✓
exchange hash; transcript and HKDF
SHA-512
✓
✓
Ed25519's own internals
HMAC / HKDF
—
✓
key schedule
X25519
✓
✓
curve25519-sha256; the TLS key share
Ed25519
✓
✓
host certificates; CertificateVerify
ChaCha20-Poly1305
✓
✓
the default cipher on both
AES-256-GCM
✓
✓
because the far end may insist
base64 / DER
✓
✓
OpenSSH key files; X.509
The specification already exists
Nothing here is a judgement call
There is normally an argument at the start of a crypto module about which algorithms to support. That argument already happened, and it is committed.
src/profile.rs — written two phases early, at the request to lock the fleet down — is the whitelist, versioned, and it is already the thing that renders the node's sshd block. It names exactly one algorithm per slot. So phase 4 has no design space: the list of functions to write is a list somebody already reviewed, and a test called nothing_weak_is_on_any_list fails the build if it grows something with sha1, cbc, rc4, ssh-rsa, ecdsa or eighteen other tokens in it.
What that buys
No RSA. No NIST curves in key exchange. No CBC and no separate MAC, because both ciphers are AEAD. No TLS 1.2 record layer. No Diffie-Hellman group negotiation. Every one of those is a decoder that can be wrong and is not being written — the same argument rfb.rs made when it asked for Raw and CopyRect only, applied to a much larger surface.
Why it is the lowest risk, not the highest
Every line has a published answer
It is the largest single module in the plan, and it is the one least likely to go wrong — because unlike an RDP client negotiating with a server nobody here has run, every function in it has a known-correct output that somebody else wrote down.
Where the answers come from
Function
Checked against
X25519
RFC 7748 §5.2, plus Wycheproof's small-order and all-zero cases
Ed25519
RFC 8032 §7.1, plus Wycheproof's malleability set
ChaCha20-Poly1305
RFC 8439 §2.8.2
SHA-256 / SHA-512
FIPS 180-4
HMAC / HKDF
RFC 4231, RFC 5869
AES-256-GCM
the NIST known-answer tests
A module where every line is checked against a vector somebody else published is either right or obviously wrong. That is a different risk profile from the rest of the wire, where correctness means agreeing with a live server about an ambiguous specification. Budget the time for phase 4; do not budget the worry.
Trap one
X25519's degenerate outputs
A shared secret of all zeros means the peer sent a small-order point, and an implementation that does not check produces a session key an attacker chose. Wycheproof has the exact inputs; the check is two lines and is the difference between key agreement and theatre.
Trap two
Ed25519's canonical S
Without a bounds check on the signature's second half, a valid signature can be transformed into a second, different, also-valid signature for the same message. For a host certificate that is a live malleability bug, and it is one line.
The discipline
Constant time, or it is decoration
A reviewer should be able to read crypto.rs looking for exactly two things: a branch that depends on a secret, and a table index that depends on a secret. Neither may exist. Concretely —
Field arithmetic over 2²⁵⁵−19 in 5×51-bit limbs, with no conditional reduction. The carry chain runs the same way every time regardless of the value.
Authentication tags compared by OR-accumulating a difference across all sixteen bytes, never by an early-returning loop. A comparison that stops at the first wrong byte tells an attacker how many bytes were right.
AES is bitsliced, not tabulated. A textbook T-table AES is a cache-timing oracle. The slower version is the only one worth writing.
Which is why ChaCha20-Poly1305 is the default
It is constant time by construction, and it is fast in software on a core with no AES instructions — and a Raspberry Pi Zero 2 has no AES instructions. AES-256-GCM is implemented only because an IronRDP server very likely insists on it for TLS. The cipher the console actually prefers is the one the hardware is good at.
The deliverable
What "done" means for phase 4
It ships with nothing visible
No new pixels. No new button. The verb bar still says 6 not built, because the transports that use these primitives are phases 5 through 8. This is the one phase whose deliverable is a test report, and saying so in advance is better than delivering one and calling it progress.
Acceptance
Every published vector in the table above passes, including the Wycheproof edge cases — not only the happy-path RFC examples.
cargo test stays green on macOS and under weston on Linux, and the module cross-compiles to aarch64-unknown-linux-musl.
[dependencies] is still empty. No #[link] is added — this is pure arithmetic and needs no system library at all, which makes it the first new module since ui.rs with no unsafe in it.
A measured throughput number for ChaCha20-Poly1305 on saskatchewan, because "fast enough on a Zero 2" is a claim this plan keeps making and has not yet checked.
Then the number starts falling
Phase 5 is ssh.rs, and it turns 6 not built into 5 — a real shell on a node, as the login user, with no password asked, because copal-prep.sh already installed the fleet-human principal that makes it possible. Phase 6 takes it to 3. The interface is finished; what happens now is that its buttons start appearing.
Stated rather than glossed
The risk that does not go away
R3 · hand-written cryptography
Test vectors and narrowness reduce the surface. They do not eliminate the risk, and this is not a reviewed library. The argument for doing it anyway is the same argument the whole repository rests on: a fleet node is on a LAN that invariant 6 says never reaches the internet, a dependency is a crate fetch that fails on the one machine this is for, and the gate is the fleet's certificate authority rather than this code.
A narrow list of hand-written primitives is a better bet than a wide one. It is not the same bet as rustls, and nothing in this brief should be read as claiming otherwise.