Skip to content
HN On Hacker News ↗

GitHub - peterretief/yggstore: Sharded, encrypted storage between friends over Yggdrasil: lend each other disk space, send files by sending a sealed stub.

▲ 28 points • 10 comments • by peter_retief • 3d ago • HN discussion ↗

Pangram verdict · v3.3

We believe that this entire text is AI.

87 %

AI likelihood · overall

AI
0% human-written 100% AI-generated
SEGMENTS · HUMAN 0 of 1
SEGMENTS · AI 1 of 1
WORD COUNT 1,702
PEAK AI % 87% · §1
Analyzed
Oct 7
backend: pangram/v3.3
Segments scanned
1 windows
avg 1702 words each
Distribution
0 / 100%
human / AI fraction
Verdict
AI
Pangram v3.3

Article text · 1,702 words · 1 segments analyzed

Human AI-generated
§1 AI · 87%

Proof of concept: sharded, encrypted file storage between peers on the Yggdrasil overlay, with no VPN coordinator: node IDs come from public keys, each node only answers group members, and storage challenges check that peers still hold what they were given. The idea: people in a group lend each other disk space. Each file is encrypted, cut into pieces and spread over everyone's machines, so it survives any one machine failing. The small stub left behind holds the key, so sending someone a stub (sealed for them, by email or anything else) is a way to send them the file, with no cloud service in between. Status: experimental. The cryptography has not been reviewed by anyone independent. Don't make it the only copy of anything you can't lose. New to this? Set up a storage box (step by step, for Windows users with a second-hand mini PC). No box? The gateway lets people pay a monthly fee and use the group's storage from any S3 program (rclone, Cyberduck, Duplicati). Members choose whether their box holds customers' data, and earn credit. Every version is kept: history lets you restore an older version of a file, or a folder as it was on a given day. A new version only stores the parts that changed. Nodes can message each other: direct messages, and topics any node can publish to and follow, delivered even to nodes that were off at the time. Programs can use it too, through a local API. No Yggdrasil to install: a node can run it built in, with no daemon, TUN device or root. It talks to nodes on the daemon too. The mesh keeps members' Yggdrasil linked directly to each other, so losing one tunnel or public peer doesn't cut anyone off. Websites can live on the group: publish a folder, and several machines serve it, so a site stays up when one of them goes down. Email to your domain can arrive on the group: each message is encrypted for you as it comes in and read on your dashboard. Licence: see LICENSE. Contributions: CONTRIBUTING.md. Security issues: SECURITY.md. How it works put splits the file into 4 MiB chunks, encrypts each with AES-256-GCM (one random key per file, fresh nonce per chunk), and erasure-codes each chunk into 4 data + 2 parity shards. Shards are content-addressed (filename = SHA-256) and spread over the online peers, at most one shard of a chunk per peer when there are 6+. A stub FILE.ystub holds the manifest, including the key. Anyone with the stub and access to the peers can read the file. get fetches shards in parallel, rejects any whose hash doesn't match, and rebuilds each chunk from any 4 of its 6 shards. At upload time, while it still has the shards, the uploader precomputes 20 single-use challenges per shard into FILE.ystub.challenges.json (keep this private). verify sends one per shard: "hash this byte range with this nonce", and checks the answer. Identity and access A Yggdrasil address (200::/7) is derived from the node's public key, so the source address of a connection over the overlay identifies the caller. The address is the node ID. Each server only answers callers whose address is in peers.json (default deny), binds only to its overlay address (or, with the built-in Yggdrasil, has nothing on the machine's network at all), and only lets the original writer delete a shard. Usage Download yggstore for your system from the releases page (Linux on PCs, Raspberry Pis and arm64 boards, Windows, macOS), and check it against SHA256SUMS. Or build it yourself with Go 1.24 or later: go build -o bin/yggstore ./cmd/yggstore bin/yggstore id # prints your node ID and peers.json entry # collect one entry from every machine into peers.json, copy it to each one bin/yggstore serve -peers peers.json # on every machine (port 7400 by default) bin/yggstore status -peers peers.json bin/yggstore put -peers peers.json photo.jpg # -> photo.jpg.ystub bin/yggstore verify photo.jpg.ystub bin/yggstore get -o copy.jpg photo.jpg.ystub bin/yggstore rm photo.jpg.ystub # delete its shards peers.json: [ {"name": "desktop", "addr": "[200:1234:5678::1]:7400", "admin": true}, {"name": "pi", "addr": "[201:abcd::1]:7400", "slow": true} ] Each chunk is 6 shards, and no node gets more than 2 of them, so losing any one node never loses data. A node marked "slow": true gets one shard per chunk while the others have room for the rest, so it doesn't hold uploads back. Only the uploading machine's peers.json decides this. Nodes that run on the same machine (Docker containers, say) should share a "host" tag, e.g. {"name": "t1", "addr": "...", "host": "desktop"}. The 2-shard limit then applies per machine, and uploads need 3 machines online, not just 3 nodes. Joining a group On an admin dashboard, open Invite someone, type their name and press Make invite. You get a message to send them, with a one-time invite (yggjoin1:…, valid 7 days). They need Yggdrasil running and connected (the message lists public peers), and the yggstore program; then: yggstore join -service yggjoin1:… # -name NODE -me NAME -quota GB to change the defaults join asks the admin node to add this machine (over Yggdrasil, presenting the invite), saves the member list, adds the inviter as a contact, and with -service starts the node and dashboard as user services. On the admin side the newcomer is added to peers.json (owner: their name) and to your contacts, and the list sync tells every node. An invite works once; the admin node only keeps a hash of it. A node on a machine that is already a member is tagged with that machine automatically; otherwise tag it with "host" by hand if you know two nodes share a box. The join endpoint is the only call a non-member can make, and it does nothing without a valid invite (rate-limited to 6 tries a minute). Adding nodes An "admin": true node (the desktop) keeps every node's list in step. Each node reports which list it holds; the dashboard sends its own peers.json to any node that holds another one, so a node added on the dashboard, or a hand edit of the desktop's peers.json, reaches every node within a minute, with no restarts. A node keeps a pushed list in its data folder (peers.pushed.json), and uses whichever of that and its own -peers file changed last. Only admins may push, and a push must keep the sender admin. To add a machine: open Add a node on the dashboard and follow the steps. In short, the new machine starts with a peers.json holding just the desktop's entry, runs yggstore serve, and you paste what yggstore id -name NAME prints. New uploads use it straight away; files already stored stay put. Nodes on older software show "update yggstore" on the dashboard and are not sent lists. Install the new binary on them and restart them; a node that has never been sent a list also needs "admin": true on the admin node's entry in its own peers.json, so that it accepts lists from it. Sharing Every person runs their own node and their own dashboard. The dashboard listens on localhost only and holds your stubs, which carry the keys to your files, so it is never shared. What people share is the network of nodes. To send someone an item, add them under Sharing → Contacts with their sharing code (ys1…, shown on their dashboard), then press Share on the item. You get a .ysend file to send by email or any other way. Only that person can open it; the item's name, your note and its key are sealed inside, and opening it proves which sharing code sent it. They drop it into their outfiles, and it appears under Shared with you, where they can restore it. They need a node in this network, since nodes serve shards to members only. A received item is still yours to lose: if the sender deletes it, it is gone for everyone they shared it with. Remove on a received item only forgets it on your side. Received stubs may only point at Yggdrasil addresses or nodes in your own peers.json, so a crafted file cannot make your restore contact other machines. Your private sharing key is ~/.yggstore/sharing.key (contacts in ~/.yggstore/contacts.json). Losing it means you can't open what people send you any more, and they need your new code. The dashboard refuses actions without its own X-Yggstore header, and requests that name another host, so other web pages in your browser cannot press its buttons. Give and use Each node reports how many bytes each uploader has stored on it. The dashboard totals this per person (set with "owner" in peers.json; defaults to the machine): what they use across the network, and what their nodes hold for others. The aim is to hold about 1.5× what you use. For now it is accounting only; nothing is enforced. Outbox folder bin/yggstore dashboard -peers peers.json -outfiles outfiles # dashboard + watcher bin/yggstore watch -peers peers.json -dir outfiles # watcher only, logs to stdout outfiles/ drop files in, or folders of them → each file is sharded, then replaced by NAME.ystub beside it, so the folder tree stays outfiles/received/ items people sent you (drop their .ysend anywhere in outfiles) outfiles/whole/ drop a file or folder in → stored as one item (a folder becomes one archive, restored as a whole) outfiles/restore/ drop any .ystub in → rebuilt into restored/ outfiles/restored/ restored files and folders outfiles/delete/ drop a .ystub in → the item is removed from every node, then its stub outfiles/.yggstore/ stubs that have been restored (kept for reference) An item is picked up once it has looked the same on two polls (5 s apart) and was last changed more than 3 s ago, so half-copied items are left alone. Names ending in .part, .crdownload, .tmp and the like are ignored. In folders, every file is its own item: it can be restored or deleted on its own, and new files added to the folder later are picked up too. Folders in whole/ are streamed as a tar archive and stored as one item. On restore, only regular files and folders are written, and paths that would escape the destination are rejected. Symlinks are skipped.