v0.0.22: version bump, ETH identity in web client, version bump rule

Version:
- Workspace + protocol: 0.0.21 → 0.0.22
- Web client VERSION: 0.0.17 → 0.0.22
- Service worker cache: wz-v2 → wz-v3

ETH identity:
- Added WasmIdentity::eth_address() export (derives from seed via secp256k1)
- Web client sends eth_address during key registration
- Identity display shows ETH address first, then fingerprint
- No more server-side resolve needed — computed client-side

CLAUDE.md:
- Added MANDATORY version bump rule (4 places to update)
- Must bump on every functional change, never skip SW cache

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-03-29 08:11:31 +04:00
parent fcbf2d5859
commit 3efce2ddf4
6 changed files with 34 additions and 23 deletions

View File

@@ -1,5 +1,15 @@
# featherChat — Design Principles & Conventions
## MANDATORY: Version Bumping
**After every set of changes that modifies functionality, bump the version:**
1. `Cargo.toml` workspace version (e.g. `0.0.22``0.0.23`)
2. `crates/warzone-protocol/Cargo.toml` standalone version (same)
3. `crates/warzone-server/src/routes/web.rs` JS `VERSION` constant
4. `crates/warzone-server/src/routes/web.rs` service worker `CACHE` version (`wz-vN``wz-v(N+1)`)
Never commit functional changes without bumping all four. The service worker cache MUST be bumped or browsers will serve stale WASM.
## Architecture Principles
1. **Single seed, multiple identities** — Ed25519 (messaging), X25519 (encryption), secp256k1 (ETH address) all derived from one BIP39 seed via HKDF with domain-separated info strings.

10
warzone/Cargo.lock generated
View File

@@ -2956,7 +2956,7 @@ dependencies = [
[[package]]
name = "warzone-client"
version = "0.0.21"
version = "0.0.22"
dependencies = [
"anyhow",
"argon2",
@@ -2989,7 +2989,7 @@ dependencies = [
[[package]]
name = "warzone-mule"
version = "0.0.21"
version = "0.0.22"
dependencies = [
"anyhow",
"clap",
@@ -2998,7 +2998,7 @@ dependencies = [
[[package]]
name = "warzone-protocol"
version = "0.0.21"
version = "0.0.22"
dependencies = [
"base64",
"bincode",
@@ -3023,7 +3023,7 @@ dependencies = [
[[package]]
name = "warzone-server"
version = "0.0.21"
version = "0.0.22"
dependencies = [
"anyhow",
"axum",
@@ -3053,7 +3053,7 @@ dependencies = [
[[package]]
name = "warzone-wasm"
version = "0.0.21"
version = "0.0.22"
dependencies = [
"base64",
"bincode",

View File

@@ -9,7 +9,7 @@ members = [
]
[workspace.package]
version = "0.0.21"
version = "0.0.22"
edition = "2021"
license = "MIT"
rust-version = "1.75"

View File

@@ -1,6 +1,6 @@
[package]
name = "warzone-protocol"
version = "0.0.21"
version = "0.0.22"
edition = "2021"
license = "MIT"
description = "Core crypto & wire protocol for featherChat (Warzone messenger)"

View File

@@ -50,7 +50,7 @@ async fn pwa_manifest() -> impl IntoResponse {
async fn service_worker() -> impl IntoResponse {
([(header::CONTENT_TYPE, "application/javascript")], r##"
const CACHE = 'wz-v2';
const CACHE = 'wz-v3';
const SHELL = ['/', '/wasm/warzone_wasm.js', '/wasm/warzone_wasm_bg.wasm', '/icon.svg', '/manifest.json'];
self.addEventListener('install', e => {
@@ -242,7 +242,7 @@ let pollTimer = null;
let ws = null; // WebSocket connection
let wasmReady = false;
const VERSION = '0.0.17';
const VERSION = '0.0.22';
let DEBUG = true; // toggle with /debug command
// ── Receipt tracking ──
@@ -388,11 +388,12 @@ function loadSavedIdentity() {
async function registerKey() {
const fp = normFP(myFingerprint);
const bundleBytes = wasmIdentity.bundle_bytes();
dbg('Registering key, fp:', fp, 'bundle size:', bundleBytes.length);
myEthAddress = wasmIdentity.eth_address();
dbg('Registering key, fp:', fp, 'bundle size:', bundleBytes.length, 'eth:', myEthAddress);
await fetch(SERVER + '/v1/keys/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ fingerprint: fp, bundle: Array.from(bundleBytes) })
body: JSON.stringify({ fingerprint: fp, bundle: Array.from(bundleBytes), eth_address: myEthAddress })
});
dbg('Key registered');
}
@@ -862,20 +863,14 @@ async function enterChat() {
document.getElementById('hdr-server').textContent = SERVER;
await registerKey();
addSys('Identity loaded: ' + myFingerprint);
addSys('Identity: ' + myEthAddress);
addSys('Fingerprint: ' + myFingerprint);
addSys('Key registered with server');
// Fetch ETH address from server
try {
const resolveResp = await fetch(SERVER + '/v1/resolve/' + normFP(myFingerprint));
const resolveData = await resolveResp.json();
if (resolveData.eth_address) {
myEthAddress = resolveData.eth_address;
addSys('ETH: ' + myEthAddress);
document.getElementById('hdr-eth').textContent = myEthAddress.slice(0, 10) + '...';
document.getElementById('hdr-eth').title = myEthAddress;
}
} catch(e) { dbg('ETH resolve failed:', e); }
if (myEthAddress) {
document.getElementById('hdr-eth').textContent = myEthAddress.slice(0, 10) + '...';
document.getElementById('hdr-eth').title = myEthAddress;
}
addSys('v' + VERSION + ' | DM: paste peer fingerprint or @alias above');
addSys('/alias · /g · /gleave · /gkick · /gmembers · /glist · /friend · /file · /info');

View File

@@ -52,6 +52,12 @@ impl WasmIdentity {
Seed::from_bytes(self.seed_bytes).to_mnemonic()
}
/// Get the Ethereum address derived from this seed.
pub fn eth_address(&self) -> String {
let eth = warzone_protocol::ethereum::derive_eth_identity(&self.seed_bytes);
eth.address.to_checksum()
}
/// Get the pre-key bundle as bincode bytes (for server registration).
/// The bundle is generated once and cached. The SPK secret is stored internally.
pub fn bundle_bytes(&mut self) -> Result<Vec<u8>, JsValue> {