lum_ccc_rust/docs/encryption_implementation_n...

222 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 Full Ratchet Design & Persistence (Modular Pseudo-Code)
================================================================================
Modular Design Overview (Reusable Components)
=============================================
- `CCCData` → user-configurable settings (KDF, AEAD list, ratchetFrequency, etc.)
- `RatchetEngine` → main class, one instance per channel
- `ChainState` → per-sender state (sending + receiving)
- `KeyDerivation` → static functions for all KDF calls
- `MessageStore` → Hive wrapper for raw blobs + checkpoints
Message Flow From One User to Another (End-to-End)
==================================================
**Sender Side (User A sends to User B)**
```pseudo
function sendMessage(channel, plaintext):
ccc = loadCCCData(channel.groupId)
state = loadChainState(channel.groupId, myDeviceId) // my sending state
// 1. Advance symmetric ratchet if configured
if (ccc.symmetricRatchetEveryMessage):
state.sendingChainKey = KDF(ccc.kdf, state.sendingChainKey, "next" + groupId)
// 2. Generate base_message_key for this exact message
baseKey = deriveMessageKey(state.sendingChainKey, state.lastSentCounter + 1, ccc)
// 3. Pad plaintext
padded = pad(plaintext, ccc.blockSize, ccc.minPaddingBytes, baseKey)
// 4. Encrypt with round-robin AEADs from CCCData
ciphertext = roundRobinAEAD(padded, baseKey, ccc.aeadList)
// 5. Build RelayMessage
relayMsg = new RelayMessage()
relayMsg.from_rly_user_id = myRlyUserId
relayMsg.to_rly_chan_id = channel.to_rly_chan_id
relayMsg.data = ciphertext
relayMsg.senderCounter = state.lastSentCounter + 1
// 6. Send via gRPC
gRPC.send(relayMsg)
// 7. Update and save state
state.lastSentCounter += 1
saveChainState(state)
// 8. Store raw message locally for history
storeRawMessage(channel.groupId, myDeviceId, relayMsg.senderCounter, relayMsg)
```
**Receiver Side (User B receives)**
```pseudo
function onReceive(relayMsg):
ccc = loadCCCData(relayMsg.to_rly_chan_id)
state = loadChainState(relayMsg.to_rly_chan_id, relayMsg.from_rly_user_id) // receiving state
// 1. Advance receiving chain to match senderCounter
while (state.lastReceivedCounter < relayMsg.senderCounter):
if (ccc.symmetricRatchetEveryMessage):
state.receivingChainKey = KDF(ccc.kdf, state.receivingChainKey, "next" + groupId)
state.lastReceivedCounter += 1
// 2. Derive base_message_key
baseKey = deriveMessageKey(state.receivingChainKey, relayMsg.senderCounter, ccc)
// 3. Decrypt
padded = roundRobinAEADDecrypt(relayMsg.data, baseKey, ccc.aeadList)
plaintext = removePadding(padded)
// 4. Store raw + update state
storeRawMessage(groupId, relayMsg.from_rly_user_id, relayMsg.senderCounter, relayMsg)
saveChainState(state)
return plaintext
```
2. How We Generate a Ratchet Key (Modular Function)
===================================================
```pseudo
function deriveMessageKey(chainKey: bytes32, counter: uint64, ccc: CCCData) -> bytes32:
// Exact algorithm used for every message key
info = counter.toBigEndianBytes(8) + groupId
return KDF(ccc.kdfAlgorithm, chainKey, info, outputLength=32)
function KDF(kdfType, key, info, outputLength):
if (kdfType == "KMAC256"):
return KMAC256(key=key, data=info, length=outputLength)
if (kdfType == "HKDF-SHA512"):
return HKDF_SHA512(ikm=key, info=info, length=outputLength)
// add more as user chooses in CCCData
```
3. What Key Material We Store Per Channel (No Per-Message Keys)
===============================================================
We store **only 4 things** per sender per channel:
- `rootKey` (initial shared secret, 32 bytes, AES-256-GCM encrypted under deviceMasterKey)
- `sendingChainKey` (32 bytes, current)
- `receivingChainKey` (32 bytes, current)
- `lastSentCounter` and `lastReceivedCounter` (uint64)
Plus optional checkpoints (your "index keys" request):
```pseudo
class ChainState:
encryptedRootKey // AES-256-GCM(rootKey, deviceMasterKey)
sendingChainKey // 32 bytes (plain in RAM, encrypted on disk if you want)
receivingChainKey // 32 bytes
lastSentCounter // uint64
lastReceivedCounter // uint64
// Checkpoints - every 500 messages (your "few index keys")
checkpoints: Map<uint64, bytes32> // counter -> chainKey at that point
// e.g. key 500, 1000, 1500...
```
We never store per-message keys.
Checkpoints allow fast jump to any old message without replaying the entire history.
4. How We Load Previous Messages and Decrypt Them
==================================================
**Loading Recent Messages (fast path)**
```pseudo
function loadRecentMessages(groupId, limit=100):
messages = queryHiveMessages(groupId, newest first, limit)
for each msg in messages:
if (msg.senderCounter > state.lastReceivedCounter):
// replay forward from current state
state = replayForward(state, msg.senderCounter, ccc)
baseKey = deriveMessageKey(state.receivingChainKey, msg.senderCounter, ccc)
plaintext = decryptRawMessage(msg.rawRelayMessage, baseKey, ccc)
cachePlaintext(msg, plaintext)
```
**Loading Older Messages (when user scrolls up)**
```pseudo
function decryptOlderMessages(groupId, targetCounter):
state = loadChainState(groupId, senderId)
// Find nearest checkpoint <= targetCounter
checkpointCounter = findNearestCheckpoint(state.checkpoints, targetCounter)
chainKey = state.checkpoints[checkpointCounter]
currentCounter = checkpointCounter
while (currentCounter < targetCounter):
if (ccc.symmetricRatchetEveryMessage):
chainKey = KDF(ccc.kdf, chainKey, "next" + groupId)
currentCounter += 1
baseKey = deriveMessageKey(chainKey, targetCounter, ccc)
// Decrypt the raw blob
plaintext = decryptRawMessage(rawRelayMessage, baseKey, ccc)
// Update checkpoint if needed
if (targetCounter % 500 == 0):
state.checkpoints[targetCounter] = chainKey
saveChainState(state)
return plaintext
```
5. How We Begin Generation of the Ratchet Key Chain From Root / Pub Key
=======================================================================
**At Channel Creation / Invite Acceptance**
```pseudo
function initializeRatchetFromRoot(rootKey: bytes32, ccc):
state = new ChainState()
state.encryptedRootKey = aes256GcmEncrypt(rootKey, deviceMasterKey)
// Initial chain keys from root
state.sendingChainKey = KDF(ccc.kdf, rootKey, "sending-chain" + groupId)
state.receivingChainKey = KDF(ccc.kdf, rootKey, "receiving-chain" + groupId)
state.lastSentCounter = 0
state.lastReceivedCounter = 0
// Create first checkpoint
state.checkpoints[0] = state.sendingChainKey
saveChainState(state)
```
6. What Key Material We Share in Invite Code Package
====================================================
**Invite Package (encrypted blob sent to new user)**
```pseudo
class InvitePackage:
cccData: CCCData // full user-chosen config
encryptedRootKey: bytes // rootKey encrypted under recipient's long-term public key
// (using recipient's KEM from CCCData.kemConfigs)
initialGroupId: bytes32
creatorDeviceId: bytes32
```
New user decrypts `encryptedRootKey` with their private key → calls `initializeRatchetFromRoot()` → both users now have identical ratchet starting state.
This is the complete, modular, detailed pseudo-code for Normal Mode.
You can now implement `RatchetEngine`, `ChainState`, `KeyDerivation`, and the storage classes directly.
Everything is reusable for Ultra Mode later.
Ready to code. Let me know when you want the Ultra Mode version in the same style.