ciphermesh 2.12.0 → 2.14.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.
@@ -1,4 +1,4 @@
1
- # SecureLAN Chat — Complete Technical Documentation
1
+ # CipherMesh — Complete Technical Documentation
2
2
 
3
3
  > Secure chat for a local area network (LAN) with real end-to-end encryption (E2EE).
4
4
  > The server **never** has access to the content of messages.
@@ -26,18 +26,18 @@
26
26
 
27
27
  ### What it is
28
28
 
29
- SecureLAN Chat is an instant messaging system designed to operate **exclusively within a local area network (LAN)**. It uses end-to-end encryption (E2EE) based on **Curve25519 + XSalsa20-Poly1305** (via libsodium), ensuring that the server acts only as a **blind relay** — it forwards bytes it cannot read.
29
+ CipherMesh is an instant messaging system designed to operate **exclusively within a local area network (LAN)**. It uses end-to-end encryption (E2EE) based on **Curve25519 + XSalsa20-Poly1305** (via libsodium), ensuring that the server acts only as a **blind relay** — it forwards bytes it cannot read.
30
30
 
31
31
  ### Core Principle
32
32
 
33
33
  ```
34
- Cliente A Servidor Cliente B
34
+ Client A Server Client B
35
35
  | | |
36
- |--- payload cifrado ----->| |
37
- | |--- payload cifrado ----->|
36
+ |--- encrypted payload --->| |
37
+ | |--- encrypted payload --->|
38
38
  | | |
39
- | O servidor NAO possui | |
40
- | a chave para decifrar | |
39
+ | The server does NOT | |
40
+ | hold the key to open it | |
41
41
  ```
42
42
 
43
43
  The server knows **only**:
@@ -55,15 +55,15 @@ The server **never** knows:
55
55
 
56
56
  ```
57
57
  ┌──────────────┐
58
- Servidor
59
- (Relay)
58
+ Server
59
+ (Relay)
60
60
  │ :3600 │
61
61
  └──────┬───────┘
62
62
  │ WebSocket
63
63
  ┌────────────┼────────────┐
64
64
  │ │ │
65
65
  ┌─────┴─────┐ ┌───┴───┐ ┌─────┴─────┐
66
- Cliente A │ │ ... │ │ Cliente N │
66
+ Client A │ │ ... │ │ Client N │
67
67
  │ (Terminal) │ │ │ │ (Terminal) │
68
68
  └───────────┘ └───────┘ └───────────┘
69
69
  ```
@@ -74,14 +74,14 @@ This is the **star** topology (star topology) — the default mode. All clients
74
74
 
75
75
  ```
76
76
  ┌───────────┐
77
- Cliente A │
77
+ Client A │
78
78
  │ (Terminal) │
79
79
  └─────┬─────┘
80
80
  │ WebSocket direto
81
81
  ┌─────────┼──────────┐
82
82
  │ │
83
83
  ┌───┴───┐ ┌────┴────┐
84
- │ ... │ │ Cliente N│
84
+ │ ... │ │ Client N
85
85
  │ │ │(Terminal)│
86
86
  └───────┘ └─────────┘
87
87
  ```
@@ -94,13 +94,24 @@ In P2P mode (`npm run p2p`), peers discover each other via mDNS on the LAN and c
94
94
 
95
95
  ### Production Dependencies
96
96
 
97
- | Package | Version | Role | Why this lib? |
98
- |--------|--------|-------|-------------------|
99
- | **ws** | ^8.18 | WebSocket server/client | The most mature and performant WebSocket implementation for Node.js. Zero dependencies. Natively supports binary frames (essential for encrypted payloads). Used by thousands of projects in production. |
100
- | **sodium-native** | ^4.3 | Cryptography | Native binding of **libsodium** for Node.js. Runs in compiled C (not pure JS), offering real performance and audited security. libsodium is considered the most secure and easy-to-use modern cryptographic library. Used by Signal, Discord, Wireguard. |
101
- | **blessed** | ^0.1.81 | Terminal UI | A library for building rich interfaces in the terminal. Supports layouts with boxes, inputs, scrolling, colors, borders — similar to ncurses but in JS. Lets you create a modern app-style UI without leaving the terminal. |
102
- | **chalk** | ^5.4 | Terminal colors | Colored text formatting in the terminal. Used to highlight nicks, timestamps, errors, status. ESM-only in v5 (compatible with our `"type": "module"`). |
103
- | **play-sound** | ^1.1 | Audio | Plays audio files (mp3) for sound notifications. Uses the OS's native players (mplayer, aplay, cmdmp3). |
97
+ <!-- The package list is checked against package.json by
98
+ test/architecture-doc.test.js. Versions deliberately live only in
99
+ package.json: a number copied here is a number that goes stale, and
100
+ pinning it would put a doc edit in the way of every dependency bump. -->
101
+
102
+ | Package | Role |
103
+ | --- | --- |
104
+ | **@noble/post-quantum** | ML-KEM-768 for the post-quantum hybrid handshake (`src/crypto/PQHybrid.js`). libsodium has no ML-KEM, and this one is audited. |
105
+ | **blessed** | Terminal UI — boxes, scrolling, colours, borders. ncurses-shaped, in JS. Its key parser is old enough to need a shim (`src/client/keyboard.js`). |
106
+ | **bonjour-service** | mDNS service discovery, so P2P peers find each other on the LAN with no relay (`src/p2p/Discovery.js`). |
107
+ | **boxen** | The framed server and P2P banners (`src/shared/banner.js`, `src/p2p/index.js`). |
108
+ | **chalk** | Coloured output outside the blessed UI — banners, prompts, the boot sequence. ESM-only in v5, which suits `"type": "module"`. |
109
+ | **gradient-string** | The gradient across the startup banner (`src/shared/banner.js`). |
110
+ | **jimp** | Decodes received images so they can be drawn as half-blocks in the chat (`src/client/ImagePreview.js`). |
111
+ | **node-notifier** | Desktop notifications. Driven only from `src/shared/notifyWorker.js` — see §4.2 for why it is kept at arm’s length. |
112
+ | **qrcode-terminal** | Renders `/invite` as a QR code in the terminal. |
113
+ | **sodium-native** | Native binding of **libsodium**. Runs in compiled C, not pure JS, so the security is audited and the secret memory is real — see the comparison below. |
114
+ | **ws** | The most mature and performant WebSocket implementation for Node.js. Zero dependencies. Natively supports binary frames (essential for encrypted payloads). |
104
115
 
105
116
  ### Why sodium-native and not tweetnacl?
106
117
 
@@ -121,11 +132,12 @@ In P2P mode (`npm run p2p`), peers discover each other via mDNS on the LAN and c
121
132
  ### Development Dependencies
122
133
 
123
134
  | Package | Role |
124
- |--------|-------|
125
- | **eslint** ^9.17 | Linting ensures code quality and consistency |
126
- | **@eslint/js** ^9.17 | ESLint's recommended base configuration |
127
- | **globals** ^15.14 | Globals definitions (node, browser) for ESLint |
128
- | **prettier** ^3.4 | Automatic formatting consistent code with no style debates |
135
+ | --- | --- |
136
+ | **@eslint/js** | ESLint's recommended base configuration. |
137
+ | **eslint** | Linting. |
138
+ | **figlet** | Not used at runtime: `test/banner.test.js` pins the committed ASCII banner against figlet’s own output, so the art cannot drift. |
139
+ | **globals** | Globals definitions (node) for ESLint. |
140
+ | **prettier** | Automatic formatting — consistent code with no style debates. |
129
141
 
130
142
  ### Node.js >= 20
131
143
 
@@ -140,71 +152,112 @@ Minimum requirement: Node.js 20 LTS. Reasons:
140
152
 
141
153
  ## 3. Directory Structure
142
154
 
155
+ <!-- Checked by test/architecture-doc.test.js: every module under src/ has to
156
+ appear here, and nothing here may name a file that does not exist. -->
157
+
143
158
  ```
144
- securelan-chat/
159
+ ciphermesh/
160
+
161
+ ├── bin/
162
+ │ └── ciphermesh.js # CLI entry point (npx ciphermesh)
145
163
 
146
164
  ├── docs/
147
- └── ARCHITECTURE.md # Este documento
165
+ ├── ARCHITECTURE.md
166
+ │ ├── PLUGINS.md
167
+ │ ├── PROTOCOL.md
168
+ │ ├── SETUP.md
169
+ │ └── commands.json
148
170
 
149
171
  ├── src/
150
172
  │ ├── server/
151
- │ │ ├── index.js # Entry point do servidor
152
- │ │ ├── WebSocketServer.js # Gerencia conexoes WebSocket
153
- │ │ ├── SessionManager.js # Controla sessoes ativas (clientes conectados)
154
- │ │ ├── MessageRouter.js # Roteia payloads cifrados entre clientes
155
- │ │ ├── OfflineQueue.js # Fila de mensagens para peers offline
156
- │ │ └── CertManager.js # Geracao e carregamento de certs TLS
173
+ │ │ ├── index.js # Server entry point
174
+ │ │ ├── CertManager.js # TLS certificate generation and loading
175
+ │ │ ├── config.js # Server configuration and env vars
176
+ │ │ ├── ConnectionGuard.js # Rate limits and connection abuse guards
177
+ │ │ ├── MessageRouter.js # Routes encrypted payloads between clients
178
+ │ │ ├── OfflineQueue.js # Queue for messages to offline peers
179
+ │ │ ├── preflight.js # Startup checks before the port is opened
180
+ │ │ ├── presence.js # Presence/hub counters
181
+ │ │ ├── SessionManager.js # Active sessions (connected clients)
182
+ │ │ └── WebSocketServer.js # WebSocket connection handling
157
183
  │ │
158
184
  │ ├── client/
159
- │ │ ├── index.js # Entry point do cliente
160
- │ │ ├── UI.js # Interface blessed (layout, rendering)
161
- │ │ ├── Connection.js # Conexao WebSocket com o servidor
162
- │ │ ├── ChatController.js # Logica central: conecta UI + Connection + Crypto
163
- │ │ └── FileTransfer.js # Envio/recepcao de arquivos cifrados (chunks)
185
+ │ │ ├── index.js # Client entry point
186
+ │ │ ├── ChatController.js # Core logic: UI + Connection + Crypto
187
+ │ │ ├── Connection.js # WebSocket connection to the server
188
+ │ │ ├── FileTransfer.js # Encrypted file send/receive (chunks)
189
+ │ │ ├── ImagePreview.js # Half-block image previews
190
+ │ │ ├── keyboard.js # Keyboard-protocol shim (Shift+Enter)
191
+ │ │ └── UI.js # Blessed interface (layout, wrapping, rendering)
164
192
  │ │
165
193
  │ ├── crypto/
166
- │ │ ├── KeyManager.js # Gera e gerencia pares de chaves (em memoria)
167
- │ │ ├── MessageCrypto.js # Cifra e decifra mensagens (crypto_box_easy)
168
- │ │ ├── Handshake.js # Protocolo de troca de chaves publicas
169
- │ │ ├── NonceManager.js # Geracao e validacao de nonces
170
- │ │ ├── DoubleRatchet.js # PFS via Double Ratchet (DH ratchet + KDF chains)
171
- │ │ ├── TrustStore.js # TOFU + SAS (persistencia de fingerprints)
172
- │ │ └── StateManager.js # Persistencia cifrada de estado (Argon2id + secretbox)
194
+ │ │ ├── CertPinStore.js # TLS certificate pinning
195
+ │ │ ├── DeniableEncrypt.js # Deniable (symmetric) message mode
196
+ │ │ ├── DeviceIdentity.js # Ed25519 identity and the signed device list
197
+ │ │ ├── DoubleRatchet.js # PFS via Double Ratchet (DH ratchet + KDF chains)
198
+ │ │ ├── Handshake.js # Public-key exchange protocol
199
+ │ │ ├── HistoryStore.js # Encrypted local history on disk
200
+ │ │ ├── IdentityBackup.js # Encrypted identity + trust export/import
201
+ │ │ ├── KeyManager.js # Key pairs (in memory)
202
+ │ │ ├── MessageCrypto.js # Encrypt/decrypt (crypto_box_easy)
203
+ │ │ ├── NonceManager.js # Nonce generation and replay checks
204
+ │ │ ├── PQHybrid.js # Post-quantum hybrid (X25519 + ML-KEM-768)
205
+ │ │ ├── RoomKey.js # Room key derivation and rotation
206
+ │ │ ├── SealedSender.js # Sealed sender — the relay cannot see who sent what
207
+ │ │ ├── SenderKey.js # Sender keys for group messages
208
+ │ │ ├── StateManager.js # Encrypted state persistence (Argon2id + secretbox)
209
+ │ │ └── TrustStore.js # TOFU + SAS (fingerprint persistence)
173
210
  │ │
174
211
  │ ├── p2p/
175
- │ │ ├── index.js # Entry point do modo P2P
176
- │ │ ├── Discovery.js # mDNS discovery via bonjour-service
177
- │ │ ├── PeerServer.js # WebSocket server local (porta aleatoria)
178
- │ │ ├── PeerConnectionManager.js # Gerencia conexoes outbound/inbound
179
- │ │ └── P2PChatController.js # Orquestrador P2P (crypto + UI + peers)
212
+ │ │ ├── index.js # P2P mode entry point
213
+ │ │ ├── Discovery.js # mDNS discovery via bonjour-service
214
+ │ │ ├── P2PChatController.js # P2P orchestrator (crypto + UI + peers)
215
+ │ │ ├── PeerConnectionManager.js # Outbound/inbound connection management
216
+ │ │ └── PeerServer.js # Local WebSocket server (random port)
180
217
  │ │
181
218
  │ ├── protocol/
182
- │ │ ├── messages.js # Definicao dos tipos de mensagem do protocolo
183
- │ │ └── validators.js # Validacao de estrutura dos payloads
219
+ │ │ ├── capabilities.js # Feature negotiation between versions
220
+ │ │ ├── messages.js # Protocol message types
221
+ │ │ └── validators.js # Payload structure validation
184
222
  │ │
185
223
  │ └── shared/
186
- │ ├── constants.js # Constantes globais (portas, limites, versao)
187
- └── logger.js # Logger estruturado (com niveis e timestamps)
188
-
189
- ├── test/
190
- ├── crypto.test.js # Testes do modulo criptografico
191
- ├── protocol.test.js # Testes de validacao do protocolo
192
- ├── nonce.test.js # Testes do gerenciador de nonces
193
- ├── integration.test.js # Testes de integracao E2E
194
- ├── double-ratchet.test.js # Testes do Double Ratchet
195
- ├── trust-store.test.js # Testes do TrustStore + SAS
196
- ├── message-crypto.test.js # Testes do MessageCrypto (padding, encrypt)
197
- └── state-manager.test.js # Testes de persistencia de estado
224
+ │ ├── AuditLog.js # Local audit trail
225
+ ├── banner.js # Startup banners
226
+ ├── commandSuggest.js # Did-you-mean for mistyped commands
227
+ ├── config.js # Client config file
228
+ ├── constants.js # Global constants (ports, limits, version)
229
+ ├── coverTraffic.js # Cover traffic (anti-metadata)
230
+ ├── desktopNotify.js # Desktop notifications breaker, throttle, isolation
231
+ ├── deviceProvisioning.js # Multi-device request/grant/accept
232
+ ├── dnd.js # Do-not-disturb / mentions-only gating
233
+ ├── doctor.js # Connection diagnosis for /doctor
234
+ ├── emoji.js # `:shortcode:` map
235
+ ├── fuzzy.js # Fuzzy matching for the palette and pickers
236
+ │ ├── invite.js # Invite strings and QR payloads
237
+ │ ├── keyArt.js # Fingerprint art
238
+ │ ├── lastSession.js # Last-session hints
239
+ │ ├── logger.js # Structured logger (levels and timestamps)
240
+ │ ├── notifyWorker.js # One-shot notification helper, spawned console-less
241
+ │ ├── onboarding.js # First-run setup wizard
242
+ │ ├── panic.js # Duress wipe
243
+ │ ├── pluginCommand.js # The /plugins command
244
+ │ ├── PluginManager.js # Plugin loading and sandboxing
245
+ │ ├── prompt.js # Readline prompts
246
+ │ ├── terminalGraphics.js # kitty/iTerm2 inline image protocols
247
+ │ ├── themes.js # Nick colour themes
248
+ │ ├── tips.js # Security/UX tips
249
+ │ ├── trust.js # Trust badges
250
+ │ └── voiceNote.js # Voice note record/playback
198
251
 
252
+ ├── test/ # 90 suites, run with `npm test`
199
253
  ├── scripts/
200
- └── generate-fingerprint.js # Utilitario: gera fingerprint de chave publica
254
+ ├── generate-commands.mjs # Regenerates docs/commands.json from the code
255
+ │ └── ...
201
256
 
202
- ├── .editorconfig
203
- ├── .eslintrc.js
204
- ├── .gitignore
205
- ├── .npmrc
257
+ ├── Formula/ciphermesh.rb # Homebrew formula
258
+ ├── eslint.config.js
206
259
  ├── .prettierrc
207
- ├── jsconfig.json
260
+ ├── CHANGELOG.md
208
261
  ├── package.json
209
262
  └── README.md
210
263
  ```
@@ -263,33 +316,67 @@ securelan-chat/
263
316
  - Connects to the server and starts the UI
264
317
 
265
318
  #### `src/client/UI.js` — Blessed Interface
266
- - Layout divided into 3 areas:
319
+ - Layout divided into 4 areas:
267
320
 
268
321
  ```
269
- ┌─────────────────────────────────────────┐
270
- SecureLAN Chat [3 online] E2E │ <- Header/Status bar
271
- ├─────────────────────────────────────────┤
272
-
273
- [10:30] Alice: Ola! │ <- Chat area (scrollable)
274
- [10:31] Voce: Oi Alice!
275
- [10:32] * Bob entrou no chat
276
- [10:32] Bob: Fala galera
277
-
278
- ├─────────────────────────────────────────┤
279
- > Digite sua mensagem... <- Input box
280
- └─────────────────────────────────────────┘
322
+ ┌──────────────────────────────────────────────────────┐
323
+ CipherMesh ▏ felipe ● 3 online E2E │ <- Header
324
+ ├──────────────────────────────────────────────────────┤
325
+ 10:30 🦊 ana <- Chat area
326
+ Hi! This message wraps well short of the │ (scrollable)
327
+ window, not at the border
328
+
329
+ │ 10:31 🐧 felipe ✓✓
330
+ ▎ Oi ana
331
+ │ │
332
+ 10:32 * bob entrou no chat
333
+ ├──────────────────────────────────────────────────────┤
334
+ │ #general Tab ~ Ctrl+K commands ~ /help ~ ^C │ <- Status bar
335
+ ├──────────────────────────────────────────────────────┤
336
+ │ > Type your message... │ <- Input box
337
+ └──────────────────────────────────────────────────────┘
281
338
  ```
282
339
 
283
- - The status bar shows: chat name, online users, E2E indicator
284
- - Chat area with automatic and manual scroll
285
- - Input with history (up/down arrows)
286
- - Distinct colors per user (chalk)
287
- - Special commands: `/quit`, `/users`, `/fingerprint`, `/clear`, `/file`, `/sound`, `/help`
340
+ - **Messages are blocks, not lines.** A header naming the sender, then the text
341
+ wrapped at 65 % of the window (78 columns at most) and indented under it. Each
342
+ entry is one `'\n'`-joined string so it stays a single addressable log line —
343
+ reactions, read receipts, edits and the ephemeral burn all still address it by
344
+ index.
345
+ - What distinguishes a message is a coloured rule down the left of the body,
346
+ not its alignment: yellow when it mentions you, magenta for a DM, the accent
347
+ for your own, nothing for a plain incoming one.
348
+ - Runs from one sender fold under a single header, but only inside the same
349
+ minute, so folding never costs the reader a timestamp.
350
+ - `wrapTagged` wraps text that already carries blessed tags: it closes the open
351
+ tag stack at each break and reopens it after, because blessed carries its
352
+ attribute state across the whole content and a tag left open would bleed into
353
+ the next line's gutter. Wrapping before the markdown pass would be simpler and
354
+ would split `**bold**` spans in half.
355
+ - Everything is laid out again on resize, active buffer and stored ones alike,
356
+ from a per-entry recipe kept alongside the rendered string. Entries with no
357
+ recipe — image previews, animation frames — keep exactly what they were given.
358
+ - The status bar shows the room, buffer tabs, and the key shortcuts
359
+ - Chat area with automatic and manual scroll, plus a "new messages ↓" pill
360
+ - Distinct colours per user, with an emoji avatar derived from the nickname
288
361
  - Animated "typing..." indicator with support for multiple peers
289
362
  - Sound notifications (toggle via `/sound on|off`)
290
363
  - Progress bar for file transfers
291
364
  - Notifications for users joining/leaving
292
365
 
366
+ #### `src/client/keyboard.js` — Keyboard protocols
367
+ - A terminal cannot tell Shift+Enter from Enter unless the application asks it
368
+ to, so startup requests the kitty keyboard protocol (`CSI > 1 u`) and xterm's
369
+ `modifyOtherKeys` level 1 (`CSI > 4 ; 1 m`), and undoes both on the way out.
370
+ - blessed's key parser cannot read what comes back — neither a `u` final byte
371
+ after two parameters nor a `~` after three — and would emit `13;2u` as five
372
+ typed characters. So the reports are decoded on the raw byte stream ahead of
373
+ it, installed as `blessed.screen({ input })`.
374
+ - Enter with any modifier becomes a newline; every other enhanced report is
375
+ rewritten to the legacy encoding blessed already understands; anything with no
376
+ legacy equivalent is dropped rather than typed; arrows, function keys, mouse
377
+ reports and bracketed pastes pass through untouched.
378
+ - `CIPHERMESH_LEGACY_KEYS=1` skips the shim and the negotiation entirely.
379
+
293
380
  #### `src/client/Connection.js` — WebSocket Client
294
381
  - Connects to the server via `ws`
295
382
  - Automatic reconnect with exponential backoff (1s, 2s, 4s, 8s, max 30s)
@@ -306,6 +393,12 @@ securelan-chat/
306
393
  - Validates fingerprints
307
394
  - Manages file transfers via FileTransfer
308
395
  - Sound notification when text messages are received
396
+ - Desktop notifications through `src/shared/desktopNotify.js`: rate-limited to
397
+ one per 3 s, muted for the session on the first refusal with one line in the
398
+ chat saying why, and on Windows delivered by a detached, console-less helper
399
+ (`src/shared/notifyWorker.js`) because SnoreToast writes its diagnostics to the
400
+ attached console — the one blessed is drawing on — when notifications are
401
+ disabled for the application
309
402
 
310
403
  #### `src/client/FileTransfer.js` — File Transfer
311
404
  - Sending: reads the file, splits it into 48KB chunks, encrypts each chunk E2E via broadcast
@@ -433,9 +526,9 @@ securelan-chat/
433
526
  Defines the protocol's message types. All messages have:
434
527
  ```js
435
528
  {
436
- type: string, // tipo da mensagem
437
- version: 1, // versao do protocolo
438
- timestamp: number // Date.now() do remetente
529
+ type: string, // message type
530
+ version: 1, // protocol version
531
+ timestamp: number // Date.now() on the sender
439
532
  }
440
533
  ```
441
534
 
@@ -520,7 +613,7 @@ export const FILE_CHUNK_SIZE = 49152; // 48KB
520
613
  "version": 1,
521
614
  "timestamp": 1739800000000,
522
615
  "nickname": "Alice",
523
- "publicKey": "base64(32 bytes da chave publica Curve25519)",
616
+ "publicKey": "base64(32-byte Curve25519 public key)",
524
617
  "caps": ["sk1"]
525
618
  }
526
619
  ```
@@ -541,7 +634,7 @@ client's.
541
634
  {
542
635
  "sessionId": "660e8400-e29b-41d4-a716-446655440001",
543
636
  "nickname": "Bob",
544
- "publicKey": "base64(chave publica do Bob)",
637
+ "publicKey": "base64(Bob's public key)",
545
638
  "caps": ["sk1"]
546
639
  }
547
640
  ],
@@ -666,7 +759,7 @@ Besides text messages, the encrypted payload may contain commands (the `action`
666
759
  "peer": {
667
760
  "sessionId": "770e8400-e29b-41d4-a716-446655440002",
668
761
  "nickname": "Charlie",
669
- "publicKey": "base64(chave publica do Charlie)"
762
+ "publicKey": "base64(Charlie's public key)"
670
763
  }
671
764
  }
672
765
  ```
@@ -719,12 +812,12 @@ This combination (known as **NaCl crypto_box**) was chosen because:
719
812
  ### 6.3 Key Generation
720
813
 
721
814
  ```
722
- 1. Cliente inicia
723
- 2. sodium.crypto_box_keypair() gera:
724
- - publicKey: 32 bytes (pode ser compartilhada)
725
- - secretKey: 32 bytes (NUNCA sai da memoria do processo)
726
- 3. Ambas armazenadas em sodium.sodium_malloc() (secure memory)
727
- 4. Fingerprint = SHA256(publicKey) formatada como XXXX:XXXX:XXXX:XXXX
815
+ 1. The client starts
816
+ 2. sodium.crypto_box_keypair() produces:
817
+ - publicKey: 32 bytes (safe to share)
818
+ - secretKey: 32 bytes (NEVER leaves the process's memory)
819
+ 3. Both held in sodium.sodium_malloc() (secure memory)
820
+ 4. Fingerprint = SHA256(publicKey), formatted as XXXX:XXXX:XXXX:XXXX
728
821
  ```
729
822
 
730
823
  ### 6.4 Authenticated Encryption with crypto_box_easy
@@ -734,12 +827,12 @@ This combination (known as **NaCl crypto_box**) was chosen because:
734
827
  ```
735
828
  crypto_box_easy(ciphertext, plaintext, nonce, recipientPublicKey, senderSecretKey)
736
829
 
737
- Internamente:
738
- 1. X25519 DH: sharedSecret = ECDH(recipientPub, senderSec)
830
+ Internally:
831
+ 1. X25519 DH: sharedSecret = ECDH(recipientPub, senderSec)
739
832
  2. Key derivation: encKey = HSalsa20(sharedSecret, zeros)
740
- 3. Cifra: XSalsa20(plaintext, nonce, encKey) -> ciphertext
741
- 4. MAC: Poly1305(ciphertext) -> tag de 16 bytes
742
- 5. Output: tag || ciphertext (autenticado)
833
+ 3. Encrypt: XSalsa20(plaintext, nonce, encKey) -> ciphertext
834
+ 4. MAC: Poly1305(ciphertext) -> 16-byte tag
835
+ 5. Output: tag || ciphertext (authenticated)
743
836
  ```
744
837
 
745
838
  The shared key is derived implicitly on each call. The DH guarantees
@@ -749,38 +842,38 @@ that both sides (Alice and Bob) arrive at the same secret without exchanging it
749
842
 
750
843
  ```
751
844
  Input:
752
- - plaintext: Buffer (mensagem em UTF-8)
753
- - nonce: 24 bytes (gerado pelo NonceManager)
754
- - recipientPublicKey: 32 bytes (chave publica do destinatario)
755
- - senderSecretKey: 32 bytes (chave secreta do remetente)
845
+ - plaintext: Buffer (UTF-8 message)
846
+ - nonce: 24 bytes (from the NonceManager)
847
+ - recipientPublicKey: 32 bytes (recipient's public key)
848
+ - senderSecretKey: 32 bytes (sender's secret key)
756
849
 
757
- Processo:
850
+ Process:
758
851
  ciphertext = crypto_box_easy(plaintext, nonce, recipientPublicKey, senderSecretKey)
759
852
 
760
853
  Output:
761
- - ciphertext: Buffer (plaintext.length + 16 bytes de MAC)
762
- - nonce: 24 bytes (enviado junto, nao e segredo)
854
+ - ciphertext: Buffer (plaintext.length + 16 bytes of MAC)
855
+ - nonce: 24 bytes (sent alongside; it is not a secret)
763
856
 
764
- Total enviado: ciphertext (N+16 bytes) + nonce (24 bytes)
857
+ Total on the wire: ciphertext (N+16 bytes) + nonce (24 bytes)
765
858
  ```
766
859
 
767
860
  ### 6.6 Message Decryption
768
861
 
769
862
  ```
770
863
  Input:
771
- - ciphertext: Buffer (recebido da rede)
772
- - nonce: 24 bytes (recebido da rede)
773
- - senderPublicKey: 32 bytes (chave publica do remetente)
774
- - recipientSecretKey: 32 bytes (chave secreta do destinatario)
864
+ - ciphertext: Buffer (off the network)
865
+ - nonce: 24 bytes (off the network)
866
+ - senderPublicKey: 32 bytes (sender's public key)
867
+ - recipientSecretKey: 32 bytes (recipient's secret key)
775
868
 
776
- Processo:
777
- 1. NonceManager valida que nonce nao foi usado antes (anti-replay)
869
+ Process:
870
+ 1. NonceManager checks the nonce has not been seen before (anti-replay)
778
871
  2. plaintext = crypto_box_open_easy(ciphertext, nonce, senderPublicKey, recipientSecretKey)
779
- 3. Se MAC invalido -> rejeita (mensagem foi adulterada)
780
- 4. Se MAC valido -> parse do JSON interno
872
+ 3. MAC invalid -> reject (the message was tampered with)
873
+ 4. MAC valid -> parse the inner JSON
781
874
 
782
875
  Output:
783
- - plaintext: Buffer (mensagem original)
876
+ - plaintext: Buffer (the original message)
784
877
  ```
785
878
 
786
879
  ### 6.7 Nonce Structure (24 bytes)
@@ -788,12 +881,12 @@ Output:
788
881
  ```
789
882
  ┌──────────────────┬──────────────┬──────────────────────┐
790
883
  │ Timestamp (8B) │ Counter (4B) │ Random (12B) │
791
- ms desde epoch sequencial │ sodium.randombytes │
884
+ ms since epoch │ sequential │ sodium.randombytes │
792
885
  └──────────────────┴──────────────┴──────────────────────┘
793
886
 
794
- - Timestamp: impede replay entre sessoes diferentes
795
- - Counter: garante ordenacao e unicidade dentro da sessao
796
- - Random: garante unicidade mesmo com clocks sincronizados
887
+ - Timestamp: blocks replay across different sessions
888
+ - Counter: guarantees ordering and uniqueness within a session
889
+ - Random: guarantees uniqueness even with synchronised clocks
797
890
  ```
798
891
 
799
892
  ### 6.8 Fingerprint Verification
@@ -801,16 +894,16 @@ Output:
801
894
  The fingerprint lets users verify each other's identity **out of band** (for example, in person or by phone):
802
895
 
803
896
  ```
804
- 1. Alice ve seu fingerprint: A1B2:C3D4:E5F6:7890
805
- 2. Bob ve o fingerprint de Alice: A1B2:C3D4:E5F6:7890
806
- 3. Bob confirma pessoalmente com Alice que os valores batem
807
- 4. Se nao baterem -> MITM detectado
897
+ 1. Alice reads her own fingerprint: A1B2:C3D4:E5F6:7890
898
+ 2. Bob reads Alice's fingerprint: A1B2:C3D4:E5F6:7890
899
+ 3. Bob confirms with Alice, in person, that the values match
900
+ 4. They do not match -> MITM detected
808
901
  ```
809
902
 
810
903
  The fingerprint is computed like this:
811
904
  ```
812
905
  fingerprint = SHA-256(publicKey)
813
- = primeiros 8 bytes, formatados em hex com separador ':'
906
+ = first 8 bytes, hex, ':'-separated
814
907
  = "A1B2:C3D4:E5F6:7890"
815
908
  ```
816
909
 
@@ -930,19 +1023,25 @@ The all-members rule is what keeps the damage on that side of the line.
930
1023
  |---|---|---|
931
1024
  | `sk1` | client | I can **receive** a group message: I accept a sender key over the pairwise channel and can decrypt what that chain produces. |
932
1025
  | `sk1` | relay | I can fan a room-addressed `group_message` out to a room's members. |
1026
+ | `dl1` | client | I can read a signed device list handed to me over the pairwise channel. Client-only: a device list rides the sealed channel the relay already carries, so there is no relay half to negotiate. |
1027
+
1028
+ Neither means "I send group messages". Receive and fan-out shipped a release
1029
+ ahead of send — 2.11.0 and 2.12.0 — because the switch is *every member agrees*:
1030
+ if the ability to read had arrived with the ability to write, the switch would
1031
+ only ever be true in rooms where everybody upgraded at the same moment, which on
1032
+ a public hub is close to never.
933
1033
 
934
- Neither means "I send group messages" nothing does yet. Receive and fan-out
935
- are deliberately a release ahead of send, because the switch is *every member
936
- agrees*: if the ability to read arrived with the ability to write, the switch
937
- would only ever be true in rooms where everybody upgraded at the same moment,
938
- which on a public hub is close to never. See `docs/design/sender-keys-on-relay.md`.
1034
+ The per-peer loop the switch falls back to is **not** going away; `/room` reports
1035
+ which path a room is on and why. See `docs/design/sender-keys-on-relay.md` for
1036
+ that decision, and `docs/design/multi-device.md` for the change that would
1037
+ multiply the fallback's cost by the number of devices per peer.
939
1038
 
940
1039
  ## 7. Handshake Protocol
941
1040
 
942
1041
  ### 7.1 Full Diagram
943
1042
 
944
1043
  ```
945
- Cliente A Servidor Cliente B
1044
+ Client A Server Client B
946
1045
  │ │ │
947
1046
  │ 1. JOIN(nick, pubKeyA) │ │
948
1047
  │ ──────────────────────────>│ │
@@ -958,7 +1057,7 @@ which on a public hub is close to never. See `docs/design/sender-keys-on-relay.m
958
1057
  │ usando pubKeyB + secKeyA │ usando pubKeyA + secKeyB
959
1058
  │ │ │
960
1059
  │ 6. ENCRYPTED_MSG ─────────│────────────────────────> │
961
- │ │ 7. Decifra com sharedKey│
1060
+ │ │ 7. Decrypt with sharedKey
962
1061
  │ │ │
963
1062
  ```
964
1063
 
@@ -989,24 +1088,24 @@ which on a public hub is close to never. See `docs/design/sender-keys-on-relay.m
989
1088
 
990
1089
  ## 8. Step-by-Step Communication Flow
991
1090
 
992
- ### 8.1 Full Scenario: Alice sends "Ola" to Bob
1091
+ ### 8.1 Full Scenario: Alice sends "Hi" to Bob
993
1092
 
994
1093
  ```
995
- TEMPO ACAO
1094
+ TIME ACTION
996
1095
  ───── ──────────────────────────────────────────────────────
997
- t0 Alice digita "Ola" no input e pressiona Enter
1096
+ t0 Alice types "Hi" in the composer and presses Enter
998
1097
 
999
- t1 ChatController recebe o texto da UI
1000
- ChatController verifica se tem sharedKey com Bob
1001
- Se nao tem -> erro "Handshake nao completado com Bob"
1098
+ t1 ChatController receives the text from the UI
1099
+ ChatController checks it has a sharedKey with Bob
1100
+ If it does not -> error "Handshake not completed with Bob"
1002
1101
 
1003
1102
  t2 MessageCrypto.encrypt():
1004
- - NonceManager gera nonce de 24 bytes
1005
- - Serializa payload interno: { text: "Ola", sentAt: t2, messageId: "a1b2" }
1103
+ - NonceManager produces a 24-byte nonce
1104
+ - Serialises the inner payload: { text: "Hi", sentAt: t2, messageId: "a1b2" }
1006
1105
  - crypto_box_easy_afternm(payload, nonce, sharedKeyAB)
1007
- - Retorna { ciphertext: Buffer, nonce: Buffer }
1106
+ - Returns { ciphertext: Buffer, nonce: Buffer }
1008
1107
 
1009
- t3 Connection envia ao servidor:
1108
+ t3 Connection sends to the server:
1010
1109
  {
1011
1110
  type: "encrypted_message",
1012
1111
  from: "alice-session-id",
@@ -1014,27 +1113,27 @@ t3 Connection envia ao servidor:
1014
1113
  payload: { ciphertext: "base64(...)", nonce: "base64(...)" }
1015
1114
  }
1016
1115
 
1017
- t4 Servidor (MessageRouter):
1018
- - Valida estrutura (tem type, from, to, payload)
1019
- - NAO abre payload
1020
- - Encontra WebSocket do Bob pelo sessionId
1021
- - Encaminha o JSON inteiro para Bob
1116
+ t4 Server (MessageRouter):
1117
+ - Validates the structure (has type, from, to, payload)
1118
+ - Does NOT open the payload
1119
+ - Finds Bob's WebSocket by sessionId
1120
+ - Forwards the whole JSON to Bob
1022
1121
 
1023
- t5 Bob (Connection) recebe o JSON
1024
- ChatController identifica: encrypted_message de Alice
1122
+ t5 Bob (Connection) receives the JSON
1123
+ ChatController identifies it: encrypted_message from Alice
1025
1124
 
1026
1125
  t6 MessageCrypto.decrypt():
1027
- - Extrai ciphertext e nonce do payload
1028
- - NonceManager valida nonce (nao repetido, counter valido)
1126
+ - Extracts ciphertext and nonce from the payload
1127
+ - NonceManager checks the nonce (not repeated, counter valid)
1029
1128
  - crypto_box_open_easy_afternm(ciphertext, nonce, sharedKeyAB)
1030
- - Se MAC falhar -> rejeita (mensagem corrompida/adulterada)
1031
- - Se MAC ok -> parse do JSON interno
1129
+ - MAC fails -> reject (corrupted or tampered with)
1130
+ - MAC passes -> parse the inner JSON
1032
1131
 
1033
- t7 ChatController recebe { text: "Ola", sentAt: t2, messageId: "a1b2" }
1034
- Valida que sentAt e razoavel (nao muito no passado/futuro)
1132
+ t7 ChatController receives { text: "Hi", sentAt: t2, messageId: "a1b2" }
1133
+ Checks sentAt is reasonable (not far in the past or future)
1035
1134
 
1036
- t8 UI.displayMessage("Alice", "Ola", timestamp)
1037
- Bob ve: [10:30] Alice: Ola
1135
+ t8 UI.addMessage("Alice", "Hi")
1136
+ Bob sees the message under an "10:30 🦊 Alice" header
1038
1137
  ```
1039
1138
 
1040
1139
  ### 8.2 Scenario: Group Chat (broadcast)
@@ -1076,45 +1175,47 @@ a server change and is not wired up there yet.)
1076
1175
  ### 9.1 Server
1077
1176
 
1078
1177
  ```
1079
- 1. Carregar constantes (constants.js)
1080
- 2. Criar instancia WebSocketServer na porta configurada
1081
- 3. Criar SessionManager (mapa vazio de sessoes)
1082
- 4. Criar MessageRouter (referencia ao SessionManager)
1083
- 5. Registrar handlers:
1178
+ 1. Load configuration and constants (config.js, constants.js)
1179
+ 2. Run the preflight checks (port free, certs readable, limits sane)
1180
+ 3. Load or generate the TLS certificate (CertManager) wss:// by default
1181
+ 4. Create the WebSocketServer on the configured port
1182
+ 5. Create the SessionManager (empty session map) and the ConnectionGuard
1183
+ 6. Create the MessageRouter (holding a reference to the SessionManager)
1184
+ 7. Register handlers:
1084
1185
  - on('connection') -> SessionManager.handleConnection()
1085
1186
  - on('close') -> SessionManager.handleDisconnection()
1086
1187
  - on('message') -> MessageRouter.route()
1087
- 6. Iniciar heartbeat interval (ping todos os clientes a cada 30s)
1088
- 7. Registrar SIGINT/SIGTERM para graceful shutdown:
1089
- - Notificar todos os clientes
1090
- - Fechar conexoes
1091
- - Limpar recursos
1092
- 8. Imprimir no console:
1093
- - IP local (todas as interfaces de rede)
1094
- - Porta
1095
- - "Servidor pronto. Clientes podem conectar em ws://<IP>:3600"
1188
+ 8. Start the heartbeat interval (ping every client every 30s)
1189
+ 9. Register SIGINT/SIGTERM for a graceful shutdown:
1190
+ - Notify every client
1191
+ - Close the connections
1192
+ - Release resources
1193
+ 10. Print the banner: every local IP, the port, and the wss:// URLs
1096
1194
  ```
1097
1195
 
1098
1196
  ### 9.2 Client
1099
1197
 
1100
1198
  ```
1101
- 1. Exibir banner "SecureLAN Chat v1.0"
1102
- 2. Pedir nickname (validar: 1-20 chars, alfanumerico + underscore)
1103
- 3. Pedir endereco do servidor (default: localhost:3600)
1104
- 4. Gerar par de chaves (KeyManager)
1105
- 5. Exibir fingerprint da chave publica
1106
- 6. Conectar ao servidor via WebSocket
1107
- 7. Enviar mensagem JOIN (nickname + publicKey)
1108
- 8. Aguardar JOIN_ACK
1109
- 9. Se erro (nickname duplicado) -> pedir outro nickname
1110
- 10. Receber lista de peers e derivar sharedKey com cada um
1111
- 11. Inicializar UI blessed
1112
- 12. Exibir lista de usuarios online
1113
- 13. Entrar no loop de input
1114
- 14. Registrar handler de SIGINT para:
1115
- - sodium_memzero() em todas as chaves
1116
- - Fechar conexao WebSocket
1117
- - Destruir UI blessed
1199
+ 1. Show the CipherMesh banner
1200
+ 2. First run only: the setup wizard (nickname, server, theme), saved to the
1201
+ config file so it never asks twice
1202
+ 3. Ask for the nickname (1-20 chars, alphanumeric + underscore) unless saved
1203
+ 4. Ask for the server address, or accept an invite string (default: localhost:3600)
1204
+ 5. Restore the encrypted session state if there is one (StateManager)
1205
+ 6. Generate or load the key pair (KeyManager) and the device identity
1206
+ 7. Show the public-key fingerprint and its key art
1207
+ 8. Connect over WebSocket, pinning the certificate (CertPinStore)
1208
+ 9. Send JOIN (nickname + public key); wait for JOIN_ACK
1209
+ 10. On error (duplicate nickname) -> ask for another one
1210
+ 11. Receive the peer list and run the handshake with each, deriving a shared key
1211
+ 12. Start the blessed UI: request the keyboard protocols, draw the header,
1212
+ the chat log, the status bar and the composer
1213
+ 13. Enter the input loop
1214
+ 14. Register a SIGINT handler to:
1215
+ - sodium_memzero() every key
1216
+ - Persist the encrypted state
1217
+ - Close the WebSocket
1218
+ - Restore the terminal (keyboard protocols, bracketed paste) and destroy the UI
1118
1219
  ```
1119
1220
 
1120
1221
  ---
@@ -1262,7 +1363,7 @@ routing). With sealed sender, the *sender* side of the social graph stays hidden
1262
1363
  ```
1263
1364
  Startup:
1264
1365
  1. Se existe estado salvo → prompt passphrase → loadState() → restaura KeyManager, Handshake, peers
1265
- 2. Se nao existeprompt passphrase opcional (para proteger sessao futura)
1366
+ 2. If there is none optional passphrase prompt (to protect a future session)
1266
1367
 
1267
1368
  Shutdown (Ctrl+C, /quit):
1268
1369
  Se passphrase definida → serializeState() → saveState() cifrado
@@ -1310,48 +1411,49 @@ Shutdown (Ctrl+C, /quit):
1310
1411
  **Future evolution** — P2P with a DHT (for larger networks):
1311
1412
  ```
1312
1413
  1. Distributed Hash Table para discovery
1313
- 2. Cada no mantem tabela de roteamento parcial
1314
- 3. Mensagens podem ser roteadas por multiplos hops
1315
- 4. Redundancia e tolerancia a falhas
1414
+ 2. Each node keeps a partial routing table
1415
+ 3. Messages can be routed over multiple hops
1416
+ 4. Redundancy and fault tolerance
1316
1417
  ```
1317
1418
 
1318
- ### 11.6 Professional Open-Source Project
1419
+ ### 11.6 Project Infrastructure
1420
+
1421
+ Most of this section used to be a wishlist. It is now a status list, which is a
1422
+ better thing for it to be — what is left is the short part.
1423
+
1424
+ **In place**:
1319
1425
 
1320
- **Repository structure**:
1321
1426
  ```
1322
- securelan-chat/
1323
- ├── .github/
1324
- │ ├── workflows/
1325
- ├── ci.yml # CI: lint + test em cada PR
1326
- ├── release.yml # Release automatica com tags
1327
- │ └── security-audit.yml # npm audit semanal
1328
- │ ├── ISSUE_TEMPLATE/
1329
- ├── bug_report.md
1330
- └── feature_request.md
1331
- ├── PULL_REQUEST_TEMPLATE.md
1332
- └── CODEOWNERS
1333
- ├── docs/
1334
- │ ├── ARCHITECTURE.md
1335
- │ ├── SECURITY.md # Politica de seguranca
1336
- │ ├── CONTRIBUTING.md # Guia de contribuicao
1337
- │ └── PROTOCOL.md # Especificacao do protocolo
1338
- ├── LICENSE # MIT ou Apache-2.0
1339
- ├── CHANGELOG.md # Historico de mudancas (semver)
1340
- ├── CODE_OF_CONDUCT.md
1341
- └── SECURITY.md # Como reportar vulnerabilidades
1427
+ .github/
1428
+ ├── workflows/
1429
+ │ ├── ci.yml # lint + format + tests, Node 20 and 22, on every PR
1430
+ │ ├── codeql.yml # CodeQL static analysis
1431
+ │ ├── release.yml # validate -> npm publish (OIDC) -> GitHub Release -> relay deploy
1432
+ ├── binaries.yml # standalone binaries
1433
+ │ ├── docker-publish.yml # image to GHCR
1434
+ │ ├── deploy.yml # site deploy, called by docker-publish
1435
+ │ └── hub-monitor.yml # scheduled hub health checks
1436
+ ├── dependabot.yml
1437
+ └── CODEOWNERS
1342
1438
  ```
1343
1439
 
1344
- **Best practices**:
1345
- - Semantic versioning (semver)
1346
- - Conventional commits
1347
- - CI/CD with GitHub Actions
1348
- - Dependabot to update dependencies
1349
- - CodeQL for static security analysis
1350
- - Releases signed with GPG
1351
- - Documentation on GitHub Pages
1352
- - Badges in the README (CI, coverage, license, version)
1353
- - Issue templates and PR templates
1354
- - Security policy with a responsible-disclosure process
1440
+ - Semantic versioning, and conventional commits enforced by commitlint
1441
+ - `LICENSE` (MIT), `SECURITY.md` with a disclosure process, `CONTRIBUTING.md`,
1442
+ `TERMS.md`
1443
+ - `CHANGELOG.md`, an entry per release
1444
+ - Badges in the README
1445
+ - npm publishing over **OIDC Trusted Publishing** — no long-lived token exists to
1446
+ leak
1447
+ - A Homebrew formula (`Formula/ciphermesh.rb`), whose digest is filled in after
1448
+ the tag publishes the tarball
1449
+
1450
+ **Not done, and worth doing**:
1451
+
1452
+ - Issue and pull-request templates
1453
+ - A code of conduct
1454
+ - GPG-signed release artefacts (the npm provenance attestation covers part of
1455
+ this, but not the GitHub Release assets)
1456
+ - Published API documentation
1355
1457
 
1356
1458
  ### 11.7 Other Improvements
1357
1459