v0.0.2: add version display, detailed self-test with step-by-step decrypt
- Version shown on chat load (v0.0.2) - Self-test now does step-by-step: X3DH shared secret comparison, then manual ratchet init + decrypt (not via decrypt_wire_message) - Shows: rng output, shared_match, alice/bob shared secrets, decrypt result - This isolates whether X3DH or ratchet or AEAD fails Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
11
warzone/Cargo.lock
generated
11
warzone/Cargo.lock
generated
@@ -2555,7 +2555,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-client"
|
name = "warzone-client"
|
||||||
version = "0.1.0"
|
version = "0.0.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
@@ -2584,7 +2584,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-mule"
|
name = "warzone-mule"
|
||||||
version = "0.1.0"
|
version = "0.0.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -2593,7 +2593,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-protocol"
|
name = "warzone-protocol"
|
||||||
version = "0.1.0"
|
version = "0.0.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bincode",
|
"bincode",
|
||||||
@@ -2616,7 +2616,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-server"
|
name = "warzone-server"
|
||||||
version = "0.1.0"
|
version = "0.0.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -2642,7 +2642,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-wasm"
|
name = "warzone-wasm"
|
||||||
version = "0.1.0"
|
version = "0.0.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bincode",
|
"bincode",
|
||||||
@@ -2650,6 +2650,7 @@ dependencies = [
|
|||||||
"getrandom 0.2.17",
|
"getrandom 0.2.17",
|
||||||
"hex",
|
"hex",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
"rand",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.0"
|
version = "0.0.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
rust-version = "1.75"
|
rust-version = "1.75"
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ let peerBundles = {}; // peerFP -> bundle bytes
|
|||||||
let pollTimer = null;
|
let pollTimer = null;
|
||||||
let wasmReady = false;
|
let wasmReady = false;
|
||||||
|
|
||||||
|
const VERSION = '0.0.2';
|
||||||
let DEBUG = true; // toggle with /debug command
|
let DEBUG = true; // toggle with /debug command
|
||||||
|
|
||||||
function dbg(...args) {
|
function dbg(...args) {
|
||||||
@@ -453,8 +454,8 @@ async function enterChat() {
|
|||||||
await registerKey();
|
await registerKey();
|
||||||
addSys('Identity loaded: ' + myFingerprint);
|
addSys('Identity loaded: ' + myFingerprint);
|
||||||
addSys('Key registered with server');
|
addSys('Key registered with server');
|
||||||
addSys('DM: paste peer fingerprint or @alias above');
|
addSys('v' + VERSION + ' | DM: paste peer fingerprint or @alias above');
|
||||||
addSys('/alias <name> · /g <group> · /glist · /info · /clear');
|
addSys('/alias · /g · /glist · /info · /selftest · /reset · /debug');
|
||||||
|
|
||||||
const savedPeer = localStorage.getItem('wz-peer');
|
const savedPeer = localStorage.getItem('wz-peer');
|
||||||
if (savedPeer) $peerInput.value = savedPeer;
|
if (savedPeer) $peerInput.value = savedPeer;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ hex.workspace = true
|
|||||||
bincode.workspace = true
|
bincode.workspace = true
|
||||||
x25519-dalek.workspace = true
|
x25519-dalek.workspace = true
|
||||||
ed25519-dalek.workspace = true
|
ed25519-dalek.workspace = true
|
||||||
|
rand.workspace = true
|
||||||
uuid = { version = "1", features = ["v4", "serde", "js"] }
|
uuid = { version = "1", features = ["v4", "serde", "js"] }
|
||||||
|
|
||||||
# profile.release is set at workspace root
|
# profile.release is set at workspace root
|
||||||
|
|||||||
@@ -204,6 +204,11 @@ impl WasmSession {
|
|||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn self_test() -> Result<String, JsValue> {
|
pub fn self_test() -> Result<String, JsValue> {
|
||||||
|
// Check randomness works
|
||||||
|
let mut rng_test = [0u8; 8];
|
||||||
|
rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut rng_test);
|
||||||
|
let rng_hex = hex::encode(rng_test);
|
||||||
|
|
||||||
// Alice
|
// Alice
|
||||||
let alice_seed = Seed::generate();
|
let alice_seed = Seed::generate();
|
||||||
let alice_id = alice_seed.derive_identity();
|
let alice_id = alice_seed.derive_identity();
|
||||||
@@ -216,6 +221,7 @@ pub fn self_test() -> Result<String, JsValue> {
|
|||||||
|
|
||||||
// Bob's pre-key bundle
|
// Bob's pre-key bundle
|
||||||
let (bob_spk_secret, bob_spk) = generate_signed_pre_key(&bob_id, 1);
|
let (bob_spk_secret, bob_spk) = generate_signed_pre_key(&bob_id, 1);
|
||||||
|
let bob_spk_secret_bytes = bob_spk_secret.to_bytes();
|
||||||
let bob_otpks = generate_one_time_pre_keys(0, 5);
|
let bob_otpks = generate_one_time_pre_keys(0, 5);
|
||||||
let bob_bundle = PreKeyBundle {
|
let bob_bundle = PreKeyBundle {
|
||||||
identity_key: *bob_pub.signing.as_bytes(),
|
identity_key: *bob_pub.signing.as_bytes(),
|
||||||
@@ -237,43 +243,43 @@ pub fn self_test() -> Result<String, JsValue> {
|
|||||||
let encrypted = alice_ratchet.encrypt(b"hello from WASM self-test")
|
let encrypted = alice_ratchet.encrypt(b"hello from WASM self-test")
|
||||||
.map_err(|e| JsValue::from_str(&format!("encrypt: {}", e)))?;
|
.map_err(|e| JsValue::from_str(&format!("encrypt: {}", e)))?;
|
||||||
|
|
||||||
let wire = WireMessage::KeyExchange {
|
// Clone encrypted for later use (wire takes ownership)
|
||||||
|
let encrypted_clone = encrypted.clone();
|
||||||
|
|
||||||
|
let _wire = WireMessage::KeyExchange {
|
||||||
sender_fingerprint: alice_pub.fingerprint.to_string(),
|
sender_fingerprint: alice_pub.fingerprint.to_string(),
|
||||||
sender_identity_encryption_key: *alice_pub.encryption.as_bytes(),
|
sender_identity_encryption_key: *alice_pub.encryption.as_bytes(),
|
||||||
ephemeral_public: *x3dh_result.ephemeral_public.as_bytes(),
|
ephemeral_public: *x3dh_result.ephemeral_public.as_bytes(),
|
||||||
used_one_time_pre_key_id: x3dh_result.used_one_time_pre_key_id,
|
used_one_time_pre_key_id: x3dh_result.used_one_time_pre_key_id,
|
||||||
ratchet_message: encrypted,
|
ratchet_message: encrypted,
|
||||||
};
|
};
|
||||||
let wire_bytes = bincode::serialize(&wire)
|
// Step-by-step Bob-side decrypt (NOT using decrypt_wire_message)
|
||||||
.map_err(|e| JsValue::from_str(&e.to_string()))?;
|
|
||||||
|
|
||||||
// Bob decrypts using decrypt_wire_message
|
|
||||||
let bob_spk_hex = hex::encode(bob_spk_secret.to_bytes());
|
|
||||||
let bob_seed_hex = hex::encode(bob_seed.0);
|
|
||||||
|
|
||||||
let result_str = decrypt_wire_message(&bob_seed_hex, &bob_spk_hex, &wire_bytes, None)?;
|
|
||||||
let result: serde_json::Value = serde_json::from_str(&result_str)
|
|
||||||
.map_err(|e| JsValue::from_str(&e.to_string()))?;
|
|
||||||
|
|
||||||
let text = result.get("text").and_then(|v| v.as_str()).unwrap_or("MISSING");
|
|
||||||
|
|
||||||
// More detailed info
|
|
||||||
let alice_shared_hex = hex::encode(x3dh_result.shared_secret);
|
let alice_shared_hex = hex::encode(x3dh_result.shared_secret);
|
||||||
|
|
||||||
// Bob's side: compute shared secret for comparison
|
// Bob: X3DH respond
|
||||||
let bob_shared = x3dh::respond(
|
let bob_shared = x3dh::respond(
|
||||||
&bob_id, &bob_spk_secret, None,
|
&bob_id, &bob_spk_secret, None,
|
||||||
&alice_pub.encryption, &x3dh_result.ephemeral_public,
|
&alice_pub.encryption, &x3dh_result.ephemeral_public,
|
||||||
).map_err(|e| JsValue::from_str(&format!("X3DH respond direct: {}", e)))?;
|
).map_err(|e| JsValue::from_str(&format!("X3DH respond: {}", e)))?;
|
||||||
let bob_shared_hex = hex::encode(bob_shared);
|
let bob_shared_hex = hex::encode(bob_shared);
|
||||||
|
|
||||||
let shared_match = alice_shared_hex == bob_shared_hex;
|
let shared_match = alice_shared_hex == bob_shared_hex;
|
||||||
|
|
||||||
|
// Bob: init ratchet
|
||||||
|
// Need a fresh copy of spk_secret (bob_spk_secret was moved into respond)
|
||||||
|
let bob_spk_secret2 = x25519_dalek::StaticSecret::from(bob_spk_secret_bytes);
|
||||||
|
let mut bob_ratchet = RatchetState::init_bob(bob_shared, bob_spk_secret2);
|
||||||
|
|
||||||
|
// Bob: decrypt
|
||||||
|
let decrypt_result = bob_ratchet.decrypt(&encrypted_clone);
|
||||||
|
let decrypt_text = match &decrypt_result {
|
||||||
|
Ok(plain) => String::from_utf8_lossy(plain).to_string(),
|
||||||
|
Err(e) => format!("DECRYPT_ERROR: {}", e),
|
||||||
|
};
|
||||||
|
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"alice_fp={}, bob_fp={}, wire_bytes={}, alice_shared={}..., bob_shared={}..., shared_match={}, decrypted='{}', PASS={}",
|
"rng={}, shared_match={}, alice_shared={}..., bob_shared={}..., decrypt='{}', PASS={}",
|
||||||
alice_pub.fingerprint, bob_pub.fingerprint, wire_bytes.len(),
|
rng_hex, shared_match, &alice_shared_hex[..16], &bob_shared_hex[..16],
|
||||||
&alice_shared_hex[..16], &bob_shared_hex[..16], shared_match,
|
decrypt_text, decrypt_text == "hello from WASM self-test"
|
||||||
text, text == "hello from WASM self-test"
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user