FOR OPERATORS
Your machine, carrying other people’s ciphertext.
A relay is one Rust binary. It accepts sealed messages, keeps them briefly, and hands them to whoever holds the key to the box. Below is exactly what it will store, what it will cost you, and the five commands between a fresh machine and this:
The short way
There is an interactive installer. It builds the binary, asks the handful of questions the steps below spell out, writes a service file and preserves your relay-id when you re-run it after a pull.
It prints the same relay address and relay-id the banner above shows. Read the rest of this page if you want to know what it did — not because you have to do it by hand.
What your machine will be holding
Hosting other people’s traffic is a commitment you should be able to describe precisely. Here is the whole inventory — not a summary of it.
| What lands on the disk | How long it stays | Can you read it? |
|---|---|---|
| Sealed messages Ciphertext addressed to a drop-box. You hold the envelope, never a key to it. Until fetched, or the TTL sweep | Until fetched, or TTL | NO — ciphertext |
| Drop-box addresses Opaque 32-byte labels, blinded per relay and rotating. They do not resolve to a person, and a second operator’s copy of “the same” box looks different. With the message | With the message | NO — unlinkable |
| Prekey bundles Public keys clients publish so strangers can start a conversation. Public by design. Until republished, RAM only | Until republished | YES — but public |
| The node list Other relays’ addresses and ids. Infrastructure, not users. While running | While running | YES — public |
| Your own keypair The relay-id clients pin. Lose it and every client must be told a new one; leak it and someone can impersonate your relay. Permanent — back it up | Permanent | YES — protect it |
| Request logs There are none. The request path contains no logging statement, and a test in the repository fails if one appears. Nothing is written | — | NOTHING TO READ |
The same thing, by hand
What the installer does, step by step — each one showing what you type and what changes on the machine afterwards.
-
Build it
One crate, no services to install, no database.
# Rust 1.75+ and a C toolchain are the only build deps git clone https://github.com/bytesoffreedom/karst cd karst/impl cargo build --release -p relayYou now have target/release/relay, a single binary you can copy anywhere.
-
Start it once, locally
Run it before you expose anything. The last block it prints is the pair a client needs.
./target/release/relay karst-relay … listening on 0.0.0.0:9444 │ relay-id 9f3a1c…c7d1The relay-id is stable across restarts — it is derived from a keypair on disk, not from the address. Back that file up.
-
Tell it where it can be reached
A relay only advertises itself if it has a routable address to advertise. On loopback or
0.0.0.0it stays silent rather than publishing something nobody can dial.export KARST_RELAY_ADVERTISE=relay.example.org:9444 ./target/release/relayYour relay now appears in the node lists of relays it gossips with — and only ever with the address it declares about itself.
-
Join the network
Point it at one relay you already know. Discovery spreads from there; each new relay is dialed and asked directly before it is believed.
export KARST_RELAY_PEERS=other.example:9444@a41f…9b02 ./target/release/relayPeers are a starting point, not a trust list. Nothing a peer says about a third relay is stored until that relay says it itself.
-
Keep it up
Nothing here needs a scheduler, a cron job or a reverse proxy. A unit file is enough.
# /etc/systemd/system/karst-relay.service [Service] Environment=KARST_RELAY_ADVERTISE=relay.example.org:9444 ExecStart=/opt/karst/relay Restart=alwaysRestarts are cheap: queued mail is in memory by default, so a restart drops what had not been fetched yet. Clients resend; nobody is told a comforting lie about delivery.
Everything you can configure
The whole surface. There is no config file to learn.
| KARST_RELAY_ADVERTISE | The routable host:port to list yourself as. Skipped when it is
loopback or 0.0.0.0 — you are simply not discoverable then. |
| KARST_RELAY_PEERS | addr@relay-id, comma-separated. Seeds the node list clients learn
from. |
| KARST_RELAY_TLS_CERT KARST_RELAY_TLS_KEY |
PEM pair. Setting both serves the WebSocket-over-TLS carrier, so the relay looks like an ordinary HTTPS endpoint. Optional: Noise already encrypts everything underneath. |
| KARST_RELAY_QUOTA | Per-capability request and byte budget. The default is sized for chat; raise it only if you know why. |
What it costs you
Said before you start rather than discovered afterwards.
Bandwidth, mostly
Every message is padded to a constant size, so traffic scales with message count, not with what people write. A small VPS carries a lot of conversation.
You are hosting strangers
You cannot read any of it, and you cannot choose what it says. Decide whether you are willing to host content you will never see, in your jurisdiction, before you advertise.
Your posture is a claim
“This relay logs nothing” is true of the code and unprovable from outside. Clients are told to treat it as a claim. Run it in a way you would be comfortable defending.
Your infrastructure may log
The relay writes nothing, but a reverse proxy, conntrack or a provider’s netflow might. That is outside the binary and outside anyone’s ability to verify remotely.