lum_ccc_rust/docs/encryption_implementation_n...

166 lines
7.9 KiB
ReStructuredText
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

================================================================================
Normal Mode Complete Detailed Design & Persistence Specification
================================================================================
**Project**: World's Most Secure Messaging App
**Mode**: Normal Mode (default for all channels)
**Date**: February 20, 2026
**Version**: 1.0 Written for someone who has never built a ratchet before
This document explains **everything** from first principles. Every term is defined the first time it appears. Every high-level statement is followed immediately by the low-level cryptographic details so you can understand exactly how it works and implement it.
1. What “Ratchet” Actually Means (Simple Explanation)
=====================================================
A **ratchet** is a security system that works like a real metal ratchet tool you use on a socket wrench:
- It only turns **one way** (forward).
- Every time you click it forward, the old position is **permanently lost** — you cannot turn it backward.
- This gives **forward secrecy**: if someone steals your keys tomorrow, they cannot go back and read messages you sent yesterday.
In our app, the ratchet is a mathematical way to keep changing the encryption keys for every single message.
2. What the Double Ratchet Actually Is (High-Level + Low-Level)
===============================================================
We use a **Double Ratchet**.
It has two separate chains that run at the same time.
**High-Level View**
- Chain 1 (Symmetric Ratchet): Advances on almost every message. Very fast.
- Chain 2 (Asymmetric Ratchet): Runs less often (you decide how often). This is the strong “reset” step that gives post-compromise security.
**Low-Level View Symmetric Ratchet (the one that runs every message)**
Start with a 32-byte secret called the **chain key**.
For every new message:
1. Take the current chain key.
2. Run it through your chosen KDF (e.g. KMAC256) with the data “next” + groupId.
3. The output becomes the **new chain key** for the next message.
4. The old chain key is thrown away forever (deleted from memory and never stored).
5. From the **current** chain key you also derive the actual encryption key for this message (called the **base message key**):
```text
base_message_key = KDF(chain_key, counter.to_bytes(8) + groupId, output=32 bytes)
```
This is the “click” of the ratchet. Each message moves the chain forward and deletes the previous state.
**Low-Level View Asymmetric Ratchet Step (the “reset” you asked about)**
This is the part that “injects fresh randomness and resets the symmetric chain”.
How it works in detail:
1. The app decides it is time for a reset (according to your setting in CCCData.ratchetFrequency, e.g. every 50 messages).
2. The sender generates a fresh public-key keypair using the KEM algorithm you chose in CCCData.kemConfigs (for example X448 + Kyber1024 + McEliece460896).
3. The sender sends the **public** part of this keypair **inside** the encrypted payload of the current message (never in the wire header).
4. The receiver decrypts the message normally, sees the new public key, and performs the KEM decapsulation to get a fresh shared secret (3264 bytes of true randomness).
5. Both sides now take this fresh shared secret and mix it into the symmetric chain:
```text
new_chain_key = KDF(old_chain_key, fresh_shared_secret, "asymmetric-reset" + groupId)
```
6. The old symmetric chain key is deleted forever.
This is the “reset”.
It injects **brand new, high-entropy randomness** that the attacker does not have, even if they had stolen all previous keys.
After this step, the conversation “heals” — past compromises no longer matter for future messages.
This is exactly how post-compromise security works.
3. What Exactly Is Stored on Your Device (Very Specific)
========================================================
For each chat (channel) we store exactly these things:
- **CCCData** one object per channel containing every setting you chose (which providers, which KEM, which AEADs, ratchet frequency, padding size, etc.).
- **Raw encrypted wire blobs** for every message that ever arrived, we store the **exact bytes** that came from the relay. These are the permanent chat history. They are stored in Hive exactly as received.
- **Ratchet state** (one set per person you are chatting with in that channel):
- encryptedRootKey (the original shared secret, encrypted under your device master key)
- currentChainKey (the latest symmetric chain key, 32 bytes)
- lastProcessedCounter (the highest message counter we have successfully decrypted for this person)
- checkpoints (a small map: every 500 messages we save a snapshot of the chain key at that counter so we dont have to replay 10,000 steps when loading very old messages)
We **never** store a key for each individual message.
We only keep the current state of the ratchet plus the raw encrypted blobs.
4. Message Flow From One User to Another (Detailed)
===================================================
**Sending Side (User A → User B)**
1. User A types a message.
2. App loads CCCData for this channel (all your chosen algorithms).
3. App loads User As sending ratchet state for this channel.
4. App advances the symmetric chain (runs KDF on currentChainKey to produce newChainKey).
5. App derives base_message_key = KDF(newChainKey, currentCounter + 1, groupId).
6. App pads the plaintext using the block size and random padding bytes you set in CCCData.
7. App encrypts the padded plaintext using the list of AEAD algorithms in CCCData (round-robin).
8. App puts the ciphertext into RelayMessage.data.
9. App sends the RelayMessage to the relay.
10. App updates its sending ratchet state (saves newChainKey and increments counter) and stores the raw RelayMessage locally.
**Receiving Side (User B receives)**
1. User B receives RelayMessage from relay.
2. App immediately stores the exact raw bytes in Hive.
3. App loads CCCData and User As receiving ratchet state.
4. App advances the receiving chain forward until it reaches the incoming message counter.
5. App derives the same base_message_key.
6. App decrypts using the same AEAD list from CCCData.
7. App removes padding and shows the message.
8. App updates and saves the receiving ratchet state.
5. How We Load Messages on Cold Start
=====================================
**Recent Messages (what you see immediately)**
1. You open the app and authenticate → device master key is created.
2. App loads CCCData and the current ratchet state for each person.
3. App loads the newest raw blobs from storage.
4. For each new message, it advances the ratchet forward from the last saved state, derives the base message key, decrypts, and shows it.
5. After decrypting, it updates the saved ratchet state.
**Older Messages (when you scroll up)**
1. You scroll up.
2. Older messages appear grayed out with “Tap to unlock older messages”.
3. When you tap, the app asks for your passphrase or biometrics again.
4. If successful, the app replays the ratchet forward from the last saved state to the old messages you want to see.
5. It derives the correct base message key for each old message, decrypts the raw blob, and shows it.
6. It updates the ratchet state and checkpoints so next time it is faster.
6. How the Ratchet Chain Is Started (Channel Creation / Invite)
===============================================================
1. Creator generates a fresh 32-byte root secret.
2. Root secret is encrypted under the recipients public key (using the KEM you chose in CCCData) and sent in the invite.
3. Recipient decrypts the root secret.
4. Both sides run the same initialization:
- sendingChainKey = KDF(rootSecret, "sending" + groupId)
- receivingChainKey = KDF(rootSecret, "receiving" + groupId)
5. Both save the encrypted root and initial chain keys.
6. Counters start at 0.