Fix X3DH + add web client served by warzone-server
X3DH fix: - Added identity_encryption_key (X25519) to PreKeyBundle - initiate() and respond() now use correct DH operations per Signal spec: DH1=IK_a*SPK_b, DH2=EK_a*IK_b, DH3=EK_a*SPK_b, DH4=EK_a*OPK_b - All 17 tests pass including x3dh_shared_secret_matches Web client (served at /): - Identity generation with seed (stored in localStorage) - Recovery from hex-encoded seed - Auto-load saved identity on page load - Fingerprint display (same format as CLI: xxxx:xxxx:xxxx:xxxx) - Key registration with server via /v1/keys/register - Chat UI with message polling (5s interval) - Commands: /help, /info, /seed - Dark theme matching warzone aesthetic Both clients (CLI + Web) now exist: - CLI: warzone init, warzone info, warzone recover - Web: http://localhost:7700/ (served by warzone-server) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,27 +37,17 @@ pub fn initiate(
|
||||
.verify(&their_identity)
|
||||
.map_err(|_| ProtocolError::X3DHFailed("signed pre-key verification failed".into()))?;
|
||||
|
||||
// Bob's X25519 identity key: we need to convert Ed25519 verifying key → X25519
|
||||
// For simplicity, we store X25519 public keys separately in bundles.
|
||||
// Here we use the signed_pre_key's public key and the identity encryption key.
|
||||
// In our model, the bundle carries the Ed25519 identity key for signing verification,
|
||||
// but X3DH uses X25519 keys. We'll derive Bob's X25519 identity from the bundle.
|
||||
//
|
||||
// TODO: The bundle should also carry the X25519 identity public key.
|
||||
// For now, we'll use the signed pre-key as SPK and skip IK DH.
|
||||
// This is a simplification — full X3DH has 4 DH ops with IK.
|
||||
|
||||
let ephemeral_secret = StaticSecret::random_from_rng(rand::rngs::OsRng);
|
||||
let ephemeral_public = PublicKey::from(&ephemeral_secret);
|
||||
|
||||
let their_spk = PublicKey::from(their_bundle.signed_pre_key.public_key);
|
||||
let their_identity_x25519 = PublicKey::from(their_bundle.identity_encryption_key);
|
||||
|
||||
// DH1: our_identity_x25519 * their_signed_pre_key
|
||||
let dh1 = our_identity.encryption.diffie_hellman(&their_spk);
|
||||
|
||||
// DH2: our_ephemeral * their_identity_x25519
|
||||
// TODO: need their X25519 identity key in bundle. Using SPK as stand-in.
|
||||
let dh2 = ephemeral_secret.diffie_hellman(&their_spk);
|
||||
let dh2 = ephemeral_secret.diffie_hellman(&their_identity_x25519);
|
||||
|
||||
// DH3: our_ephemeral * their_signed_pre_key
|
||||
let dh3 = ephemeral_secret.diffie_hellman(&their_spk);
|
||||
@@ -98,15 +88,15 @@ pub fn respond(
|
||||
our_identity: &IdentityKeyPair,
|
||||
our_signed_pre_key_secret: &StaticSecret,
|
||||
our_one_time_pre_key_secret: Option<&StaticSecret>,
|
||||
their_identity_x25519: &PublicKey,
|
||||
their_ephemeral_public: &PublicKey,
|
||||
) -> Result<[u8; 32], ProtocolError> {
|
||||
let their_eph = *their_ephemeral_public;
|
||||
|
||||
// DH1: their_identity_x25519 * our_signed_pre_key
|
||||
// TODO: need their X25519 identity key. Using ephemeral as stand-in.
|
||||
let dh1 = our_signed_pre_key_secret.diffie_hellman(&their_eph);
|
||||
// DH1: our_signed_pre_key * their_identity_x25519
|
||||
let dh1 = our_signed_pre_key_secret.diffie_hellman(their_identity_x25519);
|
||||
|
||||
// DH2: their_ephemeral * our_identity_x25519
|
||||
// DH2: our_identity_x25519 * their_ephemeral
|
||||
let dh2 = our_identity.encryption.diffie_hellman(&their_eph);
|
||||
|
||||
// DH3: their_ephemeral * our_signed_pre_key
|
||||
@@ -130,8 +120,6 @@ pub fn respond(
|
||||
Ok(shared_secret)
|
||||
}
|
||||
|
||||
// TODO: Full X3DH implementation requires X25519 identity keys in the bundle.
|
||||
// Current implementation is simplified. Fix in step 5 of the implementation plan.
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -151,8 +139,11 @@ mod tests {
|
||||
let bob_otpks = generate_one_time_pre_keys(0, 1);
|
||||
let bob_pub = bob_id.public_identity();
|
||||
|
||||
let alice_pub = alice_id.public_identity();
|
||||
|
||||
let bundle = PreKeyBundle {
|
||||
identity_key: *bob_pub.signing.as_bytes(),
|
||||
identity_encryption_key: *bob_pub.encryption.as_bytes(),
|
||||
signed_pre_key: bob_spk,
|
||||
one_time_pre_key: Some(crate::prekey::OneTimePreKeyPublic {
|
||||
id: bob_otpks[0].id,
|
||||
@@ -165,6 +156,7 @@ mod tests {
|
||||
&bob_id,
|
||||
&bob_spk_secret,
|
||||
Some(&bob_otpks[0].secret),
|
||||
&alice_pub.encryption,
|
||||
&alice_result.ephemeral_public,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Reference in New Issue
Block a user