Files
featherChat/warzone/crates/warzone-protocol/src/sender_keys.rs
Siavash Sameni 86da52acc4 v0.0.13: Sender Keys for efficient group encryption
Protocol (sender_keys.rs):
- SenderKey: symmetric key with chain ratchet (forward secrecy per chain)
- generate(), rotate(), encrypt(), decrypt()
- SenderKeyDistribution: share key via 1:1 encrypted channel
- SenderKeyMessage: encrypted group message (O(1) instead of O(N))
- Chain key ratchets forward on each message (HKDF)
- Generation counter for key rotation tracking
- 4 tests: basic, multi-message, rotation, old-key rejection

WireMessage:
- GroupSenderKey variant: encrypted group message
- SenderKeyDistribution variant: key sharing

Server: dedup handles new variants.
CLI TUI + recv: stub handlers for new message types.
23/23 protocol tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 13:23:10 +04:00

211 lines
7.0 KiB
Rust

//! Sender Keys for efficient group encryption.
//!
//! Instead of encrypting per-member (O(N)), each member generates a
//! symmetric "sender key" and distributes it to all group members via
//! 1:1 encrypted channels. Group messages are encrypted ONCE with the
//! sender's key, and the same ciphertext is delivered to all members.
//!
//! Key rotation: on member join/leave, all members rotate their sender keys.
use serde::{Deserialize, Serialize};
use crate::crypto::{aead_decrypt, aead_encrypt, hkdf_derive};
use crate::errors::ProtocolError;
/// A sender key: symmetric key + chain for forward ratcheting.
#[derive(Clone, Serialize, Deserialize)]
pub struct SenderKey {
/// Who owns this key.
pub owner_fingerprint: String,
/// Group this key belongs to.
pub group_name: String,
/// Current chain key (ratchets forward on each message).
pub chain_key: [u8; 32],
/// Message counter.
pub counter: u32,
/// Generation (incremented on rotation).
pub generation: u32,
}
impl SenderKey {
/// Generate a new sender key for a group.
pub fn generate(owner_fingerprint: &str, group_name: &str) -> Self {
let mut chain_key = [0u8; 32];
rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut chain_key);
SenderKey {
owner_fingerprint: owner_fingerprint.to_string(),
group_name: group_name.to_string(),
chain_key,
counter: 0,
generation: 0,
}
}
/// Rotate: new random chain key, increment generation.
pub fn rotate(&mut self) {
rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut self.chain_key);
self.counter = 0;
self.generation += 1;
}
/// Derive a message key from the current chain key, then ratchet forward.
fn derive_message_key(&mut self) -> [u8; 32] {
let info = format!("wz-sk-msg-{}-{}", self.generation, self.counter);
let mk_bytes = hkdf_derive(&self.chain_key, b"", info.as_bytes(), 32);
let mut message_key = [0u8; 32];
message_key.copy_from_slice(&mk_bytes);
// Ratchet chain key forward
let ck_bytes = hkdf_derive(&self.chain_key, b"", b"wz-sk-chain", 32);
self.chain_key.copy_from_slice(&ck_bytes);
self.counter += 1;
message_key
}
/// Encrypt a message with this sender key.
pub fn encrypt(&mut self, plaintext: &[u8]) -> SenderKeyMessage {
let message_key = self.derive_message_key();
let aad = format!("{}:{}:{}", self.group_name, self.generation, self.counter - 1);
let ciphertext = aead_encrypt(&message_key, plaintext, aad.as_bytes());
SenderKeyMessage {
sender_fingerprint: self.owner_fingerprint.clone(),
group_name: self.group_name.clone(),
generation: self.generation,
counter: self.counter - 1,
ciphertext,
}
}
/// Decrypt a message from another member using their sender key.
/// `self` is the RECEIVER's copy of the SENDER's key.
pub fn decrypt(&mut self, msg: &SenderKeyMessage) -> Result<Vec<u8>, ProtocolError> {
// Fast-forward chain if needed (handle skipped messages)
if msg.generation != self.generation {
return Err(ProtocolError::RatchetError(format!(
"generation mismatch: expected {}, got {}",
self.generation, msg.generation
)));
}
// We need to advance to the right counter
while self.counter < msg.counter {
// Skip this message key (lost message)
let _ = self.derive_message_key();
}
if self.counter != msg.counter {
return Err(ProtocolError::RatchetError("counter mismatch".into()));
}
let message_key = self.derive_message_key();
let aad = format!("{}:{}:{}", msg.group_name, msg.generation, msg.counter);
aead_decrypt(&message_key, &msg.ciphertext, aad.as_bytes())
}
}
/// An encrypted group message using sender keys.
#[derive(Clone, Serialize, Deserialize)]
pub struct SenderKeyMessage {
pub sender_fingerprint: String,
pub group_name: String,
pub generation: u32,
pub counter: u32,
pub ciphertext: Vec<u8>,
}
/// Distribution message: sent via 1:1 encrypted channel to share a sender key.
#[derive(Clone, Serialize, Deserialize)]
pub struct SenderKeyDistribution {
pub sender_fingerprint: String,
pub group_name: String,
pub chain_key: [u8; 32],
pub generation: u32,
}
impl From<&SenderKey> for SenderKeyDistribution {
fn from(sk: &SenderKey) -> Self {
SenderKeyDistribution {
sender_fingerprint: sk.owner_fingerprint.clone(),
group_name: sk.group_name.clone(),
chain_key: sk.chain_key,
generation: sk.generation,
}
}
}
impl SenderKeyDistribution {
/// Convert distribution into a receiver's copy of the sender key.
pub fn into_sender_key(self) -> SenderKey {
SenderKey {
owner_fingerprint: self.sender_fingerprint,
group_name: self.group_name,
chain_key: self.chain_key,
counter: 0,
generation: self.generation,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_encrypt_decrypt() {
let mut alice_key = SenderKey::generate("alice", "ops");
// Bob gets a copy of Alice's key (via distribution)
let dist = SenderKeyDistribution::from(&alice_key);
let mut bob_copy = dist.into_sender_key();
let msg = alice_key.encrypt(b"hello group");
let plain = bob_copy.decrypt(&msg).unwrap();
assert_eq!(plain, b"hello group");
}
#[test]
fn multiple_messages() {
let mut alice_key = SenderKey::generate("alice", "ops");
let dist = SenderKeyDistribution::from(&alice_key);
let mut bob_copy = dist.into_sender_key();
for i in 0..10 {
let msg = alice_key.encrypt(format!("msg {}", i).as_bytes());
let plain = bob_copy.decrypt(&msg).unwrap();
assert_eq!(plain, format!("msg {}", i).as_bytes());
}
}
#[test]
fn rotation() {
let mut alice_key = SenderKey::generate("alice", "ops");
let dist1 = SenderKeyDistribution::from(&alice_key);
let mut bob_copy = dist1.into_sender_key();
let msg1 = alice_key.encrypt(b"before rotation");
let _ = bob_copy.decrypt(&msg1).unwrap();
// Rotate
alice_key.rotate();
let dist2 = SenderKeyDistribution::from(&alice_key);
let mut bob_copy2 = dist2.into_sender_key();
let msg2 = alice_key.encrypt(b"after rotation");
let plain = bob_copy2.decrypt(&msg2).unwrap();
assert_eq!(plain, b"after rotation");
}
#[test]
fn old_key_cant_decrypt_new() {
let mut alice_key = SenderKey::generate("alice", "ops");
let dist = SenderKeyDistribution::from(&alice_key);
let mut bob_old = dist.into_sender_key();
alice_key.rotate();
let msg = alice_key.encrypt(b"new generation");
assert!(bob_old.decrypt(&msg).is_err());
}
}