Chat Lifecycle & Destruct
Nuke self-destructs, full-mesh group nukes, soft archiving, and pre-reset message recovery.
Overview
Conversations in Wiltkey follow a strict lifecycle. Unlike traditional messaging platforms where accounts and logs are persistent, Wiltkey chats are highly transient. They end either by key exhaustion (wilting), a user-initiated local archive (which preserves chat history but frees large key assets), or a destructive nuke (which wipes cryptographic keys and database logs on both devices).
Nuke vs. Archive Flow
How it Works
1. Nuke (Both-Sides Self-Destruct)
When a user nukes a chat locally via nukeContact(receivedFromPeer: false), the client:
- Removes the contact row and message windows from active memory.
- Deletes the pad file from disk via
WiltkeyOtpService.deleteKeystreamFile. - Wipes SQLite records (messages, profiles, lane allocations).
- Clears associated caches (e.g.
ChatMetaStore,CustomEmojiStore). - Sends a
NUKE_RECIPIENTcommand containing a"VAPORIZE"envelope over the WebSocket.
The Go relay server receives this command, locks the recipient's message queue in Redis (BlockQueue), queues the nuke payload, and pushes a nuke frame immediately if the recipient is online. Upon receiving the nuke frame, the peer client wipes their local logs and responds with an ACK_NUKE message, instructing the server to release the queue block.
2. Group Nuke (Full-Mesh Self-Destruct)
Because there is no central group room, group nukes are fanned out in a full-mesh layout. The initiator sends a group_nuke envelope to every member. When a member client processes this envelope:
- If they are the host, they re-transmit the
group_nuketo all other members (excluding the initiator and themselves) to catch spokes who only know the host, then wipe locally. - Spokes wipe local tables and files instantly.
3. Archive (Soft-Nuke)
Archiving drops the large keystream pad file to reclaim storage space while keeping the message history readable. Crucially, before deleting the pad, the client runs a message recovery sweep:
Future<void> _preserveMessagesBeforePadReset(String keyHash) async {
// Query DB for received-but-never-opened (OTP-only) messages
final pending = await WiltkeyDatabase.instance.getOtpOnlyMessages(contact.id);
for (final msg in pending) {
final plainBytes = await WiltkeyOtpService.xorWithKeystream(keyHash, ...);
msg.decryptedText = utf8.decode(plainBytes);
// Write copy encrypted under the master key to DB
await WiltkeyDatabase.instance.saveMessage(msg, masterKeyHex: masterKeyHex);
}
}
Once recovery is complete, the pad file is deleted, and the contact is flagged as isArchived = true. The client pushes an archive_signal over the metadata channel. The peer receives this, flags a premature wilt on their end, and posts a system line explaining that the chat is now read-only.
Key Files & Symbols
| File Path | Symbol Name | Description |
|---|---|---|
lib/core/state_lifecycle.dart |
nukeContact() |
Deletes local pads, wipes databases, and sends WS self-destruct envelopes. |
lib/core/state_lifecycle.dart |
_preserveMessagesBeforePadReset() |
Recovers unopened background messages using the active pad before deletion. |
lib/core/state_lifecycle.dart |
archiveChat() |
Coordinates the soft-nuke archive pipeline and transmits the archive metadata signal. |
lib/core/state_lifecycle.dart |
_fanOutGroupNuke() |
Fans out group nuke frames to members in a full mesh. |
Gotchas & Edge Cases
If the application crashes or is terminated during a nuke, a pad file or contact row might remain orphaned. To clean this up, a master self-destruct (
triggerNuke) does a sweep of the entire document folder, reconciling and deleting any file ending in .pad.