Phase four of eleven · orrery · the brief

Two thousand lines with a published answer

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.rs about 2,000 lines dependencies 0 unlocks 4 phases risk 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
ModuleDoesLinesState
surface.rsthe platform seam — Input, Surface, Offscreen~200done
ui.rsimmediate-mode widgets, tested with no compositor~750done
mac.rsAppKit, no runtime Objective-C classes~800done
wl.rsWayland by hand, now with seat, pointer, keyboard~1,500done
keymap.rsthe keyboard, baked in — no libxkbcommon~400done
lab.rsthe museum interface, selection, results pane~1,100done
media.rsthe card ledger, the sequence checks~700done
pty.rsa child on a real terminal — and phase 5's shell~250done
profile.rsthe crypto whitelist, versioned~250done
crypto.rsthe primitives~2,000phase 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.

src/what sits on what
  crypto.rs   the primitives          ~2000   ← phase 4
       │
       ├── ssh.rs    transport, auth, channels   ~1400   phase 5
       │       └── sftp.rs   SFTP v3              ~600   phase 6
       │
       └── tls.rs    TLS 1.3, one suite          ~1100   phase 7
               └── rdp.rs    the RDP client      ~2200   phase 8

  Terminal ── ssh.rs        Exchange ── sftp.rs
  Send     ── sftp.rs       Control  ── rdp.rs / rfb.rs
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
PrimitiveSSH-2TLS 1.3What 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
FunctionChecked against
X25519RFC 7748 §5.2, plus Wycheproof's small-order and all-zero cases
Ed25519RFC 8032 §7.1, plus Wycheproof's malleability set
ChaCha20-Poly1305RFC 8439 §2.8.2
SHA-256 / SHA-512FIPS 180-4
HMAC / HKDFRFC 4231, RFC 5869
AES-256-GCMthe 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 —

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.