feat: WarzonePhone lossy VoIP protocol — Phase 1 complete

Rust workspace with 7 crates implementing a custom VoIP protocol
designed for extremely lossy connections (5-70% loss, 100-500kbps,
300-800ms RTT). 89 tests passing across all crates.

Crates:
- wzp-proto: Wire format, traits, adaptive quality controller, jitter buffer, session FSM
- wzp-codec: Opus encoder/decoder (audiopus), Codec2 stubs, adaptive switching, resampling
- wzp-fec: RaptorQ fountain codes, interleaving, block management (proven 30-70% loss recovery)
- wzp-crypto: X25519+ChaCha20-Poly1305, Warzone identity compatible, anti-replay, rekeying
- wzp-transport: QUIC via quinn with DATAGRAM frames, path monitoring, signaling streams
- wzp-relay: Integration stub (Phase 2)
- wzp-client: Integration stub (Phase 2)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-03-27 12:45:07 +04:00
commit 51e893590c
47 changed files with 7097 additions and 0 deletions

View File

@@ -0,0 +1,214 @@
//! Warzone identity key exchange.
//!
//! Implements the `KeyExchange` trait from `wzp-proto`:
//! - Identity: 32-byte seed -> HKDF -> Ed25519 (signing) + X25519 (encryption)
//! - Fingerprint: SHA-256(Ed25519 pub)[:16]
//! - Per-call: ephemeral X25519 -> ChaCha20-Poly1305 session
use ed25519_dalek::{Signer, SigningKey, Verifier, VerifyingKey};
use hkdf::Hkdf;
use rand::rngs::OsRng;
use sha2::{Digest, Sha256};
use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret};
use wzp_proto::{CryptoError, CryptoSession, KeyExchange};
use crate::session::ChaChaSession;
/// Warzone-compatible key exchange implementation.
pub struct WarzoneKeyExchange {
/// Ed25519 signing key (identity).
signing_key: SigningKey,
/// X25519 static secret (derived from seed, used for identity encryption).
#[allow(dead_code)]
x25519_static_secret: StaticSecret,
/// X25519 static public key.
#[allow(dead_code)]
x25519_static_public: X25519PublicKey,
/// Ephemeral X25519 secret for the current call (set by generate_ephemeral).
ephemeral_secret: Option<StaticSecret>,
}
impl KeyExchange for WarzoneKeyExchange {
fn from_identity_seed(seed: &[u8; 32]) -> Self {
// Derive Ed25519 signing key via HKDF
let hk = Hkdf::<Sha256>::new(None, seed);
let mut ed25519_bytes = [0u8; 32];
hk.expand(b"warzone-ed25519-identity", &mut ed25519_bytes)
.expect("HKDF expand for Ed25519 should not fail");
let signing_key = SigningKey::from_bytes(&ed25519_bytes);
// Derive X25519 static key via HKDF
let mut x25519_bytes = [0u8; 32];
hk.expand(b"warzone-x25519-identity", &mut x25519_bytes)
.expect("HKDF expand for X25519 should not fail");
let x25519_static_secret = StaticSecret::from(x25519_bytes);
let x25519_static_public = X25519PublicKey::from(&x25519_static_secret);
Self {
signing_key,
x25519_static_secret,
x25519_static_public,
ephemeral_secret: None,
}
}
fn generate_ephemeral(&mut self) -> [u8; 32] {
let secret = StaticSecret::random_from_rng(OsRng);
let public = X25519PublicKey::from(&secret);
self.ephemeral_secret = Some(secret);
public.to_bytes()
}
fn identity_public_key(&self) -> [u8; 32] {
self.signing_key.verifying_key().to_bytes()
}
fn fingerprint(&self) -> [u8; 16] {
let pub_bytes = self.identity_public_key();
let hash = Sha256::digest(pub_bytes);
let mut fp = [0u8; 16];
fp.copy_from_slice(&hash[..16]);
fp
}
fn sign(&self, data: &[u8]) -> Vec<u8> {
let sig = self.signing_key.sign(data);
sig.to_bytes().to_vec()
}
fn verify(peer_identity_pub: &[u8; 32], data: &[u8], signature: &[u8]) -> bool {
let Ok(verifying_key) = VerifyingKey::from_bytes(peer_identity_pub) else {
return false;
};
let Ok(sig_bytes) = <[u8; 64]>::try_from(signature) else {
return false;
};
let sig = ed25519_dalek::Signature::from_bytes(&sig_bytes);
verifying_key.verify(data, &sig).is_ok()
}
fn derive_session(
&self,
peer_ephemeral_pub: &[u8; 32],
) -> Result<Box<dyn CryptoSession>, CryptoError> {
let secret = self
.ephemeral_secret
.as_ref()
.ok_or_else(|| {
CryptoError::Internal("no ephemeral key generated; call generate_ephemeral first".into())
})?;
let peer_public = X25519PublicKey::from(*peer_ephemeral_pub);
// Use diffie_hellman with a clone of the StaticSecret
let secret_bytes: [u8; 32] = secret.to_bytes();
let secret_clone = StaticSecret::from(secret_bytes);
let shared_secret = secret_clone.diffie_hellman(&peer_public);
// Expand shared secret via HKDF
let hk = Hkdf::<Sha256>::new(None, shared_secret.as_bytes());
let mut session_key = [0u8; 32];
hk.expand(b"warzone-session-key", &mut session_key)
.expect("HKDF expand for session key should not fail");
Ok(Box::new(ChaChaSession::new(session_key)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deterministic_identity_from_seed() {
let seed = [0x42u8; 32];
let kx1 = WarzoneKeyExchange::from_identity_seed(&seed);
let kx2 = WarzoneKeyExchange::from_identity_seed(&seed);
assert_eq!(kx1.identity_public_key(), kx2.identity_public_key());
assert_eq!(kx1.fingerprint(), kx2.fingerprint());
}
#[test]
fn different_seeds_different_keys() {
let kx1 = WarzoneKeyExchange::from_identity_seed(&[0x01; 32]);
let kx2 = WarzoneKeyExchange::from_identity_seed(&[0x02; 32]);
assert_ne!(kx1.identity_public_key(), kx2.identity_public_key());
}
#[test]
fn fingerprint_is_16_bytes_of_sha256() {
let seed = [0x99u8; 32];
let kx = WarzoneKeyExchange::from_identity_seed(&seed);
let fp = kx.fingerprint();
assert_eq!(fp.len(), 16);
// Verify manually
let pub_key = kx.identity_public_key();
let hash = Sha256::digest(pub_key);
assert_eq!(&fp[..], &hash[..16]);
}
#[test]
fn sign_and_verify() {
let seed = [0xAA; 32];
let kx = WarzoneKeyExchange::from_identity_seed(&seed);
let data = b"hello warzone";
let sig = kx.sign(data);
assert!(WarzoneKeyExchange::verify(
&kx.identity_public_key(),
data,
&sig
));
}
#[test]
fn verify_wrong_data_fails() {
let seed = [0xAA; 32];
let kx = WarzoneKeyExchange::from_identity_seed(&seed);
let sig = kx.sign(b"correct data");
assert!(!WarzoneKeyExchange::verify(
&kx.identity_public_key(),
b"wrong data",
&sig
));
}
#[test]
fn verify_wrong_key_fails() {
let kx1 = WarzoneKeyExchange::from_identity_seed(&[0x01; 32]);
let kx2 = WarzoneKeyExchange::from_identity_seed(&[0x02; 32]);
let sig = kx1.sign(b"data");
assert!(!WarzoneKeyExchange::verify(
&kx2.identity_public_key(),
b"data",
&sig
));
}
#[test]
fn full_handshake_alice_bob_same_session_key() {
let mut alice = WarzoneKeyExchange::from_identity_seed(&[0xAA; 32]);
let mut bob = WarzoneKeyExchange::from_identity_seed(&[0xBB; 32]);
let alice_eph_pub = alice.generate_ephemeral();
let bob_eph_pub = bob.generate_ephemeral();
let mut alice_session = alice.derive_session(&bob_eph_pub).unwrap();
let mut bob_session = bob.derive_session(&alice_eph_pub).unwrap();
// Verify they can communicate: Alice encrypts, Bob decrypts
let header = b"call-header";
let plaintext = b"hello from alice";
let mut ciphertext = Vec::new();
alice_session
.encrypt(header, plaintext, &mut ciphertext)
.unwrap();
let mut decrypted = Vec::new();
bob_session
.decrypt(header, &ciphertext, &mut decrypted)
.unwrap();
assert_eq!(&decrypted, plaintext);
}
}