Project
Dialtone
A Linux-first realtime chat TUI in Go with application-layer encrypted channel messaging and optional WebRTC voice.
Backend + Realtime Systems
12-02-2026
Project
A Linux-first realtime chat TUI in Go with application-layer encrypted channel messaging and optional WebRTC voice.
Backend + Realtime Systems
12-02-2026
Highlights
Dialtone is a realtime chat project written in Go, built around a WebSocket server, a terminal client, and a separate voice daemon. The main goal is straightforward: keep everyday chat fast while reducing server visibility into message content. In practice, channel message bodies, sender display names, and channel names are encrypted client-side before upload, while the server stores and routes ciphertext plus the metadata required for delivery and authorization.
The project is structured as distinct binaries for server, client, and voice (dialtone-voiced), with a PostgreSQL backend for persistence and an API surface for auth, channels, encrypted message history, device public keys, and key envelopes. The client experience is terminal-first, using a Bubble Tea TUI, and local secret material is stored in an encrypted keystore.
The server API is organized around authentication, channels, ciphertext message exchange, device public key lookup, key-envelope storage/retrieval, and WebSocket events for chat and voice signaling. HTTP handles account, key, and history workflows, while WebSocket handles realtime fan-out and peer signaling.
Dialtone uses application-layer encryption for channel chat data. A random symmetric channel key is generated client-side and used to encrypt message bodies, sender display names, and channel names before upload (AES-256-GCM). The server stores and relays these fields as opaque ciphertext strings.
For multi-device access, channel keys are shared as per-device key envelopes. Each envelope is produced client-side by deriving a shared secret from X25519 ECDH + HKDF-SHA256 with the recipient device public key, then encrypting the channel key with AES-256-GCM. The server stores and returns envelopes but does not decrypt them.
Trusted-user directory profile display names are encrypted client-side with a separate symmetric directory key, which is also shared through per-device envelopes.
Usernames are intentionally sent in plaintext during registration/login so authentication can work, and are stored server-side as peppered HMAC-SHA256 hashes (not plaintext). Local secret material (device private key, channel keys, directory key) is encrypted at rest in the client keystore using scrypt-derived keys and AES-256-GCM.
The practical boundary is: content fields are client-encrypted, while operational metadata remains visible to the server (for example user IDs, channel IDs, timestamps, device public keys, and envelope routing fields). The current model assumes clients trust server-delivered device public keys (no out-of-band fingerprint verification layer yet).
Voice is implemented as a dedicated daemon process instead of being embedded directly inside the TUI. The client and daemon communicate over local IPC, and the daemon handles microphone capture, push-to-talk behavior, VAD-based speaking state, and WebRTC peer connections. Signaling is carried through the existing Dialtone WebSocket channel using explicit voice and WebRTC message types, while media transport uses WebRTC with STUN/TURN configuration.
Global push-to-talk is configurable at launch (--voice-ptt plus --voice-ptt-backend auto|portal|hotkey). On Linux Wayland, the portal backend uses XDG Desktop Portal GlobalShortcuts: Dialtone requests a shortcut binding, then maps portal Activated/Deactivated events to speaking state on key down/up. In auto mode on Wayland, portal is attempted first; if unavailable, the daemon falls back to VAD by default (or can allow hotkey fallback via DIALTONE_PTT_WAYLAND_HOTKEY_FALLBACK=1).
Operationally, this separation keeps the terminal UI responsive and makes the media path easier to evolve independently from the text chat layer.
Dialtone follows an admin-and-user model where the server operator manages infrastructure, environment secrets, invite generation, and TURN service for voice. Users run the client, register with an invite token, and then log in to chat. For voice sessions, TURN is a required part of real-world connectivity and is supported through project configuration and compose-based coturn setup.
At this stage, Linux is the supported platform for production use, with release artifacts and runtime assumptions focused on Linux hosts.
The project has been tested successfully in two practical setups on Linux (Arch): first on a local LAN, and then against a public server deployment. These runs validated registration/login, encrypted channel messaging, and voice signaling/connection behavior under real network conditions. The project includes unit and integration tests for core components.
This validation reflects implementation testing rather than a formal external cryptographic audit.