How Varsto works
The technology behind Varsto, one mechanism at a time: how a file becomes encrypted blocks, which keys exist, how the signed ledger keeps track of every copy, how devices sync and find each other, and what is not done yet.
Everything here is implemented in the source-available core (crates/varsto-core) and described with exact field layouts in the storage format. It is alpha software: formats may still change, and neither the design nor the code has been audited. The threat model and the failure model say what it does and does not defend against.
Overview

Varsto has two kinds of parts, and nothing of ours in between.
- Your devices (laptops, desktops, phones) hold the keys. They cut files into blocks, encrypt and decrypt, and run the sync in a background service.
- Your storages hold ciphertext only: an S3-compatible bucket, any rclone remote, a NAS or network share, a local or removable disk, or a pool of disks on a shelf. Varsto needs nothing from them beyond put, get, list and delete.
Every storage holds the same three kinds of object: encrypted blocks of file content, each device's encrypted file lists (manifests), and each device's signed ledger, plus a few small encrypted records for devices, folders and policies. Every path has exactly one writer: a device writes only under its own id, and blocks are named by the hash of their ciphertext and never overwritten. Storages therefore need no locks, and each one doubles as a mailbox: devices that are never online at the same time still converge, as long as they share one storage. Devices can also exchange blocks directly, on the LAN or across the internet.
There is no Varsto account, server or relay. If the project stopped tomorrow, your installed devices would keep syncing through your storages.
From file to encrypted blocks

- Chunking. The file is cut into content-defined chunks whose size follows the file: about 256 KiB for files under 32 MiB, 1 MiB up to 1 GiB, 4 MiB above (a rolling hash picks the cut points), so an edit in the middle changes a few chunks, not the whole file.
- Identity. Each chunk gets an id: a BLAKE3 keyed hash of its plaintext under the folder's dedup-hash key. The same content in the same folder has the same id on every device, which makes deduplication work offline; the key stops anyone without it from testing whether you hold a known file.
- Compression. The chunk is compressed with zstd when that saves at least 5 %. The level is a format constant, so every device produces the same bytes for the same chunk.
- Encryption. XChaCha20-Poly1305 under a chunk key and nonce derived from the folder key and the chunk id, with associated data naming the vault, the folder, the chunk and its length. Deterministic keys are safe here because each key encrypts exactly one message, and they let two devices produce byte-identical objects without talking.
- Naming. The stored object is named by the BLAKE3 hash of its ciphertext. Any copy, anywhere, can be checked against its name without any key: that is how replicas, peers,
fsck --verifyand automatic verification check copies they cannot read.
Known trade-off: deterministic (convergent) encryption inside a folder means that two people who both hold the folder key can tell whether they have an identical file, and compressed chunk sizes are visible to the storage. Within a shared folder that stays true for one key epoch; removing a member gives the folder a new random key, so a removed member cannot recognise anything written afterwards.
Keys

Everything starts from one random 256-bit vault key, created on the first device and never derived from anything you type. It is the one secret to keep; the recovery kit is that key on paper.
- On each device the vault key and the device's signing key are stored in
keys.enc, encrypted under a key that Argon2id derives from the device passphrase. Changing the passphrase re-wraps that file and touches nothing in storage. - Vault-wide keys are derived from the vault key by purpose: ledger, device registry, folder record, replica ledger, peer authentication and beacon tag. A compromise of one derived key does not expose the others.
- Per folder a random folder key is published to your other devices in an encrypted folder record. From it come the dedup-hash key, the metadata key (file lists and thumbnails) and one key per chunk. One folder key opens that folder and nothing else; this is what sharing hands over.
- Per device there is a signing key (Ed25519 + ML-DSA-65) whose public half is the device's identity; the device id is a hash of it.
Removing a device starts a new key epoch: a new random vault key, sealed to each remaining device with the hybrid key exchange below. New ledger batches, records, file lists and blocks use the new keys, and a chain of encrypted epoch records lets the new key open everything older. The removed device finds a signed revocation the next time it reaches a storage and stops; with a wipe order it also empties its folders and keys.
The algorithms
| Purpose | Algorithm | Notes |
|---|---|---|
| Content and metadata encryption | XChaCha20-Poly1305 (AEAD) | 256-bit keys, 192-bit nonces. Every ciphertext carries associated data that names what it is, so a storage cannot move an object into another role. |
| Hashing and key derivation | BLAKE3 (keyed hash and derive_key) | Chunk ids and file hashes are keyed. Every derived key carries the context e2ee-sync-format/0/<purpose>. |
| Passphrase to key | Argon2id | At least 64 MiB, 3 passes; a random salt per device. The passphrase never leaves the device. |
| Signatures (ledger, records) | Ed25519 + ML-DSA-65, hybrid | Both signatures cover the same bytes and both must verify. A key with a post-quantum part never accepts an Ed25519-only signature. |
| Key exchange (sharing, new epochs) | X25519 + ML-KEM-768, hybrid | Both shared secrets and the full transcript go into one key; a sealed secret stays private unless both algorithms are broken. |
| Security-key folders | FIDO2 hmac-secret | The security key derives a 32-byte secret from a credential and a per-folder salt after a touch. |
| Recovery kit | BIP-39 words, Shamir over GF(256) | 24 words with checksum; optional 2-of-3 shares. |
The ledger

The ledger is Varsto's bookkeeping. Every device keeps its own append-only log of facts about what it did, and no device ever writes into another one's log. The facts are small and concrete: "I wrote block 7f3a to the storage I call box", "I downloaded 7f3a from box and its hash matched", "I published version 12 of this folder's file list".
Every device reads every other device's log through the storages and replays all of them into the same picture: which blocks exist, where every copy is, who wrote it, who has checked it and when. Nothing in it is trusted for where it came from: each batch is signed by the device that made it, and a copy counts as verified only when a different device has checked it.
Without the ledger, a sync tool can only hope its copies exist. With it, Varsto can say how many copies of every block there are, in which places, how recently each was checked, and which ones nobody has checked yet.
What it records
| Event | Meaning |
|---|---|
| Device enrolled | This device joined the vault, under this name. |
| Folder added | This device created a folder. |
| Block stored | This device wrote an encrypted block to a storage: folder, chunk id, object name, size and the storage. A claim, attested only by the writer. |
| Block verified | This device downloaded the block from a storage and its hash matched its name. Recorded by downloads, fsck --verify, automatic verification and full disk checks. |
| Block on device | This device holds the block's plaintext, as part of a file. |
| File list published | This device published version n of a folder's file list, with its hash and file count. |
| Storage retired | A storage was removed from the vault: claims on it made before this event no longer count as copies. |
No file names and no content: blocks are identified by keyed chunk ids and ciphertext hashes, and the whole batch is encrypted anyway.
Signed, chained and sealed

Events are collected into batches, typically one per sync rather than one per file. A batch carries the device id, a sequence number (1, 2, 3, ...), the hash of the device's previous batch, a logical clock, its creation time and the events.
- The body is encrypted with XChaCha20-Poly1305 under the ledger key, with the device id and sequence number as associated data.
- The device signs its id, the sequence number and the hash of the encrypted body with both Ed25519 and ML-DSA-65. Signing the ciphertext hash means anyone can check the chain without the key.
- The signed batch is written to every storage that holds records as
ledger/<device>/<sequence>, never overwritten.
A reader accepts a batch only when both signatures verify against the device's public key in the device registry and its previous-hash matches. The post-quantum signature costs about 3.3 KB, which is why events travel in batches: the ledger grows by a few kilobytes per sync, not per file.
Exchanged through your storages
Every storage is a mailbox. After a device commits a batch it uploads every batch of its own that a storage lacks; on every pull it lists the ledger on each hot storage, takes the batches it has not seen, checks them, decrypts them and keeps a local copy. Because each storage carries the whole ledger, a laptop that syncs at night and a phone that syncs in the morning see the same history without ever meeting. Nobody orders anything centrally: a logical (Lamport) clock orders changes, not the wall clock. Untrusted replicas and the people you share a folder with write ledgers too, sealed under keys that open only their part: a replica's claims name objects, not files, and a member's claims cover only the shared folder.
The view: where every block is
Replaying all batches gives each device the same view: for every block of every folder, its object name and size, the storages that claim a copy and which device claimed it, which devices verified it there and when, and which devices hold the plaintext. It also knows the newest file list of each device for each folder, the devices and folders that exist, and which storages are retired. Storage names are local to each device ("box" here, "primary" there), so every storage carries a random identity, and devices match each other's claims by it.
What it is used for
- Uploading once. Before pushing, a device skips every storage that already claims a block, so a file added on two devices goes up once.
- Freeing space safely. A file becomes a placeholder only when the ledger shows every block on a storage that is not a transferrer, and the folder's copy rules still hold.
- Durability policies. "At least three copies, one of them in the cloud, each verified by another device within 30 days" is evaluated from the ledger alone, and the service raises an alert when a folder gets worse.
- Automatic verification. The background service picks copies that other devices wrote and nobody has checked recently, downloads them, compares the ciphertext hash with the name and records the result, within a daily budget.
fsck. Compares the ledger with what the storages actually list: claims without an object, blocks with no copy anywhere, blocks verified elsewhere.- Transferrer disks. A carrier disk takes only the blocks no other device holds yet, and gives the space back once both ends have them.
- Removing a storage. Varsto plans from the ledger which blocks would fall below the folder's minimum, copies them elsewhere, checks again and only then records the retirement.
- Costs. A storage's monthly cost is the bytes the ledger records on it times its price.
- Removing a device. A revocation names the last batch of the removed device that counts; anything it signs later is left out of every view, and only the file lists those batches announced are merged.
Forks and restored devices
Because each device numbers and chains its own batches, history cannot be rewritten quietly. Two different batches with the same device and sequence number, or a batch whose previous-hash does not match, are a fork: every device that sees one marks that device as forked, and fsck reports it. The usual cause is a device restored from an old backup or a cloned disk, which would carry on from a sequence number its newer self has already used. When a device finds batches under its own identity that it never wrote, it fences itself: it stops signing and pushing until it is enrolled again as a new device, so two diverging copies of one identity never both write history.
Limits
- A signature proves that a device said it stored a block, not that the block is still there. That is why a copy counts as verified only after another device has checked it; there is no challenge-response proof of storage yet.
- A storage that serves an older but valid part of the ledger is not caught by signatures; it shows only when another storage or device has newer batches. Rollback detection is a design goal, not a tested property.
- The ledger grows with every sync. Since alpha.8 devices write checkpoints and prune acknowledged batches, so what is stored stays bounded; members of shared folders and replicas still keep every batch.
- Verification times come from device clocks: a badly wrong clock can cause false alerts, though it does not change the order of changes.
- Storages see the number of ledger directories (your device count) and the size and timing of batches.
File lists and sync
The ledger says where blocks are; manifests say what the files are. After a change a device publishes its full view of a folder as a new manifest: every path with its size, modification time, content hash, block list and a version vector (for each device, the clock of its last change). It is encrypted under the folder's metadata key and announced in the ledger. Other devices fetch the manifests they have not seen and merge path by path:
- a version that includes the other wins; identical content merges;
- an edit beats a concurrent deletion, so a deletion never destroys someone's work;
- two different concurrent edits give a deterministic winner (larger clock, then device id), and the other version is kept as
name.conflict-<device>-<clock>.ext, so both devices converge without another round.
Deleted files go to a local trash, never removed outright. The background service watches your folders and syncs within seconds of a change. Selective folders show every file but keep placeholders for those you have not fetched, and fetch a file when you open it. Manifests are full views today; very large folders will need incremental ones.
Peer to peer
Devices also exchange blocks directly. Because encryption is deterministic within a folder, a device that holds a file can serve exactly the object the storage holds, and the receiver checks it against its name like any storage copy. A device that needs a block asks its peers first and the storages second, and spreads consecutive blocks over the peers that answer.
- Finding each other. On the LAN, a multicast beacon that carries a keyed tag, not the vault id. Across the internet, an encrypted rendezvous record in your storages with each device's addresses.
- Crossing NATs. HTTP on the LAN, QUIC over UDP across the internet: STUN tells a device its public address, both sides punch holes, and when that fails, one of your own reachable devices relays. There is no vendor relay.
- Who may connect. Each device has its own certificate, whose hash is pinned in its record; every request also carries a short-lived proof made with the vault's peer authentication key. A stranger gets nothing, and a removed device is dropped from the peer list.
Tested on localhost, on the maintainer's own networks and on a three-device rig across NATs. Two symmetric NATs cannot be punched and fall back to a relay, which needs one reachable device in the vault (a home server, a VPS, a forwarded port); sockets are IPv4 only.
How fast. Benchmarks measures downloads between Paris, Amsterdam and Warsaw, peer to peer against an S3 bucket, with the method, the numbers and what is still slow.
Transport and the local interface. Objects are ciphertext before they leave the device, so transport adds no second layer for its own sake: S3 is spoken over HTTPS with Signature Version 4, rclone remotes use rclone's own configuration, and the S3 secret is kept in an encrypted file. The desktop interface is served only on 127.0.0.1, every request carries a per-session token, the Host header is checked, and the page runs under a strict Content Security Policy.
What each party can see
| Party | Sees | Cannot see |
|---|---|---|
| A storage provider (bucket, NAS, disk, rclone remote) | Object names (hashes of ciphertext), object sizes, how often you write and read, your device count from the number of ledger directories. | File names, contents, folder names, which blocks belong to which file, the hash of your plaintext. |
| An untrusted replica (a friend's NAS) | The same as a storage provider, plus that objects verify against their names. It signs its own claims under a replica-only key. | Any content, name or file list; it never receives a folder key or the vault key. |
| A peer on the LAN or the internet | That some device requests objects by name, after proving it holds the vault's peer authentication key. | Anything without that key: a stranger gets no object, and the LAN beacon does not name the vault. |
| Someone you share a folder with | That folder: every file in it, and your devices' signed claims about it. | Any other folder, the vault key, your device registry. |
| An AI assistant through the MCP server | The folders you granted, read-only unless you said otherwise. | Folders you did not grant; Strongroom folders, ever. |
| A thief with your unlocked laptop | Everything a normal folder holds while the device is unlocked. | Strongroom folders without your security key; nothing written after you remove the device from another one, except in folders shared with other people. |
Sharing a folder

The recipient's device creates a hybrid key pair (X25519 + ML-KEM-768) and prints a request code with the public half. Your device encapsulates to both halves, derives one key from both shared secrets and the transcript, and encrypts the folder key with it. The resulting token can travel over any channel: only the requesting device can open it, and it opens one folder.
Before sealing, both of you compare a fingerprint: six words derived from the request code and the name in it. You read them to each other over a call or in person; a code swapped on its way shows other words, and the recipient's device refuses a token whose confirmed words are not its own.
The recipient's device then writes its own ledger for that folder, sealed under a key derived from the folder key: both sides see each other's claims for that folder and nothing else. Removing a member, or revoking a token nobody accepted yet, gives the folder a new random key: your devices and the members who stay receive it (sealed to each member's own key), everything written from then on is under it, and the removed member's later changes are ignored. What they already received stays readable to them; a key cannot be recalled.
Strongroom: cryptographic, not a dialog

A Strongroom folder's key is never at rest. At creation the security key makes a credential, then produces a 32-byte secret from it and a random per-folder salt (FIDO2 hmac-secret). Varsto wraps the folder key under that secret and stores only the credential id, the salt and the wrapped key. Unlocking repeats the touch and keeps the key in memory for a window (15 minutes by default), then forgets it. There is no path to the folder key without the physical key: not through the passphrase, the vault key or a stolen backup. Backup security keys get their own wrap of the same folder key. Converting an existing folder gives it a new id and key, re-encrypts it and deletes the old ciphertext; re-keying a Strongroom does the same with a new key wrapped for every enrolled security key, for when a key may have been kept by someone.
Passphrases and the recovery kit

The device passphrase goes through Argon2id to unlock keys.enc; the background app can keep it in the operating system's keychain so the service unlocks itself after login. Losing a passphrase loses that device's copy of the keys, not the vault: another device, or the recovery kit, enrols a new one.
The recovery kit prints the vault key as 24 BIP-39 words (a miscopied word is caught by the checksum) and, if you ask, three Shamir shares of which any two rebuild the key while one alone reveals nothing. There is no account, no escrow and no reset: the kit is how you carry the responsibility. Print a new kit after removing a device, because the old one holds the old key.
What is not done
- Not audited. The algorithms are standard and the libraries well-known Rust implementations, but the composition has not been reviewed by an outside cryptographer, and the storage format may still change.
- Metadata. Storages see object sizes, write and read patterns, the number of devices and roughly the number of blocks. Padding and cover traffic are not implemented.
- Convergent encryption inside a folder lets co-holders of the folder key confirm identical files.
- Removing a device protects what is written afterwards, not what the device already held: existing data is not re-encrypted, shared and Strongroom folders keep their keys, and storage credentials are not rotated. Until a second factor exists, any of your devices can remove any other (in a vault with an organization, only administrators can).
- Removing a member of a shared folder protects what is written afterwards; the storage you both reach stays writable to them until you change its access.
- The ledger has no proof of storage, rollback detection is untested, and checkpoints and pruning (alpha.8) bound its size for vault devices only: members of shared folders and replicas still keep every batch.
- Placement. A folder can be written to chosen storages or places, and blocks can be moved between storages (keeping only a cold copy of idle files). Files whose blocks are only on cold storage are not fetched until you move them back, and devices on 0.0.1-alpha.8 or older stop syncing once a move is recorded.
- Strongroom protects the folder key, not a file you fetched and left on disk; the recovery kit does not cover Strongroom folders.
- The vault key is a single secret. Keep the recovery kit in more than one place.
Field-level details, byte layouts and version rules: storage format, key hierarchy, ledger signing notes, security principles, failure model.