ciphermesh 2.8.0 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/README.md +94 -91
- package/README.pt-BR.md +91 -90
- package/docs/PLUGINS.md +53 -14
- package/docs/commands.json +640 -0
- package/docs/demo.svg +2 -2
- package/package.json +3 -2
- package/src/client/ChatController.js +14 -10
- package/src/client/UI.js +4 -1
- package/src/client/index.js +6 -1
- package/src/p2p/P2PChatController.js +78 -11
- package/src/p2p/index.js +6 -1
- package/src/server/ConnectionGuard.js +158 -0
- package/src/server/WebSocketServer.js +35 -0
- package/src/server/config.js +15 -0
- package/src/server/index.js +18 -0
- package/src/server/preflight.js +96 -0
- package/src/shared/PluginManager.js +103 -30
- package/src/shared/config.js +12 -0
- package/src/shared/constants.js +15 -0
- package/src/shared/pluginCommand.js +106 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,57 @@
|
|
|
3
3
|
Notable changes per release. Older versions are reconstructed from the git
|
|
4
4
|
history — the commit bodies and pull requests remain the fuller record.
|
|
5
5
|
|
|
6
|
+
## 2.10.0
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **\`--check\` for operators.** The deploy guide already explained every
|
|
11
|
+
footgun here, which is the problem: a document is read once, by whoever set
|
|
12
|
+
the machine up, while the misconfiguration lasts as long as the machine does.
|
|
13
|
+
The worst of them — \`TRUST_PROXY\` left off behind a reverse proxy — is
|
|
14
|
+
invisible from outside, because everything works and the per-IP cap, the rate
|
|
15
|
+
limit and the banlist simply apply to the proxy and protect nobody.
|
|
16
|
+
|
|
17
|
+
\`ciphermesh-server --check\` validates and exits without opening a socket, so
|
|
18
|
+
it is safe against a live host, and exits non-zero on an error so a deploy
|
|
19
|
+
script can gate on it. The same findings print at every startup, because a
|
|
20
|
+
warning you have to ask for is a warning nobody sees.
|
|
21
|
+
|
|
22
|
+
- **Connection-rate limiting.** The relay capped how many sockets one address
|
|
23
|
+
could hold, and how many messages a session could send, but nothing capped how
|
|
24
|
+
fast an address could _open_ connections. Connect, run the hybrid handshake,
|
|
25
|
+
disconnect, repeat: the concurrency cap never trips because the sockets are
|
|
26
|
+
never held, and every attempt costs the relay an X25519 and an ML-KEM-768
|
|
27
|
+
operation while costing the client almost nothing. That asymmetry was the one
|
|
28
|
+
real denial-of-service route into a public hub.
|
|
29
|
+
|
|
30
|
+
An address that exceeds `CONNECTION_RATE_PER_MINUTE` (60 by default, still
|
|
31
|
+
LAN-friendly) is refused for a minute, then five, then thirty. An hour of
|
|
32
|
+
behaving clears the record, so a shared NAT gateway cannot accumulate strikes
|
|
33
|
+
forever, and the refusal says how long to wait so a well-behaved client backs
|
|
34
|
+
off instead of extending its own ban.
|
|
35
|
+
|
|
36
|
+
- **A byte budget per connection** (`MAX_BYTES_PER_SECOND`,
|
|
37
|
+
`MAX_BYTES_BURST`). The message limit counts messages, and messages are padded
|
|
38
|
+
into buckets of up to 32 KiB, so a session sitting at the limit is a
|
|
39
|
+
multi-megabit stream. Bytes are the resource that runs out. The burst
|
|
40
|
+
allowance keeps a legitimate file transfer from looking like an attack.
|
|
41
|
+
|
|
42
|
+
Both are continuously refilling token buckets rather than fixed windows: a
|
|
43
|
+
fixed window lets a caller spend its whole allowance at the end of one window
|
|
44
|
+
and again at the start of the next, which is twice the intended rate at
|
|
45
|
+
exactly the moment an attacker aims for.
|
|
46
|
+
|
|
47
|
+
## 2.9.0
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- `/block` now works in **P2P** as well. It matters more there than on a relay:
|
|
52
|
+
P2P has no room owners at all, so `/kick`, `/mute` and `/ban` have nobody to
|
|
53
|
+
act on anyone's behalf. Refusing to listen is the only protection there is, and
|
|
54
|
+
the refusal message for the moderation commands now says so instead of leaving
|
|
55
|
+
the user with nothing.
|
|
56
|
+
|
|
6
57
|
## 2.8.0
|
|
7
58
|
|
|
8
59
|
### Added
|
package/README.md
CHANGED
|
@@ -41,28 +41,28 @@ forwarding, survives CGNAT).
|
|
|
41
41
|
|
|
42
42
|
## ✨ Highlights
|
|
43
43
|
|
|
44
|
-
| | Feature
|
|
45
|
-
|
|
46
|
-
| 🔐
|
|
47
|
-
| 🔄
|
|
48
|
-
| 🛡️
|
|
49
|
-
| 🕶️
|
|
50
|
-
| 🕵️
|
|
51
|
-
| 🌐
|
|
52
|
-
| 📨
|
|
53
|
-
| ✓✓
|
|
54
|
-
| 🗂️
|
|
55
|
-
| 🖼️
|
|
56
|
-
| 📎
|
|
57
|
-
| 💬
|
|
58
|
-
| 🎞️
|
|
59
|
-
| 👻
|
|
60
|
-
| 🔒
|
|
61
|
-
| 🗂️
|
|
62
|
-
| 🩺
|
|
63
|
-
| 🔐
|
|
64
|
-
| 🛰️
|
|
65
|
-
| 🧩
|
|
44
|
+
| | Feature | The gist |
|
|
45
|
+
| --- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
46
|
+
| 🔐 | **Real E2EE** | Curve25519 + XSalsa20-Poly1305 via libsodium, keys in `sodium_malloc` — never touch disk |
|
|
47
|
+
| 🔄 | **Perfect Forward Secrecy** | Double Ratchet: one key per message, compromise today ≠ read yesterday |
|
|
48
|
+
| 🛡️ | **Hybrid post-quantum** | X25519 **+ ML-KEM-768** folded into the ratchet root — beats "harvest now, decrypt later" while staying ≥ classical security ([details](docs/ARCHITECTURE.md)) |
|
|
49
|
+
| 🕶️ | **Metadata resistance** | **Sealed sender** — the relay never sees who sent a message — plus fixed-bucket length padding on every ciphertext and opt-in cover traffic (`/cover`) |
|
|
50
|
+
| 🕵️ | **TOFU + SAS** | Key-change detection (MITM alarm), 6-digit voice-verifiable codes, and inline **✓/✗** trust badges next to names |
|
|
51
|
+
| 🌐 | **LAN & internet** | Auto-detects Tailscale, shows the reachable address in the banner |
|
|
52
|
+
| 📨 | **Invites with QR** | `/invite` prints a `ciphermesh://` string + QR — paste it, you're in the right room |
|
|
53
|
+
| ✓✓ | **Encrypted read receipts** | The ✓✓ travels as ordinary ciphertext — the server can't tell it apart |
|
|
54
|
+
| 🗂️ | **Encrypted local history** | Opt-in (passphrase only), Argon2id + XSalsa20-Poly1305, `/search` & `/export` |
|
|
55
|
+
| 🖼️ | **Image previews** | Received photos render right in the chat as colored half-blocks |
|
|
56
|
+
| 📎 | **Resumable transfers** | Lost chunks are re-requested; reconnects resume from where they stopped |
|
|
57
|
+
| 💬 | **Modern chat feel** | Right-aligned own messages, per-user emoji avatars, replies with quotes, `:fire:` → 🔥 |
|
|
58
|
+
| 🎞️ | **Animated UI** | Splash intro, reconnect spinner, live transfer bars (shimmer + ETA), a lock-closing handshake on connect, and a pulsing "new messages ↓" pill |
|
|
59
|
+
| 👻 | **Deniable & ephemeral** | Symmetric-crypto deniable mode; ephemeral messages _burn away_ char-by-char when they expire |
|
|
60
|
+
| 🔒 | **Private rooms** | `/create <room> <password>` — zero-knowledge: the password never leaves your machine (Argon2id → Ed25519 challenge-response) and room content gets an extra symmetric layer the relay can't fake its way into |
|
|
61
|
+
| 🗂️ | **Multi-room buffers** | Be in several rooms at once — **Alt+1..9** switches, unread badges per room. Which room a message belongs to travels _inside_ the encrypted payload: the relay never learns it |
|
|
62
|
+
| 🩺 | **It explains itself** | `/doctor` diagnoses a failing connection layer by layer — address, DNS, TCP, TLS, protocol — and tells you what to do about each failure |
|
|
63
|
+
| 🔐 | **Screen lock** | `/lock` and `/autolock` put the session behind your passphrase when you step away; `/panic` is still there for the worse moment |
|
|
64
|
+
| 🛰️ | **Serverless P2P mode** | mDNS peer discovery on the LAN — no relay at all, and nearly the same command set |
|
|
65
|
+
| 🧩 | **Plugins** | Drop a JS file in `~/.ciphermesh/plugins` and get new slash-commands — `/roll` and `/poll` examples included ([Plugin API](docs/PLUGINS.md)) |
|
|
66
66
|
|
|
67
67
|
## 🚀 Quick start
|
|
68
68
|
|
|
@@ -131,10 +131,10 @@ download from the
|
|
|
131
131
|
> v2.7.2 never embedded its native addon and only ran on the machine that built
|
|
132
132
|
> it, so those attachments have been removed.
|
|
133
133
|
|
|
134
|
-
| Binary
|
|
135
|
-
|
|
136
|
-
| `ciphermesh-<platform>`
|
|
137
|
-
| `ciphermesh-server-<platform>` | Relay only, for self-hosters who want nothing else on the machine.
|
|
134
|
+
| Binary | What it is |
|
|
135
|
+
| ------------------------------ | ----------------------------------------------------------------------------------------------------------- |
|
|
136
|
+
| `ciphermesh-<platform>` | Everything: client, relay and P2P. `ciphermesh server` and `ciphermesh p2p` work exactly as they do on npm. |
|
|
137
|
+
| `ciphermesh-server-<platform>` | Relay only, for self-hosters who want nothing else on the machine. |
|
|
138
138
|
|
|
139
139
|
**Everyone** (including the host):
|
|
140
140
|
|
|
@@ -200,39 +200,41 @@ software was built for.
|
|
|
200
200
|
<details>
|
|
201
201
|
<summary><b>Essentials</b></summary>
|
|
202
202
|
|
|
203
|
-
| Command
|
|
204
|
-
|
|
205
|
-
| `/help`
|
|
206
|
-
| `/tips`
|
|
207
|
-
| `/
|
|
208
|
-
| `/
|
|
209
|
-
| `/
|
|
210
|
-
| `/
|
|
211
|
-
| `/
|
|
212
|
-
| `/
|
|
213
|
-
| `/
|
|
214
|
-
| `/
|
|
203
|
+
| Command | Description |
|
|
204
|
+
| ----------------------------- | ----------------------------------------------------------------- |
|
|
205
|
+
| `/help` | All commands |
|
|
206
|
+
| `/tips` | Show a rotating security/UX tip |
|
|
207
|
+
| `/plugins` | List loaded plugins and the commands they add |
|
|
208
|
+
| `/users` | Who's online (with away/status) |
|
|
209
|
+
| `/msg <nick> <text>` | Private message (DM) |
|
|
210
|
+
| `/reply <text>` | Reply quoting the last received message |
|
|
211
|
+
| `/me <action>` | Third-person action — _«ana is compiling»_ |
|
|
212
|
+
| `/watch [add\|remove\|clear]` | Alert on a keyword in **any** room, like a mention |
|
|
213
|
+
| `/invite [host:port]` | Generate a `ciphermesh://` invite + QR code |
|
|
214
|
+
| `/nick <new>` | Change nickname (before joining — recovers from "nickname taken") |
|
|
215
|
+
| `/quit` | Leave |
|
|
215
216
|
|
|
216
217
|
</details>
|
|
217
218
|
|
|
218
219
|
<details>
|
|
219
220
|
<summary><b>Rooms</b></summary>
|
|
220
221
|
|
|
221
|
-
| Command
|
|
222
|
-
|
|
223
|
-
| `/join <room> [password]`
|
|
224
|
-
| `/leave [room]`
|
|
225
|
-
| `/create <room> <password>`
|
|
226
|
-
| `/rooms`
|
|
227
|
-
| `/room`
|
|
228
|
-
| `/topic [text\|clear]`
|
|
229
|
-
| `/owner`
|
|
230
|
-
| `/kick` `/mute` `/ban`
|
|
222
|
+
| Command | Description |
|
|
223
|
+
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
224
|
+
| `/join <room> [password]` | Open a room as a **new buffer** — you stay in your other rooms (IRC style) |
|
|
225
|
+
| `/leave [room]` | Leave a room; its buffer closes (the last room is protected) |
|
|
226
|
+
| `/create <room> <password>` | Create a **private room** 🔒 — see below |
|
|
227
|
+
| `/rooms` | List rooms (🔒 marks private ones) |
|
|
228
|
+
| `/room` | Current room + your buffer list |
|
|
229
|
+
| `/topic [text\|clear]` | Show or set the room topic — shown in the status bar and synced to whoever joins later |
|
|
230
|
+
| `/owner` | Room owner |
|
|
231
|
+
| `/kick` `/mute` `/ban` | Owner moderation — bound to the public key, so a rename does not undo a ban |
|
|
232
|
+
| `/block` `/unblock` `/blocklist` | Stop seeing someone, **just for you**. Nothing is sent, the relay never learns, and they are not told — so anyone can use it, including in `general`, which has no owner. Works in P2P too, where there is no moderation at all. |
|
|
231
233
|
|
|
232
234
|
**Buffers:** be in several rooms at once — **Alt+1..9** switches, and the status
|
|
233
235
|
bar shows `[1:general] [2:dev •3]` with per-room unread badges. Because the
|
|
234
236
|
relay is blind (sealed sender), which room a message belongs to travels
|
|
235
|
-
|
|
237
|
+
_inside_ the encrypted payload — the server never learns it.
|
|
236
238
|
|
|
237
239
|
**Private rooms** are zero-knowledge: the password never leaves your machine.
|
|
238
240
|
Joining derives an Ed25519 key from the password (Argon2id) and answers a
|
|
@@ -247,22 +249,22 @@ without verifying couldn't read a word. Share the password out-of-band.
|
|
|
247
249
|
<details>
|
|
248
250
|
<summary><b>Trust & security</b></summary>
|
|
249
251
|
|
|
250
|
-
| Command
|
|
251
|
-
|
|
252
|
-
| `/fingerprint [nick]`
|
|
253
|
-
| `/verify <nick>`
|
|
254
|
-
| `/verify-confirm <nick>`
|
|
255
|
-
| `/trust <nick>` / `/trustlist`
|
|
256
|
-
| `/contacts [add\|remove\|all]`
|
|
257
|
-
| `/backup [path]`
|
|
258
|
-
| `/deniable [on\|off]`
|
|
252
|
+
| Command | Description |
|
|
253
|
+
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
254
|
+
| `/fingerprint [nick]` | Key fingerprint + a deterministic **randomart** picture of the key |
|
|
255
|
+
| `/verify <nick>` | SAS code (~40-bit) + QR + key randomart for out-of-band verification |
|
|
256
|
+
| `/verify-confirm <nick>` | Mark peer as verified |
|
|
257
|
+
| `/trust <nick>` / `/trustlist` | Accept new key / trust status |
|
|
258
|
+
| `/contacts [add\|remove\|all]` | Contact book — persistent aliases on trust records ("this fingerprint is João"); shows in `/users`, rides along in identity backups |
|
|
259
|
+
| `/backup [path]` | Encrypted backup of identity + verified peers (restore at startup) |
|
|
260
|
+
| `/deniable [on\|off]` | Plausible-deniability mode |
|
|
259
261
|
| `/lock` / `/autolock <min\|off>` | Lock the screen behind the session passphrase — manually or after idle time (`autoLock` in config). Privacy for the "stepped away" moment; `/panic` is for the worst one |
|
|
260
|
-
| `/panic [yes]`
|
|
261
|
-
| `/cover [on\|constant\|off]`
|
|
262
|
-
| `/theme [name]`
|
|
263
|
-
| `/ephemeral <30s\|5m\|1h\|off>`
|
|
264
|
-
| `/receipts [on\|off]`
|
|
265
|
-
| `/audit [n]`
|
|
262
|
+
| `/panic [yes]` | Duress wipe — securely erase all on-disk secrets (session, history, trust, keys) and exit |
|
|
263
|
+
| `/cover [on\|constant\|off]` | Cover traffic — `on` = jittered decoys, `constant` = steady-rate paced channel |
|
|
264
|
+
| `/theme [name]` | Nick colour theme: neon, matrix, mono, sunset, ocean |
|
|
265
|
+
| `/ephemeral <30s\|5m\|1h\|off>` | Self-destructing messages |
|
|
266
|
+
| `/receipts [on\|off]` | Send read receipts (✓✓) |
|
|
267
|
+
| `/audit [n]` | Local audit log |
|
|
266
268
|
|
|
267
269
|
A green **✓** next to a name marks a SAS-verified peer; a red **✗** flags a key that changed since you last saw it (possible MITM). A newly-arrived unverified peer triggers a one-time reminder to `/verify` them.
|
|
268
270
|
|
|
@@ -271,40 +273,41 @@ A green **✓** next to a name marks a SAS-verified peer; a red **✗** flags a
|
|
|
271
273
|
<details>
|
|
272
274
|
<summary><b>History & files</b></summary>
|
|
273
275
|
|
|
274
|
-
| Command
|
|
275
|
-
|
|
276
|
-
| `/file <path>`
|
|
277
|
-
| `/voice [secs]`
|
|
278
|
-
| `/play [path]`
|
|
279
|
-
| `/accept [id]` / `/reject [id]` | Accept / decline an incoming file offer
|
|
280
|
-
| `/img [path]`
|
|
281
|
-
| `/search <term>`
|
|
282
|
-
| `/find [term]` — **Ctrl+F**
|
|
283
|
-
| `/doctor [host:port]`
|
|
284
|
-
| `/history [n]`
|
|
285
|
-
| `/retention <7d\|24h\|30m>`
|
|
286
|
-
| `/export [path]`
|
|
276
|
+
| Command | Description |
|
|
277
|
+
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
278
|
+
| `/file <path>` | Offer a file (≤ 50MB) — the recipient must `/accept`; transfers resume |
|
|
279
|
+
| `/voice [secs]` | Record & send an encrypted voice note (needs `sox`/`ffmpeg`; default 10s) |
|
|
280
|
+
| `/play [path]` | Play the last received voice note (`afplay`/`sox`/`ffplay`) |
|
|
281
|
+
| `/accept [id]` / `/reject [id]` | Accept / decline an incoming file offer |
|
|
282
|
+
| `/img [path]` | Render the last received image in **full resolution** (kitty/iTerm2) |
|
|
283
|
+
| `/search <term>` | Search the encrypted local history (on disk, across sessions) |
|
|
284
|
+
| `/find [term]` — **Ctrl+F** | Search **this room's scrollback** and press Enter to **jump to the message**, highlighted |
|
|
285
|
+
| `/doctor [host:port]` | Diagnose why a connection fails: address, DNS, TCP port, TLS (CA vs self-signed) and protocol version — each failure with what to do about it |
|
|
286
|
+
| `/history [n]` | Last n messages from history |
|
|
287
|
+
| `/retention <7d\|24h\|30m>` | Purge local history older than the given age |
|
|
288
|
+
| `/export [path]` | Export history as .txt or .json (plaintext!) |
|
|
287
289
|
|
|
288
290
|
</details>
|
|
289
291
|
|
|
290
292
|
<details>
|
|
291
293
|
<summary><b>Presence & fun</b></summary>
|
|
292
294
|
|
|
293
|
-
| Command
|
|
294
|
-
|
|
295
|
-
| `/away [reason]` / `/back`
|
|
296
|
-
| `/
|
|
297
|
-
| `/
|
|
298
|
-
| `/
|
|
299
|
-
| `/
|
|
300
|
-
| `/
|
|
301
|
-
| `/
|
|
302
|
-
| `/
|
|
303
|
-
| `/
|
|
295
|
+
| Command | Description |
|
|
296
|
+
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
297
|
+
| `/away [reason]` / `/back` | Mark yourself away — while away, unreads are counted (`[away · N new]`) and `/back` shows a summary |
|
|
298
|
+
| `/autoaway <minutes\|off>` | Go away by itself after that long without typing (1–240 minutes) |
|
|
299
|
+
| `/mentions [n]` | Recent mentions of you this session (who, where, when) |
|
|
300
|
+
| `/status <text\|off>` | Free-form status — emojis welcome (`/status :fire: coding`) |
|
|
301
|
+
| `/react <emoji>` | React to the last message — the emoji lands **on the message**, with a count when several people react |
|
|
302
|
+
| `/edit` `/delete` | Edit or delete your last message — the **original line is rewritten in place** (marked _(edited)_) or replaced by a tombstone, instead of a new line you have to mentally staple to it |
|
|
303
|
+
| `/pin` `/unpin` `/pins` | Pin messages |
|
|
304
|
+
| `/sound` `/notify` | Sound / desktop notifications |
|
|
305
|
+
| `/dnd [on\|off\|mentions\|HH:MM-HH:MM]` | Do-not-disturb, mentions-only, or quiet hours |
|
|
306
|
+
| `/clear` | Clear the chat |
|
|
304
307
|
|
|
305
308
|
</details>
|
|
306
309
|
|
|
307
|
-
Typing `:fire:` anywhere becomes 🔥 (Tab autocompletes shortcodes). **Ctrl+K** opens a fuzzy command palette, **Ctrl+E** an emoji picker. PageUp/PageDown scroll the history. **Alt+Enter** (or Shift+Enter where the terminal supports it, plus Ctrl+J) inserts a newline for multi-line messages; Enter sends. Pasting multi-line text (code included) keeps its line breaks — paste, check, Enter. Markdown works: \`code\`, **bold**,
|
|
310
|
+
Typing `:fire:` anywhere becomes 🔥 (Tab autocompletes shortcodes). **Ctrl+K** opens a fuzzy command palette, **Ctrl+E** an emoji picker. PageUp/PageDown scroll the history. **Alt+Enter** (or Shift+Enter where the terminal supports it, plus Ctrl+J) inserts a newline for multi-line messages; Enter sends. Pasting multi-line text (code included) keeps its line breaks — paste, check, Enter. Markdown works: \`code\`, **bold**, _italic_, links, plus fenced \`\`\` code blocks and | tables |. Received images preview inline (half-blocks) and render full-res with `/img` on kitty/iTerm2. Day separators and message grouping keep the log clean.
|
|
308
311
|
|
|
309
312
|
### First run & config file
|
|
310
313
|
|
|
@@ -324,7 +327,7 @@ All keys are optional (unknown keys are ignored):
|
|
|
324
327
|
|
|
325
328
|
```json
|
|
326
329
|
{
|
|
327
|
-
"nickname": "
|
|
330
|
+
"nickname": "ana",
|
|
328
331
|
"server": "wss://100.x.y.z:3600",
|
|
329
332
|
"sound": false,
|
|
330
333
|
"notify": true,
|
|
@@ -360,7 +363,7 @@ All keys are optional (unknown keys are ignored):
|
|
|
360
363
|
**Argon2id + XSalsa20-Poly1305** — no passphrase, no persistence.
|
|
361
364
|
- **Hybrid post-quantum**: each pairwise session mixes an ML-KEM-768 secret
|
|
362
365
|
into the ratchet root at setup, so recorded traffic stays unreadable to a
|
|
363
|
-
future quantum adversary. It is
|
|
366
|
+
future quantum adversary. It is _added_ to X25519, never replaces it —
|
|
364
367
|
security is at least the classical one. `/trustlist` shows `[PQ]`.
|
|
365
368
|
- **Private rooms** never send the password anywhere: it derives an Ed25519
|
|
366
369
|
key (Argon2id) that answers a server challenge, and the room content carries
|