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>
This commit is contained in:
Siavash Sameni
2026-03-27 13:23:10 +04:00
parent 653c6c050b
commit 86da52acc4
9 changed files with 280 additions and 6 deletions

View File

@@ -123,6 +123,12 @@ pub async fn run(server_url: &str, identity: &IdentityKeyPair) -> Result<()> {
Ok(WireMessage::FileChunk { filename, chunk_index, total_chunks, sender_fingerprint, .. }) => {
println!(" [file chunk] {} chunk {}/{} of '{}'", sender_fingerprint, chunk_index + 1, total_chunks, filename);
}
Ok(WireMessage::GroupSenderKey { sender_fingerprint, group_name, .. }) => {
println!(" [group] {} sent to #{}", sender_fingerprint, group_name);
}
Ok(WireMessage::SenderKeyDistribution { sender_fingerprint, group_name, .. }) => {
println!(" [sender key] received key from {} for #{}", sender_fingerprint, group_name);
}
Err(e) => {
eprintln!(" failed to deserialize message: {}", e);
}

View File

@@ -1340,6 +1340,38 @@ fn process_wire_message(
// Received chunk without header — ignore
}
}
WireMessage::GroupSenderKey {
id: _,
sender_fingerprint,
group_name,
generation: _,
counter: _,
ciphertext: _,
} => {
// TODO: decrypt with stored sender key for this sender+group
messages.lock().unwrap().push(ChatLine {
sender: sender_fingerprint[..sender_fingerprint.len().min(12)].to_string(),
text: format!("[group #{} sender-key message — key setup needed]", group_name),
is_system: false,
is_self: false,
message_id: None,
});
}
WireMessage::SenderKeyDistribution {
sender_fingerprint,
group_name,
chain_key: _,
generation: _,
} => {
// TODO: store this sender key for future group decryption
messages.lock().unwrap().push(ChatLine {
sender: "system".into(),
text: format!("Received sender key from {} for #{}", &sender_fingerprint[..sender_fingerprint.len().min(12)], group_name),
is_system: true,
is_self: false,
message_id: None,
});
}
}
}