Data Flow
How events travel through the Gozzip network.
Publishing a Post
User (device key)
│
├─ Signs kind 1 event with device subkey
├─ Adds root_identity tag pointing to root pubkey
│
└─► Relay
├─ Stores event
├─ Indexes by root_identity tag
└─ Delivers to subscribers of root_pubkey
(relay resolves device → root via kind 10050)
Subscribing to a Feed (Full Node)
Client subscribes to root_pubkey
│
└─► Relay
├─ Fetches kind 10050 for root_pubkey → [device_pubkey_1, device_pubkey_2]
├─ Fetches events where pubkey IN [device_pubkeys]
└─ Returns unified feed attributed to root identity
Subscribing to a Feed (Light Node)
Client subscribes to root_pubkey
│
└─► Relay
├─ Fetches kind 10051 (checkpoint) for root_pubkey
├─ Gets head event + sequence per device
└─ Returns only events AFTER each device's head
(last N events, not full history)
Direct Message Flow
Sender (device key)
│
├─ Encrypts content with NIP-44 to recipient's dm_key (from kind 10050)
├─ Wraps in NIP-59 sealed gift (metadata privacy)
├─ Signs outer event with device subkey
│
└─► Relay
└─► Recipient
├─ All devices can decrypt (all hold derived DM privkey)
└─ root_identity tag identifies sender
Follow-as-Commitment Indexing
User A follows User B (kind 3 updated)
│
├─ User A's node begins indexing User B's events
├─ User A's node indexes User B's connections (1st degree)
│
└─ Discovery beyond 1st degree:
├─ Possible through web of trust traversal
└─ Not instant — requires relay queries
Checkpoint Reconciliation Flow
Device A comes online
│
├─ Fetches kind 10050 → [device_B_pubkey, device_C_pubkey]
├─ Fetches kind 10051 → last known heads per device
│
├─ Queries relay: events from B, C since their last known heads
│ └─ Gets B1, B2 (new from B), C1 (new from C)
│
├─ Publishes new kind 10051 (checkpoint_delegate):
│ device A head = A_latest
│ device B head = B2
│ device C head = C1
│
└─ Done. Observers see unified timeline ordered by created_at.
Replaceable Event Fork Detection and Merge
Device A offline Device B offline
│ │
├─ Updates kind 3 (follow list) ├─ Updates kind 3 (follow list)
│ prev = event_X │ prev = event_X ← same ancestor
│ adds [Dave] │ adds [Eve], removes [Bob]
│ │
├─────── both come online ───────────┤
│ │
Device A (or B) detects fork:
Two kind 3 events with prev = event_X
│
├─ Computes merge:
│ ancestor tags: [Alice, Bob, Carol]
│ A's version: [Alice, Bob, Carol, Dave]
│ B's version: [Alice, Carol, Eve]
│ merged: [Alice, Carol, Dave, Eve]
│
├─ Publishes merged kind 3:
│ prev = [A's event_id, B's event_id] ← points to both fork tips
│
└─ All devices now see same follow list.
Read-State Sync Flow
User reads DM conversation with Bob on mobile
│
├─ Mobile publishes kind 10052:
│ d = Bob's root pubkey
│ read_until = 1709142000
│ content = NIP-44 encrypted to own root pubkey
│
└─► Relay stores (parameterized replaceable, keyed by d tag)
Desktop comes online
│
├─ Fetches kind 10052 where d = Bob's pubkey
├─ Decrypts content → read_until = 1709142000
├─ Marks Bob's messages up to that timestamp as read
│
└─ If desktop reads further (to timestamp 1709143000):
└─ Publishes new kind 10052 with read_until = 1709143000
(replaces mobile's version — later read_until always wins)
Storage Pact Formation
User needs storage partners
│
├─ Publishes kind 10055 (storage pact request):
│ volume = 50MB, min_pacts = 5
│
├─ WoT peers with similar volume see the request
│ └─ Peer responds with kind 10056 offer
│
├─ User selects partner
│ ├─ Both exchange kind 10053 (private, via encrypted DM)
│ └─ Both begin storing each other's events from current checkpoint
│
└─ Repeat until min_pacts fulfilled
Client behavior adapts with pact count: bootstrap phase (0–5, relay-primary), hybrid (5–15, mixed), sovereign (15+, peer-primary). See Storage > Three-Phase Adoption.
Storage Peer Retrieval
Bob wants Alice's events (Alice's devices are offline)
│
├─ Publishes kind 10057 (data request):
│ bp = H(Alice's pubkey || YYYY-MM-DD)
│ since = last known checkpoint
│
├─ Alice's storage peers are listening for requests
│ about pubkeys they store
│ └─ Peer responds privately with kind 10058 (data offer)
│ relay = connection endpoint
│
├─ Bob connects to responding peer
│ └─ Fetches Alice's events since checkpoint
│
├─ Bob verifies every event signature
│ (self-authenticating — standard Nostr verification)
│
└─ Fallback: try relays if no storage peers respond
Gossip Discovery Flow
Bob wants Alice's events
│
├─ Layer 0: BLE mesh (if enabled)
│ ├─ Check if any nearby devices have Alice's events via BLE
│ ├─ Noise Protocol encrypted session between mesh peers
│ ├─ Multi-hop relay up to 7 hops through intermediate devices
│ ├─ Interop with bitchat mesh network
│ └─ If BLE responds → done
│
├─ Layer 1: Check cached endpoints
│ ├─ Bob has kind 10059 from Alice with peer endpoints
│ │ (10059 is WoT-scoped — only sent to followers within 2 hops)
│ ├─ Connect directly to cached peers
│ └─ If cached endpoints respond → done
│
├─ Layer 2: Gossip (hardened — see ADR 008)
│ ├─ Send kind 10057 to directly connected peers (TTL=3)
│ │ └─ Include request_id tag for deduplication
│ ├─ Each peer receiving a request:
│ │ ├─ Check request_id against LRU cache → drop if duplicate
│ │ ├─ Check source pubkey rate limit (50 req/s) → drop if exceeded
│ │ ├─ Check if source is within 2-hop WoT → serve locally but don't forward if not
│ │ ├─ Check if they can respond (have the data)
│ │ └─ If not, decrement TTL and forward to peers
│ │ └─ Priority: pact partners first, then WoT, then extended WoT (ADR 009)
│ ├─ Reaches ~8,000 nodes in a 20-peer network (20^3)
│ ├─ Responses route back along the same path
│ └─ If gossip responds → done
│
└─ Layer 3: Relay fallback
├─ Traditional DVM broadcast via relay
└─ Existing kind 10057/10058 flow
Storage Challenge-Response
Alice verifies Bob holds her data (periodic, jittered)
│
├─ Sends kind 10054 (storage challenge):
│ challenge = random nonce
│ range = events [47..53]
│
├─ Bob computes H(events[47..53] || nonce) from local copy
│ └─ Responds with hash
│
├─ Alice verifies hash against her own copy
│ ├─ Match → Bob has the data, pact continues
│ └─ Mismatch or timeout:
│ ├─ Retry once (network issues)
│ ├─ Ask other storage peers for same range
│ ├─ If others have it → Bob's problem → 48h grace
│ └─ Grace expires → drop pact, broadcast kind 10055 for replacement
│
└─ Bob loses Alice's reciprocal storage (natural consequence)
Completeness Verification
Bob fetches Alice's events from a storage peer
│
├─ Fetches Alice's latest checkpoint (kind 10051)
│ merkle_root = abc123, event_count = 100
│
├─ Receives 100 events from storage peer
│
├─ Computes Merkle root of received events
│ ├─ Computed root matches checkpoint → complete set ✓
│ └─ Mismatch or count ≠ 100 → peer is withholding events ✗
│ └─ Try another storage peer or relay
│
└─ Signatures verified + Merkle root matches = authentic AND complete
Per-Event Chain Verification
Bob receives events from Alice's device
│
├─ Check sequence numbers
│ ├─ Events: seq 44, 45, 46, 48 ← gap at 47
│ └─ Missing event detected without checkpoint
│
├─ Check prev_hash chain
│ ├─ Event 45: prev_hash = H(event_44.id) ✓
│ ├─ Event 46: prev_hash = H(event_45.id) ✓
│ └─ Mismatch → tampered or reordered chain
│
└─ Combined with checkpoint:
├─ Per-event chain → single-device stream completeness
└─ Checkpoint Merkle root → cross-device verification
Social Recovery Flow
Setup: User designates recovery contacts
│
├─ Publishes kind 10060 for each recovery contact:
│ d = recovery_contact_root_pubkey
│ threshold = 3, total = 5
│ content = NIP-44 encrypted instructions
│ signed by root key
│
└─ Each contact receives and stores their 10060 (encrypted to them)
Recovery: User loses root key
│
├─ User generates new root keypair
│
├─ Contacts N recovery contacts out-of-band (phone, in person, etc.)
│ └─ Each contact verifies user's identity
│
├─ Each cooperating contact publishes kind 10061:
│ p = old_root_pubkey
│ new_root = new_root_pubkey
│ timelock_start = now
│
├─ N valid attestations exist → 7-day timelock begins
│ │
│ ├─ During timelock:
│ │ ├─ Old root key can cancel (proves not actually lost)
│ │ └─ Clients show "recovery in progress" for the identity
│ │
│ └─ After 7 days with no cancellation:
│ ├─ Clients accept new root key as identity successor
│ ├─ New root key publishes kind 10050 with device delegations
│ └─ Identity continues under new root key
Cascading Read-Cache Flow
Bob fetches Alice's events from storage peer S1
│
├─ Bob now has a local copy of Alice's recent events
│
├─ Carol broadcasts kind 10057 for Alice's data
│ └─ Bob's client sees the request (has Alice's events cached)
│
├─ Bob responds with kind 10058 (data offer)
│ └─ Carol fetches Alice's events from Bob
│
└─ Carol verifies Alice's signatures — source doesn't matter
└─ Popular data naturally replicates across the follower base
- No pact needed — Bob serves cached data he already has
- Load scales with O(followers), not O(storage_peers)
- See Storage > Cascading Read-Caches
Nearby Discovery Flow
User activates nearby mode (inspired by bitchat — https://github.com/permissionlesstech/bitchat)
│
├─ Client generates ephemeral keypair:
│ NOT derived from root key
│ NOT listed in kind 10050
│ NOT linked to any persistent identity
│
├─ Broadcasts presence via BLE mesh and/or local gossip:
│ Geohash tag ["g", "<geohash>"] at user-chosen precision
│ No root_identity tag — completely anonymous
│
├─ Discovers other nearby users in nearby mode:
│ └─ Sees ephemeral-key-signed content with geohash tags
│
├─ Optional: private identity reveal
│ ├─ Encrypted NIP-44 exchange via ephemeral keys
│ ├─ Each side reveals their root pubkey
│ └─ Both can follow each other via normal Gozzip identity
│
└─ Deactivate nearby mode → ephemeral key discarded, no trace
Lightning Boost Flow
User wants to boost a post through a discovery relay
│
├─ Sends kind 9734 (zap request) to relay's root pubkey:
│ amount = relay's boost price
│ e = event_id of the post to boost
│
├─ LNURL server processes payment
│ └─ Publishes kind 9735 (zap receipt)
│
├─ Relay detects zap receipt for its pubkey:
│ ├─ Verifies payment amount meets boost threshold
│ ├─ Adds event to curated feed, marked as boosted
│ └─ Pushes to relay subscribers
│
└─ Subscribers see the post in relay's curated feed
└─ Boosted content is transparently marked
Group Message Flow
User (device key)
│
├─ Signs kind 9 event with device subkey
├─ Adds root_identity tag
│
└─► Group relay
├─ Validates membership against root identities (not device keys)
└─ Delivers to group members