xPulse
🇩🇪 DE

Chat Identity – Concept

Status: CONCEPT · Erstellt April 2026 Übergeordnet: TOOL_chat_roadmap.md Verwandt: TOOL_chat_concept-device-sync.md, TOOL_chat_adr-003-localstorage-keys.md Verwandt: @xpulse/crypto


Grundsätze

Privacy First – keine Identitätsdaten auf dem Server, niemals. Datenverlust vermeiden – jede Entscheidung wird danach bewertet. Technische Nachvollziehbarkeit – schafft Vertrauen.


Warum ein eigenes Konzept?

Identität ist die Wurzel von allem in xPulse Chat – Profil, Nachrichten, Sync, Devices, Export. Ohne ein stabiles, klar definiertes Identitätsmodell können Daten verloren gehen, Peers sich nicht wiedererkennen, oder verschiedene User auf demselben Gerät sich gegenseitig stören.

Dieses Konzept legt fest:


Die zwei Identitäten

userId → identifiziert den USER – geräteübergreifend stabil
clientId → identifiziert das GERÄT – userübergreifend stabil

Ein User kann mehrere Geräte haben. Ein Gerät kann mehrere User haben. Beide Identitäten sind voneinander unabhängig.

Gerät (clientId: abc...)
├── User A (userId: xyz...)
└── User B (userId: def...)
User A (userId: xyz...)
├── iPhone (clientId: abc...)
└── MacBook (clientId: ghi...)

Keypair-Prinzip (wie SSH)

Sowohl userId als auch clientId basieren auf dem gleichen Prinzip: einem kryptografischen Schlüsselpaar – genau wie ein SSH-Key.

Was ist ein Keypair?

Private Key = dein geheimer Schlüssel
→ verlässt nie das Gerät
→ wird niemals übertragen
→ damit werden Nachrichten "unterschrieben"
Public Key = dein öffentliches Schloss
→ kann jedem gegeben werden
→ Peers kennen dich dadurch
→ daraus wird die ID abgeleitet

Wer deinen publicKey kennt, kann verifizieren dass eine Nachricht wirklich von dir kommt. Wer deinen privateKey nicht hat, kann sich nicht als du ausgeben.

Warum ist die ID dadurch reproduzierbar?

privateKey existiert → publicKey immer ableitbar → ID immer gleich ✅
privateKey verloren → neue ID – wie ein neuer SSH-Key ⚠️

Solange der privateKey sicher in localStorage liegt, ist die ID stabil – auf ewig, ohne Server, ohne Koordination.


clientId – Geräte-Identität

Generierung

Erster Start auf diesem Gerät →
Ed25519 Keypair generieren (Web Crypto API)
privateKey → xpulse_identity_client_private_key (localStorage, AES-GCM verschlüsselt)
publicKey → SHA-256(publicKey) = clientId
xpulse_identity_client_id (localStorage)
xpulse_identity_client_public_key (localStorage)

Stabilität

App startet →
xpulse_identity_client_id vorhanden? →
JA → laden, verwenden – FERTIG, nie neu generieren
NEIN → erster Start → Keypair generieren → speichern

Regel: clientId wird einmalig generiert und nie überschrieben. Logout, Login, User-Wechsel – clientId bleibt immer gleich. Sie gehört dem Gerät, nicht dem User.

Wiederherstellung nach Datenverlust

Der privateKey steckt im verschlüsselten Backup (Export). Nach localStorage clear:

Backup importieren → privateKey wiederhergestellt
→ clientId reproduzierbar → Peers kennen das Gerät noch ✅

Ohne Backup: neue clientId → einmaliges Re-Pairing mit allen Peers nötig. Das ist der einzig akzeptable Datenverlust-Fall – und er ist kommunizierbar:

"Backup sichern = Geräte-Identität sichern."


userId – User-Identität

Generierung

Erster Login / Registrierung →
Ed25519 Keypair generieren (Web Crypto API)
Eingaben: login + password + timestamp + UUID v4
salt → crypto.getRandomValues(32 bytes)
→ xpulse_identity_{userId}_salt (localStorage)
privateKey → xpulse_identity_{userId}_private_key (localStorage, AES-GCM verschlüsselt
mit PBKDF2(password + salt))
publicKey → SHA-256(publicKey) = userId
→ xpulse_identity_{userId}_public_key (localStorage)

userId wird aus einem Keypair abgeleitet – nicht direkt aus Login/Passwort. Das bedeutet: Passwort-Änderung ändert die userId nicht. Bei Passwort-Änderung: privateKey entschlüsseln → mit neuem Passwort neu verschlüsseln.

Stabilität – oberstes Gebot

⚠️ userId DARF NIEMALS neu generiert werden solange Daten existieren.
⚠️ Datenverlust durch userId-Änderung ist inakzeptabel.
⚠️ Vor JEDEM Breaking Change: prüfen ob userId migriert werden muss.
App startet →
xpulse_identity_{userId}_public_key vorhanden? →
JA → laden, verwenden – FERTIG, nie neu generieren
NEIN → erster Start → Keypair + Salt generieren → speichern

Mehrere User auf einem Gerät

Jeder User hat seine eigene userId, seinen eigenen privateKey und sein eigenes salt. Alle localStorage-Daten sind durch userId isoliert:

xpulse_identity_{userIdA}_private_key ← nur User A
xpulse_identity_{userIdB}_private_key ← nur User B
xpulse_chat_{userIdA}_profile ← nur User A
xpulse_chat_{userIdB}_profile ← nur User B

Logout User A → seine Daten bleiben unangetastet in localStorage. Login User B → komplett separate Datenwelt.


Zusammenspiel userId + clientId

Nachricht gesendet:
senderId = userId ← wer hat geschrieben?
clientId = clientId ← von welchem Gerät?

Peers adressieren Nachrichten an userId – sie kommen auf allen verbundenen Geräten dieses Users an. clientId zeigt welches Gerät gerade online ist.

Chat mit Johnny:
userId: xyz... ← immer gleich, egal welches Gerät
clientId: abc... ← iPhone
clientId: ghi... ← MacBook (wenn auch verbunden)

Export / Backup

Das Backup enthält:

Verschlüsselung der Private Keys im Backup: Mit einem vom User gewählten Backup-Passwort via @xpulse/crypto (AES-GCM). Ohne das Passwort sind die Keys im Backup wertlos.


Datenverlust-Szenarien

Szenario Konsequenz Wiederherstellung
localStorage clear clientId + userId weg Backup importieren → alles OK
Backup vorhanden, kein localStorage Neuer Start Backup importieren → alles OK
Kein Backup, localStorage clear Neue Identität Re-Pairing nötig, Chat-History weg
Neues Gerät, Backup vorhanden Backup importieren clientId neu, userId gleich → Peers kennen User noch
Neues Gerät, kein Backup Device-Pairing mit bestehendem Gerät userId übertragen → Peers kennen User noch

Algorithmus

Ed25519 verfügbar (moderne Browser)? → verwenden ✅
Nicht verfügbar?Fallback: ECDSA (P-256)
+ Info-Hinweis in UI:
"Dein Browser verwendet einen älteren
Verschlüsselungsstandard.
Ein Browser-Update wird empfohlen."

Ed25519 ist der bevorzugte Algorithmus – schneller, sicherer, kleinere Keys. ECDSA (P-256) ist der bewährte Fallback mit breitem Browser-Support. Der Hinweis beruhigt nicht – er informiert sachlich und professionell.


privateKey Verschlüsselung in localStorage

Der privateKey liegt niemals im Klartext in localStorage. Er wird mit AES-GCM via @xpulse/crypto verschlüsselt – genau wie alle anderen sensiblen Daten auch.

Verschlüsselungs-Key = PBKDF2(loginPassword + userSalt) → encryptionKey
privateKey speichern:
AES-GCM encrypt(privateKey, encryptionKey) → localStorage
privateKey lesen:
salt laden → PBKDF2(loginPassword + salt) → encryptionKey
AES-GCM decrypt(encrypted, encryptionKey) → privateKey

Das Salt ist user-spezifisch, einmalig generiert und nicht geheim – es liegt offen in localStorage (xpulse_identity_{userId}_salt). Nur das Login-Passwort muss geheim bleiben.

Auch wenn eine Browser-Extension den localStorage ausliest – ohne das Login-Passwort ist der privateKey wertlos.

Passwort ändern:privateKey entschlüsseln → mit neuem Passwort + Salt neu verschlüsseln → speichern.


Backup-Passwort

Beim Export wird ein Backup-Passwort gesetzt:

Backup-Passwort Stärke-Indikator:
< 8 Zeichen → schwach (roter Balken)
8-12 Zeichen → mittel (gelber Balken)
> 12 Zeichen → stark (grüner Balken)

Migration – Bestehende Installationen

Bestehende xPulse Chat Installationen haben noch keine Keypairs, keine userId, kein Salt und noch die alten xp_* Key-Namespaces.

Diese Migration folgt GLOBAL_adr-015-migration-strategy.md – dreiphasig mit Backup, Build Space und atomarem Switch.

Was migriert wird

AltNeu
────────────────────────────────────────────────────
xp_* Keysxpulse_chat_* / xpulse_identity_*
kein KeypairEd25519 Keypair generieren
kein userIdSHA-256(publicKey) = userId
kein Saltcrypto.getRandomValues(32) = salt
kein verschlüsselter PKprivateKey mit PBKDF2(pw + salt) verschlüsseln
Profil ohne neue FeldermigrateProfile() – fehlende Felder ergänzen
Messages ohne v / id etc. → migrateV0() im ChatConverter

Pflicht-Checks (Dry Run)

Zusätzlich zu den globalen Checks aus ADR-015:

✓ Keypair generierbar und verifizierbar?
✓ userId aus publicKey ableitbar?
✓ Salt generiert?
✓ privateKey verschlüsselbar und wieder entschlüsselbar? (Test-Runde)
✓ Alle Chat-IDs aus altem Format gefunden?
✓ Alle Messages pro Chat vollständig?
✓ Profil vollständig migriert?
✓ Alle Graveyard-Einträge übernommen?
✓ Neue Key-Struktur konsistent mit ADR-003?

Kritischer Hinweis

⚠️ userId wird bei dieser Migration NEU generiert.
⚠️ Alle Chat-Daten werden unter die neue userId migriert.
⚠️ Erst wenn ALLE Checks bestanden sind wird der Switch durchgeführt.
⚠️ Bei Fehler: vollständiger Rollback, Original unangetastet.

→ Vollständiger Migrationsprozess: GLOBAL_adr-015-migration-strategy.md


Thema Stand
Key-Rotation – kann ein User seinen Keypair erneuern? TBD – komplex, später
de/concept/identity.md 2026-04-17