Files
suaveplan/docs/drive/mesh-and-sharing.md
2026-05-27 23:29:53 +01:00

8.5 KiB

Drive Mesh and Sharing

This document covers two of the most-asked-about pieces of the drive layer:

  1. How n drives find each other and replicate chunks (drive-mesh).
  2. How files become shareable between specific drives (drive-sharing).

Both are plugins that compose with the chunks plugin and the network layer.

drive-mesh — per-file scheduling

drive-mesh makes a drive participate in a chunk-sharing mesh. It does not own peer identity or per-peer health — those live in network-plugin-registry. Mesh's job is, for one specific file:

  1. Advertise which chunks of which fileIds this drive holds (gzip-compressed bitset).
  2. Discover which peers hold the chunks this drive needs (read advertisements off the bus).
  3. Schedule a fetch plan: which chunks to pull from which peers, with parallel races and per-chunk timeouts.
  4. Score peers per file: a peer that gave you a bad signature gets -Infinity; a peer that consistently delivers fast bumps up.

Spec excerpts:

  • MESH-DEPS-01: mesh consumes ChunkSource from the chunks plugin; it does not import drive core.
  • MESH-SCHED-01..03: parallel-pull window, per-chunk timeout, server fallback.
  • MESH-SCORE-01: bad sig → score -Infinity permanently.
  • MESH-GOSSIP-01: bitset advertisements over the network bus.
  • MESH-FETCH-FILE-01: end-to-end happy path — mesh resolves, requests, verifies, hands plaintext up the chain.

The mesh fetch flow (one file)

drive-mesh.fetchFile(fileId)
   │
   ├── 1. fetch manifest (peer-first, server-fallback)
   ├── 2. local check: which chunks do we already have?
   ├── 3. for each missing chunk:
   │       ├── pick top-N candidates from per-peer score
   │       ├── race them; first verifying response wins
   │       ├── on bad sig: blacklist that peer, retry next candidate
   │       ├── on timeout: try the next candidate
   │       └── on full miss: fall through to drive-transport-https (server)
   ├── 4. verify each chunk's Ed25519 sig + AEAD tag
   ├── 5. reassemble; verify whole-file BLAKE3
   └── 6. emit `cd:advertise` so other peers know we now have these chunks

drive-mesh does NOT do

  • Peer discovery. That's the network discovery plugins (network-plugin-registry, network-discovery-*).
  • Mesh-wide redundancy (ensuring N copies of every chunk exist). That's network-plugin-content-delivery.
  • Cross-mesh relay. That's network-plugin-relay.
  • The actual transport. That's drive-transport-webrtc / drive-transport-https.

Mesh sits in the middle: scheduler for one specific file's chunks, leaving identity, redundancy, and bytes-on-wire to the layers around it.

drive-sharing — partitions and access control

drive-sharing is the access-control layer. The unit of access is a partition.

Mental model

Think of a drive as a tree of partitions. The root partition belongs to the drive's owner. Sub-partitions can be created and granted to other drives. Each partition has its own key-encryption-key (KEK); files inside a partition are encrypted with per-file fileKeys, each of which is wrapped with the partition's KEK.

drive (alice@example.com)
  └── root partition (KEK_root)
       ├── /personal/...                    (file keys wrapped by KEK_root)
       └── shared partition "wedding" (KEK_wedding)
            ├── /wedding/photo1.jpg         (file key wrapped by KEK_wedding)
            └── /wedding/photo2.jpg

Granting bob@example.com access to "wedding" means: rewrap KEK_wedding with bob's public key (X25519 sealed-box) and hand bob the wrapped blob. Bob's drive unwraps with its private key, caches KEK_wedding, and can now unwrap any fileKey in that partition.

Public API (from @suaveplan/drive-sharing proposal)

Component Role
PartitionManager Create/delete/list partitions. Each has an id and a KEK.
KeyRing Holds the unwrapped KEKs the current drive has access to.
WebCryptoProvider KEK wrap/unwrap. X25519 sealed-box plus AES-KW for symmetric rewrap.
GrantManager grant(partition, peerPubKey) / revoke(partition, peerPubKey).
TokenManager Time-limited share tokens (URL-safe) — give someone read access for 24h without exchanging keys.
TransferManager Move a file between partitions: rewrap its fileKey from the source KEK to the destination KEK.

What revocation actually does

Naive revocation is impossible after the fact: a peer who has already decrypted a chunk will always have the plaintext. Revocation in this design is forward:

  1. revoke(partition, bob) removes bob from the partition's authorized list.
  2. The next write() to that partition rotates KEK_wedding to KEK_wedding'.
  3. New fileKeys are wrapped with KEK_wedding'. Old fileKeys remain wrapped with KEK_wedding.
  4. Files written before revocation are re-keyed lazily: on first read after revocation, the drive rewraps the fileKey with the new KEK and re-uploads the wrap blob (not the data — just the small wrap).

Bob can read what he already cached. He cannot read anything new, including content uploaded after revocation. This is the standard envelope-encryption revocation pattern.

Tokens vs grants

  • Grants are per-peer-identity (X25519 public key). Revocable. Use for stable relationships.
  • Tokens are bearer (anyone with the token can use it for the validity window). Self-expiring. Use for one-off shares ("here's a link to the wedding photos for 24 hours").

TokenManager builds tokens server-side (or client-side with a server signature, depending on the deployment). The token contains the partition id, an expiry, and a wrapped KEK. The receiver's drive unwraps and uses it like any other KEK until expiry.

React UI

@suaveplan/drive-react-sharing ships:

  • <ShareDialog drive={drive} path={...} /> — full UI for granting/revoking/tokenizing.
  • <PartitionBadge> — show which partition a file belongs to.
  • <FileAccessList> — render the current grant list.
  • useSharing() — hook that exposes the imperative API.

It is a thin UI layer over @suaveplan/drive-sharing — no business logic of its own.

Composition — mesh + sharing + chunks

The four plugins (chunks, sharing, mesh, transport-*) compose cleanly because they each touch a different concern:

Plugin Concern Where it intercepts
chunks Confidentiality + integrity of bytes on disk and on wire drive:read (verify+decrypt), drive:write (encrypt+sign)
sharing Authorisation: which drives can read which partition drive:read (unwrap fileKey), drive:write (wrap fileKey)
mesh Where to fetch missing chunks drive:read (when local store misses)
transport-* Bytes on the wire to/from peers Below mesh; serves chunks-by-id

Chunks alone gives you encryption-at-rest. Chunks + sharing gives you encryption-at-rest with multi-drive access control. Chunks + sharing + mesh + transport gives you n drives that share files end-to-end-encrypted across a tiered network.

Cross-references with the network layer

The drive-network-integration change folder (proposal) is the canonical contract. Summary:

Direction Event Producer Consumer Meaning
Out cd:advertise drive-mesh network-plugin-content-delivery "I have these chunks of these fileIds"
Out cd:request drive-mesh network-plugin-content-delivery "Anyone got chunk X of file Y?"
In cd:response network-plugin-content-delivery drive-mesh "Here's the chunk" (verifies via chunks plugin)
In peer:joined/peer:left/peer:updated network-plugin-registry drive-mesh Peer identity lifecycle

drive-mesh knows nothing about which transport delivers a chunk. network-plugin-content-delivery knows nothing about which file a chunk belongs to. The bus carries content-addressed chunk requests; everything else is layer-local.

Reading further