Files
wz-phone/docs/WZP-FC-SHARED-CRATES.md
Siavash Sameni 616505e8a9 docs: shared crate strategy for WZP ↔ featherChat interop
Defines 5 tasks (FC-CRATE-1/2/3, WZP-CRATE-1/2) to make both
projects' crates importable by each other:

featherChat side:
- FC-CRATE-1: Make warzone-protocol standalone (replace workspace deps)
- FC-CRATE-2: Add CallSignal variant using wzp-proto types
- FC-CRATE-3: Extract warzone-identity micro-crate (optional)

WZP side (after FC-CRATE-1):
- WZP-CRATE-1: Replace identity mirror with real warzone-protocol dep
- WZP-CRATE-2: Verify wzp-proto works as git dep from featherChat

Priority: FC-CRATE-1 first (30 min, unblocks everything).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 09:14:25 +04:00

7.3 KiB

Shared Crate Strategy: WZP ↔ featherChat

Goal: Both projects import each other's crates directly instead of duplicating code. A change to identity derivation in featherChat automatically applies in WZP, and vice versa for call signaling types.


Current Problem

  • warzone-protocol uses workspace dependency inheritance (Cargo.toml has ed25519-dalek.workspace = true). When WZP tries to use it as a path dep, Cargo fails because it can't resolve workspace references from outside the featherChat workspace.
  • WZP had to mirror featherChat's identity.rs, mnemonic.rs, and Fingerprint type in wzp-crypto/src/identity.rs — duplicate code that can drift.
  • featherChat will need wzp_proto::SignalMessage for the WireMessage::CallSignal variant — another potential duplication.

Solution: Make Key Crates Standalone-Importable

What featherChat Needs to Do

FC-CRATE-1: Make warzone-protocol standalone-publishable

File: warzone/crates/warzone-protocol/Cargo.toml

Replace all workspace = true references with explicit versions:

# Before:
ed25519-dalek.workspace = true
x25519-dalek.workspace = true

# After:
ed25519-dalek = { version = "2", features = ["serde", "rand_core"] }
x25519-dalek = { version = "2", features = ["serde", "static_secrets"] }
chacha20poly1305 = "0.10"
hkdf = "0.12"
sha2 = "0.10"
rand = "0.8"
bip39 = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
bincode = "1"
thiserror = "2"
hex = "0.4"
base64 = "0.22"
uuid = { version = "1", features = ["v4"] }
zeroize = { version = "1", features = ["derive"] }
chrono = { version = "0.4", features = ["serde"] }
k256 = { version = "0.13", features = ["ecdsa", "serde"] }
tiny-keccak = { version = "2", features = ["keccak"] }

Keep workspace inheritance working too by using the [package] fallback pattern:

[package]
name = "warzone-protocol"
version = "0.0.20"
edition = "2021"
# Remove version.workspace and edition.workspace — use explicit values

This way the crate still works inside the featherChat workspace AND can be imported by WZP as a path dependency.

Test: From the WZP repo, this should work:

# In wzp-crypto/Cargo.toml:
warzone-protocol = { path = "../../deps/featherchat/warzone/crates/warzone-protocol" }

Effort: 30 minutes. Mechanical replacement, then cargo build to verify.

FC-CRATE-2: Add wzp-proto as a git dependency for CallSignal

File: warzone/crates/warzone-protocol/Cargo.toml

[dependencies]
# WarzonePhone signaling types (for CallSignal WireMessage variant)
wzp-proto = { git = "ssh://git@git.manko.yoga:222/manawenuz/wz-phone.git", optional = true }

[features]
default = []
wzp = ["wzp-proto"]

File: warzone/crates/warzone-protocol/src/message.rs

#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum WireMessage {
    // ... existing variants ...

    /// Voice/video call signaling (requires "wzp" feature).
    #[cfg(feature = "wzp")]
    CallSignal {
        id: String,
        sender_fingerprint: String,
        signal: wzp_proto::SignalMessage,  // Typed, not opaque bytes
    },

    /// Voice/video call signaling (without wzp feature — opaque bytes).
    #[cfg(not(feature = "wzp"))]
    CallSignal {
        id: String,
        sender_fingerprint: String,
        signal: Vec<u8>,  // Opaque JSON bytes
    },
}

Alternative (simpler): Always use Vec<u8> for the signal field and let the consumer deserialize. This avoids the feature flag complexity:

CallSignal {
    id: String,
    sender_fingerprint: String,
    signal_json: String,  // JSON-serialized wzp_proto::SignalMessage
},

featherChat server treats it as opaque. WZP client deserializes it to SignalMessage.

Effort: 1-2 hours.

FC-CRATE-3: Extract shared identity types to a micro-crate (optional, long-term)

Create warzone-identity crate containing only:

  • Seed (generation, from_bytes, from_hex, from_mnemonic, to_mnemonic)
  • IdentityKeyPair (derive from seed)
  • PublicIdentity (verifying key, encryption key, fingerprint)
  • Fingerprint (SHA-256 truncated, display format)
  • hkdf_derive() helper

Both warzone-protocol and wzp-crypto depend on warzone-identity instead of each implementing their own. This is the cleanest long-term solution but requires more refactoring.

Crate structure:

warzone-identity/
├── Cargo.toml  (standalone, no workspace inheritance)
├── src/
│   ├── lib.rs
│   ├── seed.rs
│   ├── identity.rs
│   ├── fingerprint.rs
│   └── mnemonic.rs

Dependencies: ed25519-dalek, x25519-dalek, hkdf, sha2, bip39, hex, zeroize

Both projects import it:

# featherChat:
warzone-identity = { path = "../warzone-identity" }

# WZP (via submodule):
warzone-identity = { path = "deps/featherchat/warzone-identity" }

Effort: Half a day. Extract code from warzone-protocol, update imports in both projects.


What WZP Needs to Do (after featherChat completes FC-CRATE-1)

WZP-CRATE-1: Replace identity mirror with real dependency

Once warzone-protocol is standalone-importable:

File: crates/wzp-crypto/Cargo.toml

# Remove bip39 and hex (now comes from warzone-protocol)
# Add:
warzone-protocol = { path = "../../deps/featherchat/warzone/crates/warzone-protocol" }

File: crates/wzp-crypto/src/identity.rs Replace the entire file with re-exports:

//! featherChat identity — re-exported from warzone-protocol.
pub use warzone_protocol::identity::{IdentityKeyPair, Seed};
pub use warzone_protocol::types::Fingerprint;

File: crates/wzp-crypto/src/handshake.rs Use warzone_protocol::identity::Seed internally instead of raw HKDF calls.

Effort: 1 hour (after FC-CRATE-1 is done).

WZP-CRATE-2: Make wzp-proto standalone-importable

wzp-proto already has explicit dependency versions (not workspace-inherited for external deps). It should work as a git dependency from featherChat. Verify:

# From a scratch project:
cargo add --git ssh://git@git.manko.yoga:222/manawenuz/wz-phone.git wzp-proto

If this fails, replace any remaining workspace references in wzp-proto/Cargo.toml with explicit versions.

Key types featherChat needs from wzp-proto:

  • SignalMessage (CallOffer, CallAnswer, IceCandidate, Hangup, etc.)
  • QualityProfile (for codec negotiation)
  • HangupReason

Effort: 30 minutes to verify and fix.


  1. FC-CRATE-1 — Make warzone-protocol standalone (30 min, unblocks everything)
  2. WZP-CRATE-2 — Verify wzp-proto works as git dep (30 min)
  3. FC-CRATE-2 — Add CallSignal with opaque signal_json field (1-2 hours)
  4. WZP-CRATE-1 — Replace identity mirror with real dep (1 hour)
  5. FC-CRATE-3 — Extract warzone-identity micro-crate (optional, half day)

After steps 1-4, both projects share types directly:

  • WZP imports warzone-protocol for identity/seed/fingerprint
  • featherChat imports wzp-proto (via git) for SignalMessage types
  • No duplicated code, no drift risk

Dependency Graph After Integration

warzone-identity (shared micro-crate, optional step 5)
    ↑                    ↑
warzone-protocol         wzp-crypto
    ↑                    ↑
warzone-server      wzp-proto ← wzp-codec, wzp-fec, wzp-transport
    ↑                    ↑
warzone-client      wzp-client, wzp-relay, wzp-web