Privacy-first, key-based, peer-to-peer desktop messenger.
PRAGMA secure_delete = ON zero-fills freed pages so wiped messages and sessions don't linger on disk. Delete-contact propagates bilaterally — both copies vanish when the peer is next online./y7ke/sync/1.0.0 reconcile catches anything the queue lost.See docs/PLAN.md for the single source of truth (architecture,
milestone history, capability matrix, roadmap) and CHANGELOG.md
for what shipped per version.
rustup install stablenpm install -g pnpmcargo install tauri-cli --version "^2" --locked (only needed if you want cargo tauri dev; cargo build works without it)libwebkit2gtk-4.1-dev, libgtk-3-dev, libsoup-3.0-dev, librsvg2-devpnpm --dir ui install
git config core.hooksPath scripts/hooks # auto-bump version + CHANGELOG on commit
cargo tauri dev
beforeDevCommand spawns Vite, the Tauri shell attaches to it, and the
backend runs in debug. Right-click → Inspect Element opens WebKit
DevTools (network + console + Svelte state).
cargo tauri build # full bundle (deb / AppImage / dmg / msi)
cargo tauri build --no-bundle # just the binary at target/release/y7ke
cargo build --release -p y7ke-tauri --features custom-protocol # binary with embedded frontend, no tauri-cli required
The custom-protocol feature is what makes the binary serve the bundled
frontend over Tauri's asset protocol; without it, release binaries fall
back to the dev server URL (http://localhost:1420) and refuse to
connect.
Y7KE_DATA_DIR=/tmp/y7ke-alice ./target/release/y7ke &
Y7KE_DATA_DIR=/tmp/y7ke-bob ./target/release/y7ke &
mDNS discovers both within ~3 s. Paste one Y7 URI into the other's "Add contact", accept on the receiver, exchange messages.
cargo test --workspace # unit + integration
cargo test -p y7ke-app --test v1_stress -- --ignored # 3-client stress
mDNS-dependent tests are auto-skipped on macOS / Windows runners (GitHub Actions mDNS is unreliable); they run unconditionally on Linux and locally.
┌─────────────────────────────────────────────────────────────┐
│ src-tauri ← ui/ (Svelte 5) │
│ │ │
│ AppHandle │
│ ┌──────────────┬────┴────┬──────────────┐ │
│ │ │ │ │ │
│ y7ke-storage y7ke-net identity event_loop │
│ │ │ │ │
│ SQLite libp2p swarm: │ │
│ + DEK file TCP+Noise+Yamux │ │
│ + mDNS+ping+identify │ │
│ + 3 request_response │ │
└─────────────────────────────────────────────────────────────┘
| Layer | Crate | Responsibility |
|---|---|---|
| Types + crypto | y7ke-core |
Y7Id, MessageId (UUIDv7), ConversationId (blake3), Ed25519, X25519, ChaCha20-Poly1305, HKDF |
| Storage | y7ke-storage |
sqlx-sqlite, 8 tables, app-layer column encryption with master DEK |
| Networking | y7ke-net |
libp2p swarm, three custom request-response protocols: /y7ke/handshake/1.0.0, /y7ke/msg/1.0.0, /y7ke/sync/1.0.0 |
| Composition | y7ke-app |
Wires storage + net, runs the event loop, exposes the command API |
| Desktop shell | src-tauri |
Tauri 2 shell, command surface, event emission |
| Frontend | ui/ |
Svelte 5 + Vite + TypeScript |
<app_data>/y7ke/master.dek (file mode 0600).messages.payload_enc is byte-identical to what goes over /y7ke/msg/1.0.0.HKDF(X25519(my_static_x25519, peer_static_x25519), conv_id, "y7ke-conv-v1"), where both X25519 keys come from the long-term Ed25519 identity (SHA-512 + clamp on the seed; Edwards-to-Montgomery on the pubkey). The DH is symmetric, so both peers compute the same key.sessions table only records "handshake completed" — no key material. Stealing the SQLite file alone gives you 32-byte ciphertexts and nothing to decrypt them with.PRAGMA secure_delete = ON overwrites freed pages with zeros, so a wiped message or session can't be carved out of the database file.V1 was LAN-only via mDNS. From v0.1.20 the client also speaks
Kademlia DHT for peer lookup; from v0.1.43 it carries a libp2p
Circuit Relay v2 client so two peers behind NAT/CGNAT can talk
through a public bootstrap (now upgraded to direct via DCUtR — see
NAT traversal below). Discovery
chain on dial_with_discovery,
each step gated by the user's current dial modes:
peer_state.last_addrs (persisted across restarts, filtered by
active modes)find_peer via Kad against the configured bootstraps; results
include relay multiaddrs of the form
/dns4/<bootstrap>/.../p2p-circuit/p2p/<peer> once the peer has
reserved a slotEach client proactively reserves a /p2p-circuit slot at every
configured bootstrap on connect. The bootstrap forwards encrypted
frames only — it never sees plaintext (Noise + ChaCha20-Poly1305
wrap every byte before it leaves the client). A 15-second reconnect
tick redials any bootstrap that drops, so a single VPS restart
recovers in ~10 s instead of waiting for Kad's 5-minute periodic
bootstrap.
Bootstraps come from exactly two places (the Y7KE_BOOTSTRAP env var and the
bootstrap.toml config file were removed — peers are managed only in-app):
Settings::extra_bootstraps from the encrypted SQLite,
edited on the in-app settings :3 page. The transport-agnostic shorthand
(no /tcp or /udp segment, e.g. /dns/bootstrap1.y7v.lol/4101/p2p/12D3KooW…)
is auto-expanded by the client into both a TCP and a QUIC multiaddr; it dials
both and prefers QUIC. /dns resolves A and AAAA (IPv4+IPv6); /dns4,
/dns6, /ip4, /ip6 shorthands and explicit /tcp·/udp multiaddrs all
pass through. If empty:y7ke_net::DEFAULT_BOOTSTRAPS — one hardcoded node baked at build time,
currently /dns/bootstrap1.y7v.lol/4101/p2p/12D3KooWEVq9A1w4xk1paGxywwPNy4vz8D92wxE4XKBh8DpA8fSo
(Germany). Raw-IP fallback /ip4/89.35.130.67/4101/… if DNS isn't resolving.Either way the hardcoded DEFAULT_RELAY_BOOTSTRAP is always prepended
(deduped) and rendered readonly in Settings, so it can never be deleted and a
typo in the user list can never strand the client.
The standalone daemon lives at https://github.com/9sx77ssl/y7ke-bootstrap. One-line installer:
bash <(curl -sSL https://github.com/9sx77ssl/y7ke-bootstrap/raw/main/install.sh)
The daemon runs Kad + identify + ping + relay-server. v0.1.4+ requires
its public-facing multiaddrs declared via --external-addr or the
Y7KE_BOOTSTRAP_EXTERNAL_ADDR env (comma-separated). Without it,
libp2p sends reservation acks with an empty address list and clients
reject with NoAddressesInReservation.
Example systemd drop-in:
# /etc/systemd/system/y7ke-bootstrap.service.d/external.conf
[Service]
Environment=Y7KE_BOOTSTRAP_EXTERNAL_ADDR=/dns4/your-host.example/tcp/4101
The relay carries ciphertext frames only — operator gets no
metadata beyond relay: reservation accepted and circuit accepted
log lines.
From v0.1.43, click settings :3 in the sidebar to open the settings pane.
lan only and Y7net
(the Internet mode). lan only uses mDNS for same-WiFi peers;
Y7net enables Kad-resolved direct dial plus forwarding through
bootstrap relays (covers NAT/CGNAT).+ add bootstrap to add a blank row). ping all opens a TCP
connection to each (5 s budget) and shows the latency in a
pill — green ≤150 ms, amber otherwise, red on failure. save
persists to the encrypted SQLite and re-syncs the live swarm
without a restart.Changes propagate immediately: update_settings fires an
AppEvent::SettingsChanged and NetCommand::UpdateBootstraps,
the swarm task adds any new entries to its bootstrap_peers map
and dials them. Existing connections to removed bootstraps stay
open until they drop naturally.
Two peers across arbitrary NATs talk out of the box:
The chat header and the connectivity O.O pane show exactly how each
peer is reached — the full path, not just "online": e.g.
DIRECT · QUIC · IPv6 · via DCUtR (hole-punched off a relay) vs
RELAY · TCP. Hover for a plain-English tooltip ("hole-punched from a
relay to a direct path"). The pane adds the NAT verdict and the DCUtR
success rate; the bug icon by the logo copies a full diagnostics snapshot
that also includes a connection-lineage ring — the timestamped
sequence of every path change per peer (None→Relayed→Direct,
Direct→Relayed downgrades, →Offline) so a "it was fast, then went
slow" report is debuggable after the fact.
Field status (honest): the relay / QUIC / direct paths are proven on loopback and in a netns NAT simulation, and a QUIC relay reservation against the production bootstrap is verified live. A two-machine / two-ISP confirmation of a real cross-NAT QUIC hole-punch is the one thing still pending.
docs/PLAN.md — single source of truth: architecture,
milestone history, capability matrix (PROVEN/SIMULATED/UNVERIFIED/PLANNED),
security model, transport stack, known limitations, roadmap, and the live
cross-NAT smoke procedureCHANGELOG.md — per-version diffMIT OR Apache-2.0 at your option.