A technical overview of the networking, encryption, and sync mechanisms. For the full design rationale, see the design document.
ItsGoin uses iroh (v0.96) for peer-to-peer networking. iroh provides QUIC transport with built-in NAT traversal, hole punching, and mDNS LAN discovery. Every node has a persistent ed25519 identity key.
| Type | Slots | Lifetime | Purpose |
|---|---|---|---|
| Mesh | 101 (desktop), 15 (mobile) | Long-lived | Structural backbone for routing and propagation |
| Session | 20 (desktop), 5 (mobile) | Minutes | Active interactions: DMs, delivery, peer discovery |
| Ephemeral | Unlimited | Single request | One-off sync or blob fetch |
Desktop nodes maintain 101 mesh connections divided into:
New mesh connections require bilateral agreement: the connector sends an InitialExchange message, and the acceptor either grants a slot or sends RefuseRedirect with an alternative peer suggestion.
Nodes reactively grow their mesh using diversity scoring. When a slot opens, the node evaluates N2 candidates (peers reported by mesh connections) and selects the one that maximizes reach into new parts of the network. The scoring formula:
score = 1/reporter_count + 0.3 if not_already_in_N3
Low reporter count means fewer of your existing peers know this candidate — connecting to them brings the most new reach. The growth loop backs off after 3 consecutive failures to avoid wasting resources on unreachable candidates.
Each node has three layers of network awareness, built passively from mesh peer exchanges:
| Layer | Contains | Source | Shared? |
|---|---|---|---|
| N1 (Mesh) | Live connections + social contacts | Direct | Yes (merged, no addresses) |
| N2 (Reach) | Peers' N1 shares | 1 hop | Yes (NodeIds only) |
| N3 (Search) | Peers' N2 shares | 2 hops | Never |
Privacy guarantee: N1 shares merge mesh peers with social contacts into one list, making it impossible for outsiders to distinguish infrastructure connections from social relationships. Addresses are never shared — they're resolved on-demand through the chain.
Discovery cascade when connecting to a specific peer:
ItsGoin uses 1-layer envelope encryption for all private content. The design preserves content addressing (PostId = BLAKE3 of ciphertext) while allowing visibility updates without changing the PostId.
Each private post gets a random Content Encryption Key (CEK). The post body is encrypted with ChaCha20-Poly1305 using this CEK. The CEK is then wrapped separately for each intended recipient using X25519 Diffie-Hellman derived from ed25519 identity keys.
PostId = BLAKE3(ciphertext)
CEK = random 256-bit key
ciphertext = ChaCha20-Poly1305(post_body, CEK)
wrapped_key[i] = X25519_DH(author_ed25519, recipient_ed25519[i]) XOR CEK
Visibility is separate metadata — re-wrapping the CEK for new recipients doesn't change the PostId or require re-encrypting the content.
Circles (private groups) use per-circle ed25519 keypairs instead of per-recipient wrapping. A single DH operation wraps the CEK for the entire group, keeping overhead constant regardless of group size (~100 bytes vs. ~500 bytes per recipient).
Epoch rotation: when a member is removed, the circle generates a new keypair (new epoch). The new seed is distributed to remaining members. Old posts remain readable with old keys; new posts use the new epoch. Forward secrecy without re-encrypting history.
| Level | Encryption | Audience |
|---|---|---|
| Public | None | Anyone |
| Friends | Per-recipient CEK wrapping | Mutual follows |
| Circle | Group key CEK wrapping | Circle members |
| Direct | Per-recipient CEK wrapping | Specified recipients |
The protocol uses a single ALPN (distsoc/3) with 37+ message types multiplexed over QUIC bi-streams and uni-streams.
Every 5 minutes, nodes pull posts from connected peers. The sender filters posts before sending — only posts the requester is authorized to see (based on follows, encryption recipients, and audience membership). This means the requester never learns about posts they can't decrypt.
Real-time push via uni-streams for instant delivery:
Files are stored as content-addressed blobs: BlobId = BLAKE3(file_bytes). Stored on the filesystem in 256 shards (blobs/{hex[0..2]}/{hex}) with metadata in SQLite. Max 4 attachments per post, 10MB each.
Each blob has a hosting tree: 1 upstream source + up to 100 downstream nodes. AuthorManifest (ed25519-signed) carries the author's 10 most recent posts as a neighborhood, traveling with blob responses. ManifestPush propagates updates down the tree.
When a node evicts a blob, it sends BlobDeleteNotice so the tree can heal — downstream nodes find a new upstream.
When storage fills up, blobs are evicted using a social-aware scoring formula:
priority = pin_boost + relationship * recency * freshness / (peer_copies + 1)
Your own blobs are auto-pinned and never evicted. Blobs from people you follow score higher. Blobs with many copies elsewhere score lower (safe to drop).
Anchors are standard ItsGoin nodes running on stable servers. They're regular peers that happen to be always on, which makes them useful for two things:
Anchors maintain a referral list of recently-seen peers with tiered usage caps (3/2/1 uses depending on list size). When an anchor's mesh is full, it keeps session connections for matchmaking so it remains useful as a bootstrap point.
Anchors run the same code as every other node. No special protocol, no special trust, no special services. They're just peers that are always on.
| Component | Technology |
|---|---|
| Core library | Rust |
| P2P networking | iroh 0.96 (QUIC + mDNS) |
| Local storage | SQLite (rusqlite 0.32) |
| Content addressing | BLAKE3 |
| Encryption | ChaCha20-Poly1305 + X25519 (from ed25519) |
| Desktop/mobile shell | Tauri v2 |
| Frontend | Plain HTML/CSS/JS (no build step) |
| Platforms | Android, Linux (AppImage) |