Lowkey

Lowkey Documentation

Protocol Version 1.0.0

Chunk Transfer Protocol

This document specifies Lowkey's production-ready P2P chunk transfer system introduced in v0.2.7.

Overview

The chunk transfer protocol enables reliable file piece delivery between peers using:

  • DHT Provider Discovery - Locate peers that have specific chunks
  • Request-Response Protocol - Direct unicast chunk requests
  • CBOR Binary Encoding - Efficient serialization without base64 overhead
  • Gossipsub Fallback - Legacy broadcast for edge cases

Why Request-Response?

Previous versions used Gossipsub broadcast for chunk requests:

BEFORE (broken): Peer A -> Gossipsub Broadcast -> [ALL PEERS] -> Filter by "to" field -> Peer B Problem: Mesh takes 10-30 seconds to form, messages lost before mesh ready AFTER (reliable): Peer A -> DHT Lookup (providers) -> Direct Request -> Peer B -> Direct Response Fallback: Gossipsub broadcast if no providers found

Protocol Specification

Protocol Identifier

/lowkey/chunk/1.0.0

Message Framing

All messages use length-prefixed framing:

+------------------+-----------------------------+ | Length (4 bytes)| CBOR Payload | | Big-endian u32 | (variable length) | +------------------+-----------------------------+

ChunkRequest

Sent by seekers to request a specific chunk:

// CBOR Structure
{
    chunk_id: String  // BLAKE3 hash as 64 hex characters
}

// Size limit: 256 bytes

ChunkResponse

Sent by providers in response:

// CBOR Structure
{
    found: bool,           // true if chunk was found
    data: bytes,           // Raw binary chunk data (NOT base64)
    error: String | null   // Error message if not found
}

// Size limit: 300 KiB (chunk data + overhead)

Response Examples

Scenario found data error
Success true <binary chunk> null
Not found false [] "chunk not in store"
Seed node false [] "seed node does not store chunks"

DHT Provider Records

When sharing a file, peers announce themselves as providers for each chunk in the Kademlia DHT.

Provider Record Structure

// Key: provider:{chunk_id}
// Value (JSON):
{
    "providers": [
        {
            "peer_id": "12D3KooW...",
            "timestamp": 1704672000  // Unix seconds
        }
    ]
}

// TTL: 24 hours
// Maximum providers per chunk: 10 (LRU by timestamp)

Provider Announcement

// Called when sharing a file
for chunk_id in manifest.chunks {
    dht.announce_provider(&chunk_id).await?;
}

Fetch Algorithm

async fn fetch_chunk(chunk_id: &str) -> Result<Vec<u8>> {
    // 1. Check local cache
    if local_cache.has(chunk_id) {
        return local_cache.get(chunk_id);
    }

    // 2. Find providers via DHT
    let providers = dht.find_providers(chunk_id).await?;

    // 3. Try direct request to each provider (up to 3)
    for provider in providers.iter().take(3) {
        match dht.request_chunk(provider, chunk_id).await {
            Ok(data) if blake3::hash(&data).hex() == chunk_id => {
                local_cache.store(chunk_id, &data);
                return Ok(data);
            }
            Err(e) => continue,  // Try next provider
        }
    }

    // 4. Fallback: Gossipsub broadcast
    fetch_chunk_via_gossipsub(chunk_id).await
}

Gossipsub Fallback

When no providers are found or all direct requests fail:

async fn fetch_chunk_via_gossipsub(chunk_id: &str) -> Result<Vec<u8>> {
    // Subscribe to response topic
    subscribe("lowkey-chunk-resp");

    // Broadcast request
    publish("lowkey-chunk-req", {
        "t": "req",
        "from": my_peer_id,
        "chunk": chunk_id
    });

    // Wait for response (filtered by 'to' field)
    for message in receive_with_timeout(30s) {
        if message["to"] == my_peer_id && message["chunk"] == chunk_id {
            if message["ok"] {
                return base64_decode(message["data_b64"]);
            }
        }
    }

    Err(ChunkNotFoundError)
}

Performance Characteristics

Latency

Operation Typical Time
DHT provider lookup 50-200ms
Direct chunk request 100-500ms
Gossipsub fallback 5-30s (mesh formation)

Bandwidth Comparison

Encoding Overhead
CBOR (Request-Response) ~5%
JSON + base64 (Gossipsub) ~38%

Configuration

Parameter Value
Max concurrent chunk fetches 8
Request timeout 30 seconds
Providers to try 3
Provider TTL 24 hours