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:
@@ -1,5 +1,15 @@
|
|||||||
# featherChat — Design Principles & Conventions
|
# 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
|
## 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.
|
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
10
warzone/Cargo.lock
generated
@@ -2956,7 +2956,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-client"
|
name = "warzone-client"
|
||||||
version = "0.0.21"
|
version = "0.0.22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
@@ -2989,7 +2989,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-mule"
|
name = "warzone-mule"
|
||||||
version = "0.0.21"
|
version = "0.0.22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -2998,7 +2998,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-protocol"
|
name = "warzone-protocol"
|
||||||
version = "0.0.21"
|
version = "0.0.22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bincode",
|
"bincode",
|
||||||
@@ -3023,7 +3023,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-server"
|
name = "warzone-server"
|
||||||
version = "0.0.21"
|
version = "0.0.22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -3053,7 +3053,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-wasm"
|
name = "warzone-wasm"
|
||||||
version = "0.0.21"
|
version = "0.0.22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bincode",
|
"bincode",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.0.21"
|
version = "0.0.22"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
rust-version = "1.75"
|
rust-version = "1.75"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "warzone-protocol"
|
name = "warzone-protocol"
|
||||||
version = "0.0.21"
|
version = "0.0.22"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Core crypto & wire protocol for featherChat (Warzone messenger)"
|
description = "Core crypto & wire protocol for featherChat (Warzone messenger)"
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ async fn pwa_manifest() -> impl IntoResponse {
|
|||||||
|
|
||||||
async fn service_worker() -> impl IntoResponse {
|
async fn service_worker() -> impl IntoResponse {
|
||||||
([(header::CONTENT_TYPE, "application/javascript")], r##"
|
([(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'];
|
const SHELL = ['/', '/wasm/warzone_wasm.js', '/wasm/warzone_wasm_bg.wasm', '/icon.svg', '/manifest.json'];
|
||||||
|
|
||||||
self.addEventListener('install', e => {
|
self.addEventListener('install', e => {
|
||||||
@@ -242,7 +242,7 @@ let pollTimer = null;
|
|||||||
let ws = null; // WebSocket connection
|
let ws = null; // WebSocket connection
|
||||||
let wasmReady = false;
|
let wasmReady = false;
|
||||||
|
|
||||||
const VERSION = '0.0.17';
|
const VERSION = '0.0.22';
|
||||||
let DEBUG = true; // toggle with /debug command
|
let DEBUG = true; // toggle with /debug command
|
||||||
|
|
||||||
// ── Receipt tracking ──
|
// ── Receipt tracking ──
|
||||||
@@ -388,11 +388,12 @@ function loadSavedIdentity() {
|
|||||||
async function registerKey() {
|
async function registerKey() {
|
||||||
const fp = normFP(myFingerprint);
|
const fp = normFP(myFingerprint);
|
||||||
const bundleBytes = wasmIdentity.bundle_bytes();
|
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', {
|
await fetch(SERVER + '/v1/keys/register', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
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');
|
dbg('Key registered');
|
||||||
}
|
}
|
||||||
@@ -862,20 +863,14 @@ async function enterChat() {
|
|||||||
document.getElementById('hdr-server').textContent = SERVER;
|
document.getElementById('hdr-server').textContent = SERVER;
|
||||||
|
|
||||||
await registerKey();
|
await registerKey();
|
||||||
addSys('Identity loaded: ' + myFingerprint);
|
addSys('Identity: ' + myEthAddress);
|
||||||
|
addSys('Fingerprint: ' + myFingerprint);
|
||||||
addSys('Key registered with server');
|
addSys('Key registered with server');
|
||||||
|
|
||||||
// Fetch ETH address from server
|
if (myEthAddress) {
|
||||||
try {
|
document.getElementById('hdr-eth').textContent = myEthAddress.slice(0, 10) + '...';
|
||||||
const resolveResp = await fetch(SERVER + '/v1/resolve/' + normFP(myFingerprint));
|
document.getElementById('hdr-eth').title = myEthAddress;
|
||||||
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); }
|
|
||||||
|
|
||||||
addSys('v' + VERSION + ' | DM: paste peer fingerprint or @alias above');
|
addSys('v' + VERSION + ' | DM: paste peer fingerprint or @alias above');
|
||||||
addSys('/alias · /g · /gleave · /gkick · /gmembers · /glist · /friend · /file · /info');
|
addSys('/alias · /g · /gleave · /gkick · /gmembers · /glist · /friend · /file · /info');
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ impl WasmIdentity {
|
|||||||
Seed::from_bytes(self.seed_bytes).to_mnemonic()
|
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).
|
/// Get the pre-key bundle as bincode bytes (for server registration).
|
||||||
/// The bundle is generated once and cached. The SPK secret is stored internally.
|
/// The bundle is generated once and cached. The SPK secret is stored internally.
|
||||||
pub fn bundle_bytes(&mut self) -> Result<Vec<u8>, JsValue> {
|
pub fn bundle_bytes(&mut self) -> Result<Vec<u8>, JsValue> {
|
||||||
|
|||||||
Reference in New Issue
Block a user