An end-to-end encrypted channel between an app and the server it belongs to, through relays that cannot read it. Pair once with a QR code; after that the phone reaches its server from anywhere, and at home the traffic never leaves the house. Un canal cifrado de extremo a extremo entre una app y el servidor al que pertenece, a través de relays que no pueden leerlo. Se empareja una vez con un código QR; desde entonces el teléfono llega a su servidor desde cualquier sitio, y en casa el tráfico no sale de casa.
A server that runs in an office or at home usually has no public address, and opening a port or a VPN for every phone is work most people never do. A relay solves the address problem: both ends dial out to it, and it joins them. The hard part is trust. This project makes the relay a pipe that carries only ciphertext, so it does not matter who runs it: us, you, or a stranger.
Un servidor que funciona en una oficina o en casa casi nunca tiene una dirección pública, y abrir un puerto o una VPN para cada teléfono es un trabajo que casi nadie hace. Un relay resuelve el problema de la dirección: los dos extremos se conectan hacia él y él los une. Lo difícil es la confianza. Este proyecto hace del relay un tubo que solo transporta texto cifrado, así que da igual quién lo opere: nosotros, tú o un desconocido.
Only the phone and its server hold keys. Every connection gets fresh keys, so a key stolen later does not open what was recorded before.
Solo el teléfono y su servidor tienen llaves. Cada conexión usa llaves nuevas, así que una llave robada más tarde no abre lo que se grabó antes.
The server keeps an ordered list: a relay on its own network first, a public one after. The phone uses the first that answers and moves back up when it can.
El servidor tiene una lista ordenada: primero un relay en su propia red, después uno público. El teléfono usa el primero que responde y vuelve a subir en cuanto puede.
A published framework (Noise), a protocol document precise enough to reimplement, and test vectors that every library passes, in three languages.
Un marco publicado (Noise), un documento de protocolo preciso como para reimplementarlo y vectores de prueba que pasan todas las bibliotecas, en tres lenguajes.
The host is the end that is always on: the server. The guest is the end that comes and goes: the phone. Both open a WebSocket to a relay, the relay joins the two sockets of a channel, and passes frames from one to the other without opening them. The host keeps a channel on every relay of its list; the guest uses one at a time.
El host es el extremo que siempre está encendido: el servidor. El guest es el extremo que va y viene: el teléfono. Los dos abren un WebSocket hacia un relay; el relay une los dos sockets de un canal y pasa los frames de uno a otro sin abrirlos. El host mantiene un canal en cada relay de su lista; el guest usa uno a la vez.
Both ends dial out. Relays see ciphertext and timing, never content or keys. The dashed line is the fallback road. Los dos extremos se conectan hacia fuera. Los relays ven texto cifrado y tiempos, nunca contenido ni llaves. La línea discontinua es el camino de reserva.
| host | guest | |
|---|---|---|
| Creates channels on relaysCrea canales en los relays | yes, with a project keysí, con una llave de proyecto | no, joins with its tokenno, se une con su token |
| Static keyLlave estática | one, long-liveduna, de larga duración | new for each pairingnueva en cada emparejamiento |
| HandshakesHandshakes | answersresponde | startsinicia |
| Relay listLista de relays | decides and sends itla decide y la envía | receives it, only from the hostla recibe, solo del host |
| After the handshakeDespués del handshake | the same on both sides: messages of any size, either wayigual en los dos lados: mensajes de cualquier tamaño, en los dos sentidos | |
The roles are fixed per channel, and the application picks which of its ends plays host. Every library implements both. Los roles son fijos en cada canal, y la aplicación elige cuál de sus extremos hace de host. Cada biblioteca implementa los dos.
The server draws a one-time QR. It names the server's public key and carries a secret that lives five minutes.
El servidor genera un QR de un solo uso. Lleva la llave pública del servidor y un secreto que dura cinco minutos.
The phone makes its own key, proves it knows the secret, and shows a code.
El teléfono crea su propia llave, demuestra que conoce el secreto y muestra un código.
You type that code on the server's page. Only then does the phone get in. The server never shows a code, so a stranger who photographed the QR has nothing you would type.
Tecleas ese código en la página del servidor. Solo entonces entra el teléfono. El servidor nunca muestra un código, así que quien haya fotografiado el QR no tiene nada que tú vayas a teclear.
Every promise below holds for a relay that drops, delays, replays, reorders or alters frames. The full threat model is in SECURITY.md.
Cada promesa de abajo se cumple con un relay que tira, retrasa, repite, reordena o altera frames. El modelo de amenazas completo está en SECURITY.md.
| A relay canUn relay puede | A relay cannotUn relay no puede |
|---|---|
| see when each end is connected, how much it sends, and both IP addressesver cuándo está conectado cada extremo, cuánto envía y las dos direcciones IP | read any message, now or from a recordingleer ningún mensaje, ni en vivo ni de una grabación |
| drop or delay frames, or close a connectiontirar o retrasar frames, o cerrar una conexión | inject, replay or reorder a message without the ends noticinginyectar, repetir o reordenar un mensaje sin que los extremos lo noten |
| make a pairing fail, so you scan againhacer que un emparejamiento falle, y tengas que escanear otra vez | get a key of its choice accepted by your serverconseguir que tu servidor acepte una llave que elija él |
| see an opaque id for the quota it appliesver un id opaco para la cuota que aplica | learn who the customer issaber quién es el cliente |
The relay is a small Go program with no runtime dependencies. Since confidentiality never depends on the relay, running your own is about availability and metadata: on your own network, nobody else sees when your devices talk.
El relay es un programa pequeño en Go, sin dependencias en tiempo de ejecución. Como la confidencialidad nunca depende del relay, tener el tuyo es cuestión de disponibilidad y de metadatos: en tu propia red, nadie más ve cuándo hablan tus dispositivos.
# Go 1.27 or later
git clone https://github.com/syalia-srl/relay
cd relay/server
CGO_ENABLED=0 go build -o relay ./cmd/relay
RELAY_ADDR=127.0.0.1:8010 \
RELAY_STATE_PATH=/var/lib/relay/channels.json \
RELAY_KEYS_PATH=/etc/relay/keys.json \
./relay
Or as a container: docker build -t relay server/, with /data as a volume. It belongs behind a TLS proxy, because guests accept plain ws:// only to private addresses.O como contenedor: docker build -t relay server/, con /data como volumen. Va detrás de un proxy con TLS, porque los guests solo aceptan ws:// en claro hacia direcciones privadas.
{
"relay_id": "relay.example",
"keys": [
{ "id": "office", "sha256": "<hex of your key>",
"channels": 200, "creates_per_minute": 20,
"bytes_per_day": 2000000000 },
{ "id": "home", "sha256": "<hex of another key>",
"unlimited": true }
],
"issuers": {}
}
Each key has its own quota, so one product cannot starve another. A quota of 0 allows nothing; only "unlimited": true lifts the limits, for a relay you run for yourself. The file holds digests, never keys. A vendor can also issue signed relay tokens; list its public key under issuers.Cada llave tiene su propia cuota, así que un producto no puede dejar sin servicio a otro. Una cuota de 0 no permite nada; solo "unlimited": true quita los límites, para un relay que operas para ti. El archivo guarda digests, nunca llaves. Un proveedor también puede emitir tokens de relay firmados; pon su llave pública en issuers.
The protocol, the threat model, the relay's installation and one guide per library. Every page has a Markdown twin for agents and tools, and llms.txt indexes the lot. This page's twin is index.md.
El protocolo, el modelo de amenazas, la instalación del relay y una guía por biblioteca. Cada página tiene un gemelo en Markdown para agentes y herramientas, y llms.txt lo indexa todo. El gemelo de esta página es index.md.
One static binary, a systemd unit, Caddy with TLS, and a keys.json with project keys, quotas and issuers. Includes the checks that prove it works.
Un binario estático, una unidad de systemd, Caddy con TLS y un keys.json con llaves de proyecto, cuotas y emisores. Incluye las comprobaciones que demuestran que funciona.
The normative document: relay API v1, channel protocol v3, both Noise handshakes, frames, rekey, relay choice and control messages.
El documento normativo: API v1 del relay, protocolo de canal v3, los dos handshakes Noise, frames, rekey, elección de relay y mensajes de control.
The threat model: what the code promises, what a hostile relay, a local network or a photographed QR can do, and what the code does not do.
El modelo de amenazas: qué promete el código, qué puede hacer un relay hostil, una red local o un QR fotografiado, y qué no hace el código.
The host and guest of the protocol, with async calls, a file store at mode 0600, a native-key interface and a worked echo example.
El host y el guest del protocolo, con llamadas async, un almacén en archivo con modo 0600, una interfaz de llave nativa y un ejemplo de eco.
The same host and guest for Node and browsers, with crypto.subtle, no runtime dependencies, an Android Keystore key interface and an example.
El mismo host y guest para Node y navegadores, con crypto.subtle, sin dependencias en tiempo de ejecución, una interfaz de llave para Android Keystore y un ejemplo.
The module with a context.Context on every waiting call, the standard library's cryptography, a store interface and an example.
El módulo con un context.Context en cada llamada que espera, la criptografía de la biblioteca estándar, una interfaz de almacén y un ejemplo.
Each library implements both ends: pairing, the Noise handshakes, framing, control messages and relay failover. Each carries its own small Noise for the two patterns it needs, with no Noise dependency, and every host is tested against every guest through the relay. A guest can keep its private key in native code: the libraries only ask for Diffie-Hellman results.
Cada biblioteca implementa los dos extremos: emparejamiento, handshakes Noise, frames, mensajes de control y cambio de relay. Cada una lleva su propio Noise pequeño para los dos patrones que necesita, sin depender de ningún paquete de Noise, y cada host se prueba contra cada guest a través del relay. Un guest puede guardar su llave privada en código nativo: las bibliotecas solo piden resultados de Diffie-Hellman.
Found a weakness? Report it privately through the repository's Security tab. Building a product on it? Talk to us. ¿Encontraste una debilidad? Repórtala en privado desde la pestaña Security del repositorio. ¿Vas a construir un producto encima? Hablemos.