What the design has to do
- Photos are encrypted on the phone before upload. Only album members hold the keys.
- A member can be offline for weeks and still receive every key they missed.
- Someone who joins an album can see its whole history. This is a product choice.
- Someone who is removed cannot open photos added after they left.
- The server can enforce the album's rules without being able to read photos, names, album titles or album keys.
- A copy of the database reveals as little as possible about who shares albums with whom.
For now an album has at most 10 active members and exactly one admin, its creator.
Who the design defends against
| Adversary | What they have | What Miuchio aims for |
|---|---|---|
| Someone with a database copy | Every table, no server secrets | No photos, names or titles; no email addresses; coarse times. Memberships are not readably tied to accounts, but they can be grouped by person, and signatures link each album's admin to an unnamed account |
| A curious server | The running server and its secrets | No photos, names, titles or keys. It can still see membership and upload metadata |
| A malicious server | Everything above, and it can lie | Cannot forge a signature under a key the phone already trusts. It can still substitute a key at first contact or during an invite, and withhold or hide things. Safety numbers are the check for substitution |
Standard primitives, used as specified
None of these are mine, and I use them as their specifications describe. Read the links for how they work.
| Purpose | Standard | Implementation in the app |
|---|---|---|
| Signatures | Ed25519, RFC 8032 | libsodium |
| Key agreement | X25519, RFC 7748 | libsodium |
| Key derivation | HKDF-SHA256, RFC 5869 | Dart cryptography package |
| Encryption | AES-256-GCM, NIST SP 800-38D | Dart cryptography package |
| Keyed hashing (server) | HMAC-SHA256, RFC 2104 | Go standard library |
Known designs I adapted
These start from published designs, but the versions in Miuchio are my own, written on the primitives above:
| Purpose | Based on | What is different in Miuchio |
|---|---|---|
| Offline key agreement | Signal's X3DH | Separate signing and key agreement keys, the album ID in the derivation, and one handshake per delivery. The result wraps album keys once and is then discarded |
| Key verification | Safety numbers, from Signal | My own construction: one SHA-256 over both identity keys, sorted, and the album ID, so each shared album has its own number |
| First contact | Trust on first use | Two kinds of pins, and first use allowed only when the phone holds no key for the album |
The parts I designed
The ideas underneath are not new: group keys that change when membership changes, and small keys encrypted under other keys, are common. The specific rules, formats and checks below are mine, listed with the most important first.
| Part | What it does | Checked by | Page |
|---|---|---|---|
| Epoch rules | A new album key only when someone is removed or leaves, never on a join | Server | Albums and epochs |
| Signed epoch changes | The admin signs the exact recipient list and every wrap; the server checks it against the live member set under the album lock | Server. Members not yet | Albums and epochs |
| The removal freeze | A flag stored in the same transaction as the removal; uploads are refused until the next epoch is committed | Server | Albums and epochs |
| The signer check | Which key may sign a delivery, decided from what the phone holds, not from the server | Each phone | Trust and verification |
| History on invite | One handshake wraps every earlier epoch key; the joiner installs them in order and signs a receipt | Joiner's phone, then server | Albums and epochs |
| Per photo keys and upload gates | Two keys per photo under the epoch key. Reserve and confirm both recheck the epoch and the freeze; a refused photo keeps its ciphertext and has its keys wrapped again under the newest key | Server, then the uploading phone | Photos, names and avatars |
| Changes to X3DH | Separate signing and key agreement keys, the album ID in the derivation, one handshake per delivery | Each phone | Key delivery |
| Member tokens and the user link | A random token per album; the account link is encrypted under server keys. Memberships still share one handle | Database layout | Metadata and the social graph |
| Sealed names and avatars | Names carry their epoch in front; avatars are sealed separately per album and padded to one size | Each phone | Photos, names and avatars |
| Recoverable account deletion | Deletion runs one album at a time and resumes after a crash; a receipt stops the phone wiping itself for a deletion that was not accepted | Server and phone | Albums and epochs |
| Sealed upload queue and cache | Uploads survive the app being killed, and cached photos, names and pins are sealed under a device key read once each time the app starts | Each phone | Photos, names and avatars |
Three layers of keys
Splitting it this way keeps the expensive part small. A handshake happens once per member when an epoch starts, or once per invite, never once per photo, and changing who is in the album never requires re-encrypting a photo.
One wire format
Every AES-256-GCM ciphertext starts with a version byte, and the version byte alone selects the algorithm. Names and avatar keys put a 4 byte epoch number in front of it, so the reader knows which album key to use:
| Byte | Format | Used for |
|---|---|---|
0x01 | AES-256-GCM: VER ‖ NONCE(12) ‖ TAG(16) ‖ CT | Key wraps, thumbnails, files under 1 MiB, names, avatars |
0x02 | ChaCha20-Poly1305 | Reserved, never written today |
0x03 | AES-256-GCM in 1 MiB segments | Files of 1 MiB or more |
The Go server and the Dart app share a file of known answer test vectors, so both sides must produce identical bytes for handshakes, signatures and hashes.
Reading order
- Identity and key delivery: the keys on each phone, and how an album key reaches another phone.
- Albums and epochs: creating, inviting, removing, and the signed changes the server checks.
- Metadata and the social graph: what the database records about people, and what it deliberately does not.
- Photos, names and avatars: per photo keys, the upload steps, and everything else sealed under an album key.
- Trust and verification: pins, the signer check and safety numbers.
- What Miuchio does not protect: tradeoffs and unfinished work in one place.