Repository navigation
How relays work
Chirp's servers are standalone. Two of them never share state, so they don't group into a cluster or a federation. A Chirp is never a node that relays for other people either. This page explains why, the two ways two Chirps meet today, and what is still missing before a public service.
The authoritative texts are decision 5 in docs/product-model.md and §4.5–§4.8 of HANDOFF.md. Running a server is covered in docs/relay.md.
cmd/chirp-relay is the one server Chirp ships:
- It gives each running Chirp a code and a share link.
- It matches a typed code, or an opened link, with the Chirp that owns it, and throttles guesses per client address.
- On the trusted path below, it also carries the consent request and the WebRTC signalling (offers, answers, ICE candidates).
- On the Nostr path, it only looks up a code and returns how to reach its owner.
In both cases, the session itself (screen, input, clipboard, files) does not pass through it. The session travels over WebRTC between the two Chirps: directly when the network allows it, through a TURN server when it does not (docs/stun-turn.md).
- All state is in memory, in one process. Codes, links and sessions live inside that process. A second server knows nothing about them.
-
Both ends must use the same server. A code issued by server A doesn't exist on server B. Chirp is pointed at one server with
-relayorCHIRP_RELAY. - No replicas behind a load balancer. Two instances would not share state, so a code redeemed on the other instance would not be found. One small instance serves many users, because signalling is a few kilobytes per session.
- A restart forgets everything. Each running Chirp registers again on its own and shows a new code. Links shared before the restart stop working.
The first idea was that every running Chirp would also be a node, relaying for others. Decision 5 dropped it on purpose. remotekit already carries signalling over public Nostr relays, encrypted end to end. That makes an existing, decentralized network a carrier that sees nothing, so Chirp does not have to invent and run a node network of its own.
There is also a practical reason the binary can't be that node: a Chirp runs only while someone needs help, on ordinary desktops behind NATs. It is the opposite of an always-on, reachable server.
So the decentralized part is the Nostr relay network. It is not a federation of Chirp servers.
Without -nostr, the code has six digits (482-913), and chirp-relay carries everything: the code, the consent request and the signalling. It sees the whole code, so in principle it could impersonate either end. That is acceptable for a server its users run or trust themselves, and it is why no public server is offered on this path.
With a list of Nostr relays (wss://…), the server becomes blind (decision 5; barahn/chirp#43 and #21):
-
The code has 6 + 4 digits, read as
482-913-5071.- The first six are the routing part: the server sees them, and they lead to the owner.
- The last four are a secret the owner's Chirp makes. It never leaves the two people. A link carries its own four after a dot (
<token>.0042).
-
The owner listens on those Nostr relays under a key made for its registration, and tells the server how to reach it (
PUT /api/peers/{peer}/contact). -
The helper looks the routing part up (
POST /api/lookup) and gets back only the owner's key and relays. It then reaches the owner directly over Nostr. - Before anyone is asked to consent, the two Chirps run a PAKE (CPace) over the four secret digits. Each end comes out with the other's key, and the session is signed with those keys. A wrong secret fails visibly at both ends. A key the lookup returns that is not the owner's fails the PAKE, so even the lookup is not trusted.
- Nostr relays see only gift-wrapped, encrypted messages (NIP-59, NIP-44): that some key received something, and when and how much. Input, clipboard and files stay on WebRTC data channels.
- Guessing is bounded: every lookup spends a code, metered per address by the server. Each secret serves one PAKE. A link is given up after 3 failed or abandoned PAKEs.
-
The paths coexist: an owner on Nostr can still be helped through the trusted relay by a Chirp without
-nostr, which uses the code's six routing digits. The other way round does not work: an owner without Nostr shows six digits, and a helper on Nostr cannot use them. Its Chirp says the code is incomplete, before spending anything.
-
A public server. Chirp does not offer one yet. For now, people run their own
chirp-relay, or one they trust. -
A default list of Nostr relays.
-nostrmust be given explicitly. - Known limits of the Nostr path: relays are not redialled (when every relay drops, a session in progress ends); relays that require NIP-42 auth can't be used; a request is open for 2 minutes. HANDOFF.md §4.7 and §4.8 track these.
Self-hosting keeps working with no restrictions:
-
Trusted path: run your own
chirp-relay(docs/relay.md), and point both Chirps at it. -
Blind path: the same
chirp-relayserves as the lookup. Use any Nostr relays, or run your own.
| Trusted relay (default) | Over Nostr (-nostr) |
|
|---|---|---|
| Code | 6 digits | 6 + 4 digits |
| What the server sees | The whole code, the consent request and the signalling | The routing part only |
| Who carries the signalling | chirp-relay |
Nostr relays, encrypted |
| Ends bound by the PAKE | No | Yes |
| Can the server impersonate an end | In principle, yes | No |
| Shares state with other servers | No | No. The decentralization comes from Nostr |
| Fit for a public service | No | That's the goal. Still missing: the public server and a default relay list |