> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reflecto.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Encryption

Reflecto's product promise is that the server is a dumb relay — it routes
opaque encrypted blobs between your paired devices and never sees the
plaintext. The public API has a single scoped carve-out: `POST /v1/send`
accepts plaintext over TLS so the server can encrypt-and-forward to every
recipient device. This page is the honest accounting of what changes for
callers.

## What the server sees on `/v1/send`

When you call `POST /v1/send`, the plaintext envelope exists in server RAM
for the duration of the encrypt-and-forward operation. The server:

1. Builds the envelope (`{ type, payload, source }`).
2. Looks up each recipient device's stored X25519 public key.
3. For each recipient: derives a shared secret from the server's private key
   and the recipient's public key, generates a fresh 24-byte nonce, encrypts
   with `nacl.secretbox`.
4. Writes the encrypted blob to the per-device Redis queue.
5. Discards the plaintext envelope.

The plaintext is **never** written to Redis, the database, or any log. Error
logs may carry the `token_id`, `user_id`, and an error code — never the
payload.

## What the server cannot see

| Visible to server                         | Not visible to server                                                                      |
| ----------------------------------------- | ------------------------------------------------------------------------------------------ |
| `user_id` (resolved from your token)      | Anything you send between your own devices via mirroring (encrypted on the source device). |
| `token_id`, label                         | Shared secrets, device private keys.                                                       |
| Target device list, timestamps, source IP | Long-term storage of any `/v1/send` payload (none is persisted).                           |

The exposure is bounded — plaintext exists in RAM only between the request
arriving and the per-recipient encryption finishing — and the same logging
hygiene applies regardless of which endpoint handled the request.

## Wire format

Recipients decrypt each envelope as:

```
wire_blob = [0x01] | nonce(24) | ciphertext
sym_key   = HSalsa20(0, X25519(my_private_key, server_public_key))
plaintext = nacl.secretbox.open(ciphertext, nonce, sym_key)
```

In practice you don't write the HSalsa20 step yourself — `nacl.box.before()`
in TweetNaCl (and `crypto_box_beforenm` in libsodium) does the X25519 +
HSalsa20 in one call and returns the `secretbox` key.

`nacl.secretbox` is authenticated encryption — a successful `open` proves the
ciphertext was produced by an entity holding the server's private key (i.e.
the server itself, in normal operation). A failing `open` means the envelope
was tampered with or misrouted; the device drops it. There is no separate
signature.

## Server keypair

The server holds a single X25519 keypair generated at bootstrap and
distributed to devices at pair time (via `/v1/pair/confirm` for Android,
`/v1/devices/add-extension` for the extension). Devices store the server
pubkey alongside other paired-device pubkeys, marked `type: "server"`.

There is no runtime backfill endpoint — devices that paired before the
public-API rollout must re-pair to receive the key. Server keypair rotation
is deferred to V2.

## What this means for you

* **Treat `/v1/send` payloads the way you'd treat any HTTPS POST body.** The
  privacy boundary is "no long-term server visibility," not "no momentary
  server visibility."
* **Don't put secrets in notification text** that you wouldn't put in any
  HTTPS request. The server doesn't log it, but the trust model isn't
  zero-knowledge.
* **Mirroring traffic stays end-to-end encrypted.** Notifications captured
  on your phone by `NotificationListenerService` are encrypted on-device
  before they hit the wire; the server never sees their plaintext.
