Core Concepts
Understand the fundamentals.
The vocabulary you need to read the rest of the docs.
Convergent encryption
Every file is encrypted with a key derived deterministically from its plaintext (HMAC-SHA256 + AES-256-GCM with the nonce derived from the same hash). Property: identical input → identical ciphertext → automatic deduplication across users + retransmission safety.
The trade-off: an attacker who already has a candidate plaintext can verify whether you've stored it. For most threat models (your photos, your documents) that's fine; for "is this person storing X" attacks specifically you'd want random-nonce mode (planned, not shipped).
Replication factor
Each vault has replication_factor: N. The mesh keeps trying to land
each block on at least N reliable peers through periodic gossip. RF
is achieved per-block, not per-vault — small files don't pay the cost
of large ones.
RF belongs to the vault, not to a node: it is a statement about how safe the data should be, made by whoever owns it. The value travels with the catalog, so every node that holds the vault reads the same number — including nodes with no key, which have no other way to learn it. If two people set it, the later setting wins.
A reliable node is one whose config declares reliable: true. Phones
default to false — they help seed but don't count.
Storage role
How much of the mesh's data this machine is willing to keep, set with
node.storage_role. It is the operator's call, local to the box, and
agreed with nobody.
| Role | What it does |
|---|---|
contribute (default) |
Helps each vault reach its replication factor, and stops there. |
hoard |
Keeps a copy of everything it has room for, RF met or not. The backup box. |
access-only |
Keeps nothing on anyone else's behalf. Files still download when you open them. |
hoard still honours a vault's replication_factor: 0 — that is the
owner saying "don't replicate this", not the operator saying "I have
room".
Capacity pooling
A vault's usable space is the sum of the free disk of every node willing to hold it, not one node's. Holding needs no key: a holder verifies a content hash, stores ciphertext and serves the same bytes back, so a €5/month VPS counts toward the pool exactly like your NAS does. Upload through a node that's full and the new file's blocks are placed on a peer that has room (chosen by free space); that peer becomes the holder. You get "out of space" only when the whole reachable mesh is full. The node you wrote through still lists and serves the file — it fetches the blocks from the holder on demand.
This pools capacity across files. And when a single file is too big for
any one node, it's sharded: split into contiguous runs of blocks
that are placed on different nodes, so one object can exceed any single
disk as long as the mesh holds it collectively. Small files that fit on one
node aren't sharded — sharding kicks in only when nothing else fits. Reads
gather each shard's blocks from whichever node holds them, transparently.
Tune the shard size with node.shard_size_bytes (default 1 GiB; set
negative to disable and fall back to "out of space" for oversize files).
Only blocks-mode vaults pool — a vault with a storage_path keeps its
plaintext locally as the source of truth, so its writes stay on that node.
Tune placement with node.placement_mode (local-first default, or
balanced to spread every file across the mesh from the start).
Block cache
A node fetches blocks it doesn't hold all the time — serving a file
whose bytes were placed on a peer, streaming from a vault it keeps
nothing of, answering for someone behind NAT. node.cache_bytes gives
those blocks somewhere to land so the next read doesn't go back to the
holder. Off by default.
It is a separate store with a separate ceiling, and both halves of that matter:
- A cached block is never counted as a copy and never announced. A node that advertised one would evict it by LRU and leave the mesh believing a copy exists.
- A block the node holds for real is never also cached — that would budget the same bytes twice and let a cache eviction delete a replica.
- Under disk pressure the cache is dropped whole, before any replica is considered. A cached block is a guess about the future; a replica is a promise. One shared ceiling would eventually produce a node that refused a copy because its cache was full.
Blocks are content-addressed ciphertext, so the cache needs no vault key and a hit is verified by hashing — a wrong or corrupted block is caught rather than served. Admission is "second time asked, not first", which is what stops a bulk transfer of hundreds of thousands of one-shot blocks from flushing everything worth keeping.
The hit rate is on the settings panel, and worth actually looking at: a cache pays off only when the same block passes the same node more than once, and on a mesh where readers reach holders directly it may not. It also tells whoever runs the node which blocks are popular — not linkable across vaults, since different keys make unrelated bytes, but the access pattern of the vault whose traffic it carries is visible to it.
Trusted vs. untrusted
Trust = "does this node have the vault key". Untrusted nodes happily store ciphertext blocks and participate in replication; they never see plaintext. This is what lets a €5/month VPS be redundancy without needing to trust the provider.
Trust answers what a node may read, and nothing else. It does not gate holding, being routed a file, or counting toward RF. A node does not even need the vault in its config: the first time a peer routes it a file, it writes the vault down itself — keyless — so the blocks have an owner that the eviction, quota and catalog machinery can see. Whether it accepts is its storage role's answer, not the key's.
Vaults, blocks, hash-chains
- Vault = a name + a key + a policy (RF, storage path, type).
- Block = a chunk of ciphertext addressed by SHA-256. Sizes: ≤ 512 KiB for small files, 4 MiB for large.
- Hash-chain versioning = each new version of a file carries a
parent_hashpointer to the previous version. Same file edited on two nodes simultaneously → the mesh detects the fork on next gossip. Whether old versions stay restorable is a per-node decision: a folder-backed node keeps them withkeep_history: true(storing real ciphertext blocks alongside the plaintext), bounded by ahistory_retentionpolicy (keep all / last N versions / last N days / staggered Syncthing-style thinning). So a phone can stream files, a laptop hold a plain copy, and a NAS keep the deep history — all of the same vault. - Ignore rules = the folder scanner skips junk and VCS files by
default (dotfiles,
Thumbs.db, editor scratch) and honours per-vault gitignore-style patterns viaignore_globsor a.holdignorefile in the folder.
Mesh topology + gossip
There is no central directory. Every node holds a partial view of the
mesh, gossipped via the /meshhold/hello/1.0 protocol. A first
handshake brings back every other node currently online — any of them
can be your bootstrap on the next reconnect.
Three encryption layers
- Swarm key — 256-bit shared secret; without it, nodes can't even open a libp2p connection.
- Per-connection Noise session key — fresh on every TCP stream, forward-secret, ChaCha20-Poly1305 AEAD.
- Per-vault / chat / tunnel / agent content key — independently shareable; one capability doesn't unlock another.
See the diagram on the home page for the visual.