End-to-end encrypted VoIP with text chat. Rust core, native macOS and cross-platform desktop clients, QUIC transport, MLS group keying.
Pre-alpha / experimental. APIs, wire format, and on-disk schemas can change without notice. Do not use this for sensitive communication. See
docs/08_security_review.mdfor the in-tree security review, including open findings.
Most always-on group voice tools force a tradeoff:
- Mumble has the right shape — persistent channels, push-to-talk, low-latency, self-hosted — but predates modern end-to-end encryption and offers none of it. The server sees plaintext audio.
- Discord brought serious E2EE to consumer voice with DAVE — Aura uses the same media-encryption construction. The differentiator is deployment: Discord is closed-source and centralized; Aura is built to be self-hosted by the community that runs it.
- Signal / WhatsApp have strong E2EE but are built around address-book messaging, not always-on community voice channels.
- Matrix / Element Call are general-purpose and powerful, but heavy and not optimized for low-latency hobbyist-server voice.
Aura is the gap in the middle: a small, self-hostable, Mumble-shaped voice + text app where the server is a zero-trust relay that never sees plaintext, and identity is cryptographic rather than account-based.
| Capability | macOS client | Desktop (.NET) client |
|---|---|---|
| Voice (E2EE via DAVE / MLS group keys) | ✅ | ✅ |
| Text chat (E2EE) | ✅ | ✅ |
| Push-to-talk | ✅ global hotkey | 🟡 in-window button only |
| Voice activity detection | ✅ | ✅ |
| Per-user local volume + mute | ✅ persisted across reconnects | 🟡 not persisted |
| Audio preprocessing toggles (RNNoise / AEC / NS / AGC) | ❌ RNNoise hardcoded on | ✅ user-toggleable |
| Settings persistence | ✅ | ❌ lost on app restart |
| Auto-reconnect with backoff | ✅ | 🟡 |
| Master / output volume slider | ❌ | ❌ |
| Recording | ❌ | ❌ |
Server: QUIC + TLS 1.3 transport, Ed25519 TOFU authentication, SQLite persistence, channel hierarchy, admin verification with three modes, opaque audio relay.
For the long-form roadmap and milestones, see docs/ROADMAP.md.
- All client ↔ server communication runs over a single QUIC connection with TLS 1.3.
- Audio frames travel as unreliable QUIC datagrams for low jitter; control, text, and MLS handshake messages travel on reliable streams.
- The server is stateless w.r.t. payloads — it routes opaque ciphertext between session IDs based on channel membership.
- Every client generates an Ed25519 keypair locally on first run. The public key is the user's stable identity.
- Display names are claimed TOFU-style (Trust On First Use): the first key to claim a name owns it permanently. Subsequent connections must sign a server-issued challenge with that key.
- On macOS, the private key is stored in the Secure Enclave when available. On the desktop client it lives in a file under the user's profile directory.
- The server stores no user passwords. Optional
password = ...inserver.tomladds a coarse access-control gate independent of identity.
- Each channel is backed by an MLS group (RFC 9420) using ciphersuite
MLS_128_DHKEMP256_AES128GCM_SHA256_P256. - The first client in a channel becomes the founder and owns adds/removes; the server is the MLS Delivery Service and never decrypts.
- On membership changes, an MLS commit moves the group to a new epoch; clients export new per-sender secrets.
- Aura adopts the DAVE AEAD construction (XChaCha20-Poly1305 with zero-padding commitments), originally specified by Discord, on top of MLS-derived keys.
- A per-sender symmetric key is derived from the MLS group secret via HKDF, scoped by the sender's session ID, so keys are never shared between senders.
- Both Opus audio frames and text messages use the same DAVE construction with distinct labels.
- Notes on Aura's deviations from the upstream DAVE spec live in
docs/05_dave_protocol_deviations.md. Full MLS notes indocs/MLS_SECURITY.md. Wire format indocs/protocol.md.
- Capture → VAD (optional, used in voice-activation mode) → RNNoise noise suppression → Opus encode → DAVE encrypt → QUIC datagram.
- Receive → DAVE decrypt → jitter buffer → Opus decode → mix → playback. The jitter buffer also drives the per-user "talking" indicator.
What Aura tries to protect, and what it explicitly does not. The full review is in docs/08_security_review.md.
| ✅ Confidentiality of voice + text payloads against the server operator and any network observer. | The server only sees ciphertext. Even with a full disk image of the server, past calls cannot be decrypted (forward secrecy via MLS commits, modulo open finding #4). |
| ✅ Integrity of media frames and text messages. | DAVE's AEAD prevents undetected tampering. |
| ✅ Authenticity of speakers within a channel. | Each frame is bound to a sender session whose Ed25519 identity was authenticated at connection time. |
| ✅ Forward secrecy across membership changes. | MLS commits rotate the group secret; departed members cannot decrypt future traffic. |
| ❌ Metadata. | The server (and any observer of it) can see who is connected, which channels they're in, when they speak, and packet sizes/timing. This is metadata Aura needs to route traffic. Use Tor or a similar layer if metadata resistance matters to you. |
| ❌ Endpoint compromise. | If a participant's machine is compromised, no protocol can save the conversation. |
| ❌ Identity verification beyond TOFU. | First-claim-wins for display names. Out-of-band fingerprint verification between users is not yet exposed in the UI. |
| ❌ Denial of service. | Aura mitigates several DoS vectors (per-IP handshake rate limiting, capped buffer growth) but cannot guarantee availability against a determined attacker. |
| ❌ Audited cryptographic implementation. | All primitives are widely-used libraries (ring, openmls, quinn, opus), but Aura itself has had no third-party audit. |
crates/
aura-protocol/ # shared binary wire format
aura-core/ # client engine (audio, MLS, codec) — exposed via UniFFI
aura-server/ # QUIC relay, auth, channel state, SQLite persistence
clients/
macos/ # SwiftUI client (Xcode project)
desktop/ # Avalonia (.NET 10) cross-platform client
docs/ # protocol notes, MLS notes, security review, roadmap
scripts/ # build helpers (macOS dylib build, etc.)
This walks through standing up a local server and connecting two clients on the same machine.
- Rust stable (
rustup install stable) protoconPATH(brew install protobufon macOS,apt install protobuf-compileron Debian/Ubuntu)- For macOS client: Xcode 15+
- For desktop client: .NET 10 SDK
# Generate a placeholder admin key (replace with your real client's pubkey later).
ADMIN_KEY=$(openssl rand -hex 32)
cat > server.toml <<EOF
[server]
bind_address = "0.0.0.0:8443"
max_connections = 100
log_level = "info"
[database]
path = "aura.db"
[verification]
mode = "none"
EOF
AURA_BOOTSTRAP_ADMIN_KEY=$ADMIN_KEY \
AURA_BOOTSTRAP_ADMIN_NAME="Admin" \
cargo run -p aura-serverThe server listens on 0.0.0.0:8443 and writes aura.db to the working directory.
In separate terminals (or one in Xcode and one via the desktop client), connect to localhost:8443 with two distinct display names. Each client generates its own keypair on first run; whichever one connects first claims its display name.
Join the same channel from both clients, hit your push-to-talk hotkey (macOS) or click the in-window PTT button (desktop), and you're encrypted end-to-end through your own server.
To wipe state and start over: stop the server, delete aura.db, and clear each client's local keystore (~/Library/Application Support/Aura/ on macOS, ~/.config/Aura/ on Linux, %APPDATA%\Aura\ on Windows).
server.toml, read from the working directory:
[server]
bind_address = "0.0.0.0:8443"
max_connections = 1000
log_level = "info"
# password = "optional-server-password"
[database]
path = "aura.db"
[verification]
# "none" | "optional" | "required"
mode = "optional"| Mode | Meaning |
|---|---|
none |
Anyone can connect and join voice channels. |
optional |
Admins may verify users; verification grants a cosmetic badge. |
required |
Only admin-verified users may join voice channels (text and presence still work). |
On first start with an empty database, the bootstrap admin is set via env vars:
export AURA_BOOTSTRAP_ADMIN_KEY=<64-char-hex-ed25519-public-key>
export AURA_BOOTSTRAP_ADMIN_NAME="AdminUser"
cargo run -p aura-serverThe bootstrap is consumed once — subsequent admin promotions go through the running server's admin API.
Open clients/macos/Aura.xcodeproj and hit Run. A pre-compile build phase invokes scripts/build_macos.sh, which builds aura-core as a universal dylib (arm64 + x86_64), regenerates the Swift bindings, and writes them into clients/macos/Aura/Generated/.
To build outside Xcode:
./scripts/build_macos.sh releaseSee clients/macos/Aura/RUNNING.md for the Xcode wiring details (search paths, embed-and-sign, bridging header).
cd clients/desktop
# macOS / Linux: build core for the host triple, then run.
cargo build --release -p aura-core
cp ../../target/release/libaura_core.dylib Generated/ # macOS
# cp ../../target/release/libaura_core.so Generated/ # Linux
./run.sh
# Windows (MSVC):
cargo build --release -p aura-core --target x86_64-pc-windows-msvc
copy ..\..\target\x86_64-pc-windows-msvc\release\aura_core.dll Generated\
dotnet run -c ReleaseGenerated/aura_core.cs is checked in and pinned to the core's UniFFI version. Regenerate with uniffi-bindgen-cs only if aura.udl changes.
# Workspace tests
cargo test --workspace
# Lints (clippy) — strict; CI fails on any warning
cargo clippy --workspace --all-targets -- -D warnings
# Formatting
cargo fmt --allFuzzing harnesses for the protocol parser live under crates/aura-protocol/fuzz/. See docs/FUZZING.md for how to run them with cargo fuzz.
CI runs on every push and PR. Required gates: cargo fmt --check, cargo test --workspace, cargo clippy ... -- -D warnings, Conventional Commits lint on PR commits, weekly cargo audit and cargo deny, and zizmor workflow-security analysis. The macOS Xcode build and the desktop .NET build run on their own platform-specific workflows.
Why QUIC instead of WebRTC? WebRTC is the right answer for browsers and for general-purpose A/V apps that need NAT traversal, simulcast, and SFUs. Aura is a native-app, single-server-per-community tool, and that lets us drop a lot of complexity. QUIC gives us TLS 1.3, multiplexed reliable streams plus unreliable datagrams, and 0-RTT resumption in one transport — without a SDP/ICE/DTLS-SRTP stack.
Why MLS instead of double-ratchet (Signal-style)? MLS is designed for groups: O(log N) rekeying on membership changes, a single shared epoch secret, and a delivery service that never sees plaintext. Pairwise double-ratchet sessions would scale poorly for an always-on N-party voice channel.
Why DAVE for media specifically? DAVE is a small, well-specified AEAD construction layered on top of MLS-exported keys, designed for streaming audio frames where you can't afford a full handshake per packet. The construction commits to the zero-padding so SFU-side framing tweaks are detectable. Aura uses DAVE's frame format (with some deviations) on top of MLS group keys.
Why TOFU instead of a real PKI / verified identities? Aura targets self-hosted servers run by hobbyists for friend groups and small communities, where a "bring your own CA" story is friction nobody wants. TOFU + visible Ed25519 fingerprints is the same trust model SSH has used successfully for decades. A future version may add out-of-band fingerprint verification UI.
Why Rust core + native UI shells instead of Electron / a single cross-platform UI?
The audio path needs to be tight — we want sub-frame jitter, low CPU at 48 kHz, and direct access to platform audio APIs (AVAudioEngine on macOS, WASAPI on Windows). A Rust aura-core exposed via UniFFI gives us one tested implementation of the codec / crypto / jitter buffer, with idiomatic Swift and C# bindings on top.
Is this production-ready? No. It is pre-alpha. Treat it as a research project until it has a third-party audit, a tagged release, and a track record.
- Bug reports and feature requests: open a GitHub issue (templates will guide you).
- Pull requests: see
CONTRIBUTING.mdfor the build/test loop, Conventional Commits + DCO sign-off requirements, and review expectations. - Security issues: please don't file a public issue — see
SECURITY.mdfor the private disclosure process. - Community guidelines:
CODE_OF_CONDUCT.md.
Parts of this codebase were written with AI assistance (Claude Code). By project convention, individual commits do not carry Co-Authored-By: Claude trailers — this single notice covers the repository.
Aura stands on a lot of other people's careful work:
- OpenMLS — the MLS implementation Aura uses.
- quinn — Rust QUIC.
- Opus and RNNoise — audio codec and noise suppression from Xiph.
- ring — primitives for X25519 / Ed25519 / AEAD.
- The DAVE protocol (Discord, CC BY-NC-SA 4.0) — adapted for Aura's media frames.
- UniFFI — Mozilla's binding generator that lets one Rust core back both clients.
- Avalonia — the cross-platform .NET UI framework powering the desktop client.
Copyright 2026 Jonathon Klobucar.
Licensed under the Apache License, Version 2.0. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

