
Peer-to-Peer-, Ende-zu-Ende-verschlüsselter Chat. Kein Posteingang. Kein Konto zum Wiederherstellen. Niemand hört zu — nicht einmal wir.
Peer-to-Peer-Chat mit Ende-zu-Ende-Verschlüsselung.
Kein Posteingang. Kein Konto, das wiederhergestellt werden müsste. Niemand hört mit — nicht einmal wir.
Nachrichten werden direkt zwischen Peers über libp2p übertragen und mit den Signal-artigen Olm/Megolm-Protokollen verschlüsselt (über vodozemac), bevor sie dein Gerät verlassen. Der einzige beteiligte Server ist ein kleines Verzeichnis, das Peers dabei hilft, die aktuelle Adresse des jeweils anderen zu finden. Es sieht nie Nachrichteninhalte und kann mit einem einzigen Befehl gelöscht werden.
Siehe docs/THREAT_MODEL.md und
docs/SECURITY.md für Details dazu, wogegen tatsächlich
geschützt wird und wie.
Erster Start — Namen wählen; sonst ist nichts einzurichten.
Unterhaltungen — die Gruppenleiste, Kontaktliste und ein Ende-zu-Ende-verschlüsselter Chat-Bereich.
Einstellungen — Mikrofonempfindlichkeit, Push-to-Talk, Anmelden bei Start, Netzwerkerreichbarkeit.
crates/directory-server) bildet eine Benutzer-ID auf eine aktuelle
Netzwerkadresse ab und sonst nichts. Er ist strukturell nicht in der Lage,
Nachrichteninhalte zu lesen: Seine Cargo.toml hängt nicht einmal von den
Crates ab, die dafür nötig wären.Es gibt zwei Arten von Identitäten in dieser App, die bewusst getrennt gehalten werden:
identity::Identity). Deine öffentliche
„Benutzer-ID“ ist lediglich der Fingerabdruck dieses Schlüssels
(wire_proto::user_id_from_ed25519). Sie kann von keinem Server ausgestellt
oder widerrufen werden, weil kein Server an ihrer Erstellung beteiligt ist.PeerId), das nur für die Transportschicht verwendet wird. Es kann sich
bei Neustarts ändern, ohne deine Chat-Identität im Geringsten zu
beeinträchtigen; die beiden sind nur durch einen Presence-Datensatz
verbunden, den du selbst signierst.Jemanden zu finden und tatsächlich mit ihm zu sprechen, sind zwei verschiedene Schritte:``` ┌────────────────────────┐ │ directory server │ │ (axum + one SQLite │ │ file: users, │ │ presence, group │ │ rosters. Never │ │ message content.) │ └─────────┬───────────────┘ 1. "where is bob │ 2. "here's my current right now?" │ address" (signed, │ expires in minutes) ┌─────────┴───────────────┐ ▼ ▼ ┌───────┐ 3. direct libp2p ┌───────┐ │ alice │◄──── connection ────►│ bob │ └───────┘ (Noise + Olm/ └───────┘ Megolm encrypted)
1. Alice schlägt Bob im Verzeichnis über seine Benutzer-ID nach. Das liefert seine
öffentlichen Schlüssel und seine zuletzt bekannt gegebene Netzwerkadresse. Das ist der gesamte
Inhalt des Verzeichnisses: öffentliche Schlüssel, Anzeigenamen,
Gruppenmitgliedschaftslisten und kurzlebige Adressankündigungen
(`crates/directory-server`).
2. Alice verbindet sich direkt mit Bob über libp2p (QUIC oder TCP+Noise, mit Relay +
Hole-Punching für Peers hinter NATs; siehe `crates/net`). Das Verzeichnis ist
von hier an komplett aus dem Spiel.
3. Die eigentliche Nachricht wird mit **Olm** für einen 1:1-Chat oder
**Megolm** für eine Gruppe (`crates/crypto-session`), einem Schema im Double-Ratchet-Stil,
bei dem jede Nachricht ihren eigenen Schlüssel erhält, bevor sie jemals auf
die libp2p-Verbindung gelegt wird. Es gibt keine serverseitige Inbox: Wenn Bob offline ist,
wartet die Nachricht lokal und wird erneut gesendet, statt auf der Infrastruktur anderer
gespeichert zu werden.
Alles oben Genannte wird von `crates/core`'s `AppService` orchestriert, auf den
das Rust-Backend der Tauri-App (`apps/desktop/src-tauri`) tatsächlich zugreift;
die UI spricht nie direkt mit dem Netzwerk.
## Projektstruktur```
crates/
wire-proto shared signed-request types for the directory API
identity vodozemac identity, OS-keychain key management
storage local encrypted store (contacts, messages, groups)
net libp2p transport + directory HTTP client
crypto-session Olm (1:1) / Megolm (group) session management
core orchestrates the above into `AppService` / `ChatNode`
directory-server the one server component (axum + SQLite)
apps/desktop the Tauri + React app
scripts/ build + backend-deployment scripts (§2, §5)
Auf jeder Plattform benötigst du Rust und Node.js sowie eine plattformspezifische
Toolchain, die Tauri zum Erstellen eines nativen Fensters benötigt. storage und directory-server
kompilieren SQLite außerdem gebündelt aus dem Quellcode, was einen einfachen C-Compiler benötigt (kein
OpenSSL oder andere native Kryptobibliothek wird irgendwo in diesem Projekt benötigt).
Für alle Plattformen gemeinsam:
rustup, nicht über den Paketmanager deines Betriebssystems)Installiere einen C-Compiler, pkg-config und die WebKitGTK/AppIndicator-Entwicklungspakete, gegen die Tauris Linux-Backend linkt.
Debian/Ubuntu:```sh
sudo apt update
sudo apt install libwebkit2gtk-4.1-dev build-essential curl wget file
libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev pkg-config
Fedora:```sh
sudo dnf install webkit2gtk4.1-devel openssl-devel curl wget file \
libappindicator-gtk3-devel librsvg2-devel pkgconf-pkg-config
sudo dnf group install "C Development Tools and Libraries"
Arch:```sh
sudo pacman -S --needed webkit2gtk-4.1 base-devel curl wget file openssl
appmenu-gtk-module libappindicator-gtk3 librsvg pkgconf
(Paketnamen ändern sich zwischen Tauri-Releases: Wenn ein Build fehlschlägt, weil eine
`.pc`-Datei fehlt, prüfe die
[aktuellen Tauri-Linux-Voraussetzungen](https://v2.tauri.app/start/prerequisites/)
für deine Distribution.)
</details>
<details>
<summary><strong>Windows</strong></summary>
1. Installiere die **Microsoft C++ Build Tools** (Visual Studio Installer →
Workload „Desktopentwicklung mit C++"), die sowohl für die native Tauri-Shell
als auch zum Kompilieren des gebündelten SQLite benötigt werden.
2. Installiere die **MSVC**-Rust-Toolchain: `rustup default stable-msvc`.
3. **WebView2**: bereits auf Windows 11 und den meisten aktuellen Windows-
10-Installationen vorhanden; falls nicht, wirst du vom Tauri-Build aufgefordert,
die Evergreen-Runtime zu installieren.
</details>
---
## 2. Erstellen
Vom Repository-Stammverzeichnis aus:```sh
# Rust workspace (backend crates + the directory server)
cargo build --workspace --release
# Frontend + the actual desktop app bundle (installer/.app/.exe)
cd apps/desktop
npm install
npm run tauri build
npm run tauri build erzeugt einen plattformspezifischen Installer unter
target/release/bundle/ im Repository-Root (dies ist ein Cargo-Workspace, also alle
Crates, einschließlich der Tauri-App, teilen sich ein gemeinsames target/-Verzeichnis auf oberster Ebene).
Cross-Compiling (z. B. das Erstellen des Windows-Installers unter macOS) ist nicht
eingerichtet: Baue auf jeder Zielplattform oder verwende den GitHub-Actions-Workflow von Tauri,
wenn du CI-gebauten Releases möchtest.
scripts/ enthält ein Build-Skript pro Plattform/Ausgabe, jedes unabhängig ausführbar und jedes mit dem Nachweis, tatsächlich ein funktionierendes Artefakt zu erzeugen:
| Skript | Erzeugt |
|---|---|
scripts/build-mac-dmg.sh | macOS .dmg-Installer |
scripts/build-mac-app.sh | Rohes .app-Bundle für macOS, kein Installer |
scripts/build-linux.sh | Linux .AppImage + .deb |
scripts/build-windows.ps1 | Windows .msi + .exe (NSIS) |
Jedes Skript ist lediglich ein Wrapper um npm run tauri build --bundles <...> mit den richtigen Flags und der Plattformprüfung; führe den Befehl direkt aus, wenn du eine andere Bundle-Kombination möchtest (npx tauri build --help aus apps/desktop).
scripts/release.sh vX.Y.Z erhöht die Version überall dort, wo sie benötigt wird, und taggt den Commit — siehe docs/RELEASING.md. Läuft auf macOS und Linux; committet oder pusht nicht.
Der Serverauswahl-Bildschirm (§3) zeigt immer drei Optionen: Seal (dein eigenes offizielles Netzwerk), Benutzerdefinierter Server und unten einen kleinen Link Lokaler Testserver. "Seal" ist deaktiviert (ausgegraut, mit "In diesem Build noch nicht eingerichtet"), bis du zur Build-Zeit eine URL einbaust:```sh SEAL_DEFAULT_DIRECTORY_URL=https://directory.example.com npm run tauri build
Sobald du deinen eigenen Server aufgesetzt hast (§5) und eine echte Domain darauf zeigt,
setze dies und baue neu: Jede Kopie, die du von da an verteilst, zeigt
"Seal" als echte, auswählbare Option mit dieser URL, ohne anderen Code
anzufassen. Lass es für normale/Dev-Builds ungesetzt: Es gibt keinen offiziellen
Server, der von diesem Repo gehostet wird, also bleibt "Seal" deaktiviert und die Leute fallen auf
einen benutzerdefinierten Server oder den lokalen zurück, anstatt dass die App stillschweigend auf
eine Platzhalter-Domain zeigt, die nicht wirklich etwas ausführt.
---
## 3. Im Entwicklungsmodus ausführen```sh
cd apps/desktop
npm install
npm run tauri dev
Dies startet den Vite-Entwicklungsserver, kompiliert das Rust-Backend im Debug-Modus und öffnet ein natives Fenster mit Hot-Reload im Frontend. Der erste Build kompiliert den gesamten Abhängigkeitsbaum und dauert einige Minuten; spätere Läufe sind schnell.
Beim ersten Start fragt Seal, welcher Verzeichnisserver verwendet werden soll, in dieser Reihenfolge:
127.0.0.1:47100/47101, Daten unter dem App-Datenverzeichnis deines Betriebssystems), gut zum Ausprobieren von Seal oder zum Testen von Instanzen auf einem Rechner, aber keine echte Bereitstellung. Wenn eine zweite Instanz feststellt, dass diese Ports bereits belegt sind, verwendet sie einfach den Server der ersten Instanz, anstatt einen weiteren zu starten – genau das ermöglicht es zwei Instanzen auf einem Rechner, sich zu finden. Diese Option wird automatisch ausgewählt, wenn „Seal“ nicht konfiguriert ist und du nichts anderes wählst.Die Auswahl wird gespeichert (server.json neben den anderen lokalen Daten der App) und bei jedem späteren Start stillschweigend wiederverwendet; ändern kannst du sie unter Einstellungen → Verzeichnisserver, was beim nächsten Start der App wirksam wird, anstatt zu versuchen, eine laufende Verbindung heiß zu tauschen. Für Skript-/Entwicklungszwecke überspringt eine Umgebungsvariable die Eingabeaufforderung vollständig:```sh
P2P_CHAT_DIRECTORY_URL=https://directory.example.com npm run tauri dev
### Zwei Instanzen lokal ausführen (zum tatsächlichen Testen von Nachrichten)
Jede Instanz benötigt eine eigene Identität. Seal unterstützt mehrere Konten
nativ (Einstellungen → Konten auf diesem Gerät), aber für zwei *getrennte
Prozesse* auf einer Maschine ist `P2P_CHAT_PROFILE` der schnellere Weg: Es
erstellt automatisch (beim ersten Mal) oder setzt automatisch fort (jedes weitere Mal) ein Konto
mit diesem Namen, nicht-interaktiv, und überspringt die Kontenauswahl vollständig:```sh
# terminal 1
P2P_CHAT_PROFILE=alice npm run tauri dev
# terminal 2
P2P_CHAT_PROFILE=bob npm run tauri dev
Die Serverwahl (server.json) und die Kontenliste (accounts.json) werden auf einer Maschine über alle Prozesse hinweg gemeinsam genutzt, nicht pro Profil. Die erste Instanz, die du jemals startest, wählt den Server aus, und jedes Profil danach (einschließlich bob hier) verwendet ihn stillschweigend weiter. Beide Fenster landen auf demselben eingebetteten Verzeichnisserver, sodass ihr euch gegenseitig per ID als Kontakte hinzufügen und zwischen ihnen Nachrichten austauschen könnt.
Der Dev-Server von Vite benötigt einen echten, festen Port, auf den das Webview von Tauri zeigen kann. Normalerweise bedeutet das, dass nur ein npm run tauri dev gleichzeitig laufen kann — die zweite Instanz würde feststellen, dass Port 1420 bereits belegt ist, und sofort fehlschlagen. npm run tauri ist eigentlich ein kleiner Wrapper (apps/desktop/scripts/tauri.mjs), der für jede Instanz nach der ersten den nächsten freien Port (1421, 1422, …) wählt und ihn automatisch durchreicht, sodass die beiden obigen Befehle in zwei Terminals einfach funktionieren; du musst nichts anders machen. Es ändert das Verhalten nur für dev — npm run tauri build und alles andere wird direkt an die echte CLI durchgereicht.
./scripts/run-two-mac-instances.sh # profiles: alice, bob ./scripts/run-two-mac-instances.sh carol dave
Dieselbe Idee wie oben, aber startet die tatsächlich erstellte App (die Ausgabe von `build-mac-app.sh` /
`build-mac-dmg.sh` oder eine installierte Kopie in `/Applications`) zweimal
mit unterschiedlichen `P2P_CHAT_PROFILE`s statt `npm run tauri dev`, näher
an dem, was ein echter Benutzer ausführt. Gibt die PIDs aus und wie man beide beendet.
### Debugging
- **Rust-Logs**: setze `RUST_LOG` vor dem Start, z. B.
`RUST_LOG=debug npm run tauri dev` (oder `RUST_LOG=p2p_core=debug,net=debug`
um es einzugrenzen). Die protokollierten Felder sind auf Metadaten beschränkt (Peer-/Gruppen-/Benutzer-
IDs, Fehlertypen); siehe [`docs/SECURITY.md`](https://github.com/emn4tor/seal/blob/HEAD/docs/SECURITY.md) für den Grund,
warum es sicher ist, sie ausführlich zu belassen.
- **Frontend**: Das Dev-Fenster ist eine echte Webview; Rechtsklick → Element
untersuchen (oder DevTools öffnen) funktioniert wie in einem normalen Browser.
- **Backend-Crates in Isolation**: Jede Crate hat ihre eigene Testsuite, die du
ausführen und iterieren kannst, ohne die UI überhaupt anzufassen; siehe §4.
- **Ein eigenständiger Verzeichnisserver** anstelle des eingebetteten: siehe §5.
---
## 4. Testen```sh
# everything
cargo test --workspace
# one crate, e.g. the full backend-to-backend flow a Tauri command would trigger
cargo test -p p2p-core --test app_service
# lint + format check (what CI runs)
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
# dependency vulnerability scan
cargo install cargo-audit --locked # once
cargo audit
# frontend type-check + build
cd apps/desktop && npm run build
Rückblick darauf, was das eigentlich ist, da man leicht zu viel hineininterpretiert: ein axum
Prozess, eine SQLite-Datei, drei Arten von Datensätzen (öffentliche Schlüssel, kurzlebige
Präsenzankündigungen, Gruppenlisten), alle Schreibvorgänge sind mit dem eigenen Identitätsschlüssel des Aufrufers
signiert. Er liegt niemals im Pfad einer Nachricht. Siehe
docs/THREAT_MODEL.md, warum das strukturell gilt,
nicht nur durch Richtlinien: Die Cargo.toml von directory-server hängt nicht
einmal von den Crates ab, die wissen, wie man Nachrichteninhalte liest.
sudo ./scripts/setup-backend.sh
Interaktiv, nur Linux + systemd (siehe Header des Skripts für das Warum). Es
fragt, welche Distro-Familie du verwendest (Debian/Ubuntu, Fedora/RHEL/Rocky/Alma,
Arch/Manjaro oder openSUSE, vorausgefüllt mit einer Vermutung aus `/etc/os-release`,
also meist nur eine Bestätigung mit einem Tastendruck) und installiert die Build-
Voraussetzungen dieser Distro mit einer eigenen Funktion pro Familie, bietet an, Rust
über `rustup` zu installieren, falls es fehlt, baut das Release-Binary, erstellt einen dedizierten
Systembenutzer, erzeugt ein Admin-Token, fragt, ob du
eine Domain mit automatischem HTTPS über [Caddy](https://caddyserver.com) konfigurieren möchtest
(installiert Caddy selbst, pro Distro, mit Rückgriff auf Cadys offizielles
statisches Binary, falls das Paket der Distro nicht verfügbar ist), oder nur
Loopback/Plain-HTTP binden willst, falls du es lieber selbst davor betreibst, schreibt
und aktiviert dann den systemd-Dienst. Kann gefahrlos erneut ausgeführt werden.
Alles unten ist das, was es tatsächlich tut, falls du es lieber von Hand machen
oder verstehen möchtest, bevor du es ausführst.
### macOS: ein schneller LAN-Testserver```sh
./scripts/run-mac-test-server.sh
Nicht für echtes Hosting gedacht: zum Testen der App auf zwei Geräten im selben
Netzwerk (z. B. dein Mac + ein anderes Gerät, oder zwei Personen im selben WLAN),
ohne Domain, TLS oder systemd einzurichten (das es unter macOS ohnehin nicht
gibt). Es erstellt das Release-Binary, generiert einen Admin-Token (der bei
späteren Läufen wiederverwendet wird), bindet die öffentliche API an alle
Schnittstellen und gibt die zu verwendende URL aus: die tatsächliche LAN-IP
deines Macs (via ipconfig getifaddr), nicht nur 127.0.0.1, damit andere
Geräte sie ebenfalls erreichen können. Der Admin-Port bleibt nur auf Loopback beschränkt. Läuft im Vordergrund; Ctrl-C stoppt ihn. Die Daten liegen unter
~/.seal-test-server.
DIRECTORY_DB_PATH=/var/lib/seal-directory/directory.sqlite3
DIRECTORY_PUBLIC_ADDR=0.0.0.0:8080
DIRECTORY_ADMIN_ADDR=127.0.0.1:8090
DIRECTORY_ADMIN_TOKEN=$(openssl rand -hex 32)
cargo run --release -p directory-server --bin directory-server
| Variable | Required | Meaning |
|---|---|---|
| `DIRECTORY_DB_PATH` | nein (Standard: `directory.sqlite3`, aktuelles Verzeichnis) | Wo die einzelne SQLite-Datei liegt. Das übergeordnete Verzeichnis muss existieren. |
| `DIRECTORY_PUBLIC_ADDR` | nein (Standard: `0.0.0.0:8080`) | Die Rendezvous-API, mit der Apps sprechen. Kann bedenkenlos öffentlich erreichbar sein. |
| `DIRECTORY_ADMIN_ADDR` | nein (Standard: `127.0.0.1:8090`) | Der Purge-Endpunkt. Halte ihn vom öffentlichen Internet fern; siehe unten. |
| `DIRECTORY_ADMIN_TOKEN` | **ja** | Bearer-Token für die Admin-API. Der Prozess startet ohne eines nicht. Erzeuge es mit `openssl rand -hex 32` oder Ähnlichem; verwende es nirgendwo sonst. |
Der Prozess protokolliert beim Start, an welche Adressen er sich gebunden hat, und warnt deutlich,
wenn `DIRECTORY_ADMIN_ADDR` nicht Loopback ist.
### Die App darauf ausrichten
Drei Möglichkeiten, in der Reihenfolge, in der man normalerweise zu ihnen greift:
1. **Erststart-Bildschirm**: „Custom server“ wählen und die URL eingeben. Siehe §3.
2. **Einstellungen → Directory-Server**: später ändern; wirkt beim nächsten
Neustart.
3. **`P2P_CHAT_DIRECTORY_URL`**, vor dem Start gesetzt: überspringt die Abfrage
komplett und überschreibt alles Gespeicherte, nützlich für Entwicklungs-/Skriptläufe: ```sh
P2P_CHAT_DIRECTORY_URL=https://directory.example.com npm run tauri dev
Wer einander finden möchte, muss auf dieselbe Verzeichnisinstanz zeigen; so findet man sich überhaupt erst gegenseitig.
[Service] Type=simple User=seal-directory Group=seal-directory Environment=DIRECTORY_DB_PATH=/var/lib/seal-directory/directory.sqlite3 Environment=DIRECTORY_PUBLIC_ADDR=127.0.0.1:8080 Environment=DIRECTORY_ADMIN_ADDR=127.0.0.1:8090 EnvironmentFile=/etc/seal-directory/admin-token.env ; DIRECTORY_ADMIN_TOKEN=... ExecStart=/usr/local/bin/directory-server Restart=on-failure
ProtectSystem=strict ProtectHome=true PrivateTmp=true NoNewPrivileges=true ReadWritePaths=/var/lib/seal-directory
[Install] WantedBy=multi-user.target
Hinweise:
- `DIRECTORY_PUBLIC_ADDR` ist hier absichtlich an **Loopback** gebunden; setzen Sie für TLS einen Reverse-Proxy davor (weiter unten), anstatt axum direkt dem Internet auszusetzen.
- Erstellen Sie zuerst das Systembenutzer/-gruppenkonto `seal-directory` und `/var/lib/seal-directory` (`useradd --system --no-create-home seal-directory && install -d -o seal-directory -g seal-directory /var/lib/seal-directory`), und kopieren Sie die gebaute `directory-server`-Binärdatei (aus `target/release/`) nach `/usr/local/bin/`.
- Legen Sie das Admin-Token in eine nur für Root lesbare `EnvironmentFile`, nicht direkt in die Unit-Datei (Unit-Dateien sind oft für alle Benutzer lesbar).
</details>
### TLS über einen Reverse-Proxy
<details>
<summary>Caddy-/nginx-Konfiguration anzeigen</summary>
[Caddy](https://caddyserver.com) liefert automatisches HTTPS mit dem geringsten Konfigurationsaufwand:```
# /etc/caddy/Caddyfile
directory.example.com {
reverse_proxy 127.0.0.1:8080
}
caddy run (oder systemctl enable --now caddy) übernimmt die Ausstellung/Erneuerung von Zertifikaten von selbst. Wenn Sie lieber nginx verwenden möchten, beenden Sie TLS dort und proxy_pass http://127.0.0.1:8080;, da die App aus Sicht des Proxys nur einfaches HTTP benötigt.
Was die Firewall betrifft: Nur der öffentliche Port muss von außen erreichbar sein (8080 in den obigen Beispielen, über den Proxy durch 443 abgesichert). Der Admin-Port sollte niemals von außen erreichbar sein; erreichen Sie ihn über SSH-Portweiterleitung (ssh -L 8090:127.0.0.1:8090 your-server), wenn Sie eine Bereinigung remote ausführen müssen.
cargo run --release -p directory-server --bin directory-admin --
--admin-url http://127.0.0.1:8090 --token "$DIRECTORY_ADMIN_TOKEN" purge
Dies löscht die SQLite-Datei und erstellt ein leeres Schema neu: keine `DELETE`-Anweisungen, kein teilweiser Zustand. Es kann gefahrlos ausgeführt werden, ohne vorher jemanden zu warnen: Jeder Datensatz darin ist ein Cache von Daten, die jeder Client bereits lokal besitzt (seine eigene Registrierung, Präsenz und alle Gruppen-Roster, in denen er Mitglied ist), sodass Clients sie innerhalb von Augenblicken nach ihrer nächsten Aktion wieder neu befüllen. Es gibt bewusst keine Backup-Strategie für diese Datenbank; siehe [`docs/SECURITY.md`](https://github.com/emn4tor/seal/blob/HEAD/docs/SECURITY.md) für den Grund, warum ein Backup den ganzen Zweck untergraben würde.
---
## 6. Verwendung der App
1. **Erster Start, erste Frage**: Welcher Verzeichnisserver verwendet werden soll (§3). Der Standardwert ist das, was in den Build fest eingebaut ist, den du ausführst (ein lokaler Testserver, sofern derjenige, der ihn gebaut hat, keinen offiziellen konfiguriert hat); wähle „Benutzerdefinierter Server", um auf einen zu zeigen, den du oder jemand, dem du vertraust, hostet.
2. **Wähle einen Anzeigenamen.** Dadurch wird auf deinem Gerät ein privates Schlüsselpaar erzeugt (nichts zum Merken und nichts Wiederherstellbares, falls es verloren geht: das ist beabsichtigt) und du wirst durch eine kurze In-App-Erklärung geführt, wie die Verschlüsselung tatsächlich funktioniert. Du kannst sie jederzeit über die Einstellungen erneut ansehen. Bei jedem späteren Start geht es ohne Aufforderung direkt weiter; dies geschieht nur einmal pro Konto. Füge weitere Konten (vollständig getrennte Identitäten) über Einstellungen → Konten auf diesem Gerät hinzu und wechsle ohne Neustart zwischen ihnen.
3. **Jemanden hinzufügen**: Klicke auf **+** neben „Direktnachrichten" und gib deren ID ein (zu finden in *deren* Einstellungen → Meine Identität). Es gibt bewusst kein Verzeichnis zum Durchsuchen; du verbindest dich auf dieselbe Weise, wie du eine Telefonnummer teilen würdest.
4. **Nachricht senden**: Wähle den Namen der Person aus der Liste und tippe. Die erste Nachricht an jemanden richtet automatisch eine verschlüsselte Sitzung ein.
5. **Eine Gruppe starten**: Klicke in der Symbolleiste auf **+**, gib ihr einen Namen und lade dann auf dieselbe Weise Leute per ID ein. Das Entfernen einer Person rotiert den Gruppenschlüssel, sodass diese Person nichts lesen kann, was danach gesendet wird.
6. **Alles löschen**: Einstellungen → Daten & Datenschutz. Dies ist sofortig, nur lokal und unumkehrbar: Es zerstört deine Schlüssel, Kontakte und den Verlauf auf *diesem Gerät* und hat keinerlei Auswirkung auf Personen, mit denen du gesprochen hast.