Skip to content

Noise sessions on links off the cable (ADR-0015) - #144

Merged
longzhi merged 5 commits into
mainfrom
claude/noise-sessions
Oct 13, 2026
Merged

longzhi merged 5 commits into
mainfrom
claude/noise-sessions

Conversation

@longzhi

@longzhi longzhi commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Second step of #138, implementing ADR-0015 from #141 (merge that first).

What changes

  • Firmware (firmware-rs/core/src/noise.rs, clatter, no_std): the box answers Noise_IK_25519_ChaChaPoly_SHA256 handshakes keyed by the pairing keys (Ed25519 → X25519, no re-pairing). LAN opens with link.noise instead of link.challenge; over the Relay the box sends link.noise as data whenever a computer links and accepts a session only from the computer the Relay linked. Every line after the handshake is sealed; nothing in the clear is acted on off the cable. The board seals output line by line and drops a line it has no room for before sealing, so it never costs the other end a nonce. The box prints LINK NOISE before BOX KEY.
  • Daemon (daemon/src/noise_link.rs, snow): initiator side. LAN runs the session behind a duplex stream (session loop unchanged); the Relay bridge holds lines until the session is up, sends device.hello first, and re-handshakes on every link.noise.
  • No downgrade: once the daemon has seen LINK NOISE on the cable or finished a handshake, it records the box (box-noise) and never links to it in the clear again; only the cable can make it forget (older firmware flashed).
  • Old firmware still links in the clear with a new daemon.
  • The Relay is unchanged: data was already forwarded unread.
  • Docs: docs/protocol.md (local network link, LINK NOISE), docs/relay-protocol.md (the session inside the Relay), what the Relay sees in README.md / README.zh-CN.md.

Release note for later

The firmware release that ships this must set min_app to the App release that ships this daemon (its front matter in docs/releases/), so older Apps aren't offered firmware that refuses them off the cable. No UI change, so nothing for the Mac or Linux apps.

Checked

  • cargo test --workspace: all pass; daemon tests run snow against the firmware's own clatter code (handshake, both directions, replay, busy inside the session, unpaired, downgrade refused on LAN and Relay).
  • cargo clippy --workspace --all-targets: no new warnings.
  • tools/build-firmware.sh: app image +32 KB (2,783,360 → 2,815,808 bytes).
  • End to end with the simulator and an isolated daemon (temp HOME): paired over the pty, then linked over the local network (hello round trip, box-noise learned from the handshake) and through relay-staging (sealed lines both ways).

On a box

With dragon's go-ahead: App quit, box flashed with this firmware, this branch's daemon run off the cable (serial pinned elsewhere).

  • Local network: links through the Noise session, hello round trip, `box-noise` learned from the handshake. A plaintext `device.hello` on a fresh connection gets `link.error bad_message`. Three screenshots in a row, 3.4 s each.
  • relay-staging: `link.noise` in the clear, then only ciphertext; two screenshots, 6-10 s.
  • The box test found two bugs, fixed in b90c1bd: the daemon's sealed-line reader wasn't cancel-safe inside `select!` (a heartbeat mid-line dropped half a line and ended the session), and the box checked room for all lines of one write before sending any. Covered by a new test for the reader.
  • Merged main (firmware: on the Relay's unlinked, end only the computer's link #146): the Relay's `unlinked` now ends the computer's link and its session, nothing more.
  • The box was handed to the firmware: on the Relay's unlinked, end only the computer's link #146 session, which flashes main.

🤖 Generated with Claude Code

longzhi and others added 3 commits October 13, 2026 17:13
The box answers Noise_IK_25519_ChaChaPoly_SHA256 handshakes keyed by
the pairing keys (Ed25519 converted to X25519, so no re-pairing), with
clatter in no_std. Over the local network it opens with link.noise in
place of link.challenge; over the Relay it sends link.noise as data
whenever a computer links, and accepts a session only from the computer
the Relay linked. Every line after the handshake is sealed both ways;
a line that doesn't open ends the session. Nothing in the clear is
acted on off the cable any more.

The board seals the box's output line by line (SealedLines), and drops
a line it has no room for before sealing it, so a dropped line never
costs the other end a nonce. The box prints LINK NOISE before BOX KEY.

The app image grows by 32 KB (2,783,360 -> 2,815,808 bytes).

Refs #138

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The daemon answers the box's link.noise with the initiator side of
Noise_IK_25519_ChaChaPoly_SHA256 (snow), its Ed25519 pairing key
converted to X25519. Over the local network the session runs behind a
duplex stream, so the serial worker's session loop is unchanged; over
the Relay the bridge holds outgoing lines until the session is up, sends
device.hello first, and starts a new session whenever the box sends
link.noise again. A sealed line that doesn't open ends the link.

Firmware without Noise still links in the clear, unless the box has
spoken Noise before: the daemon records that (box-noise, next to
box-key) from LINK NOISE on the cable or a finished handshake, and from
then on refuses link.challenge and believes nothing in the clear from
that box. Only the cable can make it forget.

Tests run the daemon's snow against the firmware's own clatter code.
Docs: the local network handshake, the session inside the Relay, and
what the Relay can see, in both READMEs.

Refs #138

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bridge sends device.hello first after a handshake; a hello that was
waiting for the session is dropped rather than sent a second time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 13, 2026 09:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

longzhi and others added 2 commits October 13, 2026 17:35
The daemon's bridge read sealed lines inside select!, and the reader
cleared its buffer at the start of each call: whenever the daemon sent
something (a heartbeat every 5 s) while a long line was half read, that
half was dropped and the session ended. A screenshot over the local
network failed every time. The reader now keeps what it read across a
cancelled read, and the bridge clears the line only after a whole one.

On the box, every line of one write was checked against the
connection's free room before any of them was sent, so the second line
could still overrun the pipe and be cut. Each line is now checked,
sealed and sent in turn.

On the box: screenshots over the local network (3.4 s) and through
relay-staging (6-10 s) now come back whole, three and two in a row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	firmware-rs/core/src/relay_box.rs
@longzhi
longzhi merged commit 4d02d2d into main Oct 13, 2026
2 checks passed
@longzhi
longzhi deleted the claude/noise-sessions branch October 13, 2026 09:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants