For cryptographers
Technical details
Everything broken down, step by step.
The full round-trip
- Setup. You enter an email and create a passkey. Your browser derives your identity keypair from it (PRF → HKDF → P-256), seals your email to the server's public key with HPKE, and registers
{sealed_email, share_key, label}with the worker. Your private key is never sent. - Confirm. The worker unseals your email, holds the unsigned link in KV for an hour, and emails you a one-time confirmation link. You click it; the worker signs the link with
ECDSA P-256and hands back your permanent share link. - Share. You post the link anywhere. It carries your public key, your sealed email, and the server's signature. There is no secret inside it, so it is safe to make public.
- Send. A sender opens your link and drops a file. Their browser mints a throwaway identity, runs
HPKE Authto your public key, and AES-256-GCM-encrypts the file and its metadata in 64 KiB authenticated chunks. Small files finish encrypting before the upload starts; large files stream-encrypt chunk by chunk as they upload. - Upload. The worker verifies your link's signature and returns a presigned R2 URL (several, for multipart on large files). The sender's browser PUTs the ciphertext straight to R2 through it; the worker only signs URLs and verifies links, never handling the bytes. A Durable Object enforces exactly-once delivery.
- Notify. The worker unseals your email from the link, in memory only, sends you a delivery link to
/d/<id>, then forgets the address. The link and the stored ciphertext both expire in 7 days. - Receive. You open the delivery link and the relay challenges you before serving anything: it seals a one-time nonce to the public key bound to that delivery, your browser unseals it with your passkey-derived key and answers, and only then does the relay hand out a short-lived download URL. Your browser then runs
HPKE SetupAuthR+ AES-256-GCM to verify and decrypt each chunk straight to disk. Without your passkey, the relay serves nothing. - Clean up. After saving a file you can delete it from the relay right away; deletion demands the same passkey proof as a download, so nobody else can purge your file. Otherwise the R2 object auto-deletes on its 7-day lifecycle. You can disable the link at any time; the worker flags it in KV and refuses further uploads.
Identity
Your keys are derived deterministically from your passkey, on your device, through the WebAuthn PRF extension. Your private key and the passkey secret behind it never reach our servers; only your public key does, carried inside your link. Your passkey itself syncs only through your platform's encrypted keychain, never through us.
The keypair is bound to the receive.link namespace: the domain string is mixed into the derivation, so the same passkey produces an identity that exists only for this service. The private key is reproduced from the passkey secret each session and never persisted to disk.
Cipher suite
Every file uses the same fixed cipher suite, so there's no algorithm negotiation and nothing to downgrade. It's HPKE Auth mode over DHKEM(P-256, HKDF-SHA-256), with HKDF-SHA-256 and AES-256-GCM, per RFC 9180. The crypto core is built on @hpke/core and @noble/curves (the latter for a Safari-compatible DeriveKeyPair), and its test suite verifies the implementation byte-for-byte against the spec's known-answer vectors.
File format
The filename, MIME type, and exact size live in enc_metadata, sealed with AES-256-GCM under a separate key exported from the same HPKE context. The name, type, and contents never travel in the clear, and every chunk is authenticated, so tampering is caught as you open it.
Senders are anonymous, by construction
A sender has no passkey and no account. Their browser mints a throwaway identity for each file, deriveIdentityFromPrf(random(32)), and uses it as the HPKE Auth sender. The sender_pk embedded in the file is random noise that links to nothing and no one. At the identity layer we never learn who sent you a thing. Like any server, the relay sees the sender's IP at request time, but we don't store it: it's HMAC'd into a short-lived rate-limit counter, never kept as a raw address.
The relay
- Links are signed by the server with
ECDSA P-256. The worker verifies the signature before accepting an upload, and rejects revoked or invalid links. (Share links don't expire; the per-file delivery link and its ciphertext do, after 7 days.) - Your email is
HPKE-sealed to the server's public key inside the link, and unsealed only in memory: at sign-up, at confirm, and on each delivery. It is never written to storage as plaintext. - Ciphertext is PUT to Cloudflare R2 through a presigned URL and auto-deleted on a 7-day lifecycle. A SQLite-backed Durable Object enforces exactly-once delivery.
- Downloads are gated, not public. Each delivery is bound to the recipient's public key, and the relay only issues a download URL (or deletes the file) after a single-use challenge sealed to that key is answered. Knowing a delivery URL proves nothing; holding the passkey does.
- Revocation lives in KV (a revoke-token map plus a "link is off" flag); the abuse limits are short-lived counters keyed by an HMAC of the IP, link, or email, so KV holds only digests, never a raw IP or plaintext email. Nothing in the relay ever stores a filename or plaintext: R2 holds ciphertext, KV holds flags, digests, and per-delivery key bindings.
Threat model and non-goals
- No forward secrecy. Your identity key is long-lived, so a compromised passkey can decrypt past and future files sent to that link.
- Not post-quantum. Sharing rides on HPKE over P-256, which a large quantum computer could eventually break.
- Deniable by design. We make no non-repudiation claim. A recipient can forge a file attributing any sender, and senders are ephemeral regardless, so nothing here proves who sent what.
- Observable metadata. At request time the relay sees a ciphertext's length, its timing, and the connection IP (which we hash, never store). Length implies an approximate plaintext size.
- No escrow. The server holds no key that can read your files, only its own link-signing and email-sealing keys. Your identity key isn't rotatable in v1; you issue a fresh link instead.