aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-14 19:15:49 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-14 19:15:49 +0200
commitcfe49407abb3eb48b996ab6d08d66985f29846f0 (patch)
treef49e3ceacd9a49aba818ff549ec8da6b8e7521f7
parentf0984e86d9cb596a282ce6feb7cfc2f075b2794b (diff)
downloadmeshbay-cfe49407abb3eb48b996ab6d08d66985f29846f0.tar.gz
docs: user guide and conventions catch up with per-node identity
USERGUIDE said registration submits your public keys "so other members can wrap GEK bundles for you". Both halves are wrong now: registration creates an account and nothing else, and nobody wraps anything for a key fetched from the hub. The API reference and the register body followed the same correction. CLAUDE.md gains the block a future session needs before touching registration or anything shaped like a user's public key: keys are born at first contact with a node and stay there, the hub publishes none, tokens carry no pk_user, and a scripted signup is now a real account. Left alone deliberately: first-review.md, docs/poc-v1*.md and poc/spike-results.md still describe the old JWT and registration. They are records of what was true on their date, like second-review's verdict table, and draft-v5 is what states the present. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
-rw-r--r--CLAUDE.md23
-rw-r--r--docs/USERGUIDE.md27
2 files changed, 32 insertions, 18 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index b8c2646..b2ff32a 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -143,7 +143,8 @@ Scope: `hub`, `node`, `common`, or omitted for cross-cutting
- `_bundleKey` persisted in IndexedDB (CryptoKey survives page refresh)
- `_sessionKeys` persisted in sessionStorage (survives refresh, cleared on tab close)
- `_pkFromSk()`: derive X25519 public key from recovered private key via JWK export (no hub fetch)
-- Removed auto-`regenerateKeys()` on login (was silently rotating hub keys, breaking GEK unwrap)
+- `regenerateKeys()` is gone entirely (2026-08-14): identity keys are per node, so
+ rotation is `meshbay-node member unpin <user>` plus a fresh code
- Raw answer SDP saved before `setRemoteDescription` (Chrome strips sha-256 from multi-hash SDP)
- Upload chunk size: 48KB (fits aiortc SCTP limit after msgpack overhead)
@@ -212,6 +213,26 @@ See `docs/invite-pairing-v1.md`. Read it before touching invites, admin authorit
- Floor: 12 characters and ~60 estimated bits, enforced client-side only — with the
password split (T1) the hub never sees a passphrase
+## Identity keys are PER NODE (2026-08-14)
+
+See `docs/per-node-identity-v1.md`. Read it before touching registration, the
+keypair bundle, or anything that looks like a user's public key.
+
+- A keypair is created at **first contact with a node**, encrypted under the
+ passphrase, and left on that node. Never reused elsewhere. Cracking one yields
+ the identity used with that operator and nothing anywhere else
+- **The hub stores and publishes no user keys.** `users.pk_ed25519`/`pk_x25519`
+ are dropped, `PUT /me/keys` is gone, `/pubkeys` returns an account id and the
+ node linking key. Do not reintroduce a key directory — that was H3
+- **Tokens carry no `pk_user`.** The node recorded it as the uploader's identity
+ and authorized deletion against it, so whoever issued tokens decided who could
+ delete a file. Attribution uses the roster pin (`_pinned_pk`)
+- Registration generates nothing, so a scripted signup is a real account —
+ `QE/deploy/demo.py bootstrap` takes a wiped hub and node to a working demo
+- The key handed back on a join belongs to the **group of the connection**, not
+ the group named in the invitation (an operator pairs node-wide while opening a
+ group)
+
## Two lessons that cost four rounds of live testing
- **`QE/deploy/e2e.py` cannot test `app.js`.** It is a second implementation of the
diff --git a/docs/USERGUIDE.md b/docs/USERGUIDE.md
index f5c10b8..71b0060 100644
--- a/docs/USERGUIDE.md
+++ b/docs/USERGUIDE.md
@@ -62,7 +62,9 @@ MeshBay has three components. Understanding which role each plays avoids a lot o
### Register
-Registration submits your Ed25519 (signing) and X25519 (key agreement) public keys. These let other members wrap GEK bundles for you and let nodes verify your JWT offline.
+Registration creates an account and nothing else: a username, an email, and a value derived from your passphrase that lets the hub check it without ever seeing it.
+
+**No keys are generated here.** An identity keypair belongs to a *node*, not to the hub: one is created the first time you join a given node, encrypted under your passphrase, and left with that node. So an operator who takes their own disk holds a key that is worthless on anyone else's, and the hub has no key directory to publish — which is what finding H3 read.
**Deux modes de génération de clés :**
@@ -86,22 +88,13 @@ Implémenté dans `static/keyderive.js`.
```
POST /v1/users/register
{
- "username": "string",
- "password": "string (min 8 chars)",
- "pk_user_ed25519": "base64 raw 32-byte Ed25519 public key",
- "pk_user_x25519": "base64 raw 32-byte X25519 public key",
- "keypair_bundle": "base64 AES-256-GCM encrypted bundle (web clients only, optional)"
+ "username": "string",
+ "email": "string",
+ "auth_key": "base64 (PBKDF2-SHA512 of your passphrase — the hub never sees the passphrase itself)"
}
→ 201 {"user_id": "uuid"}
→ 409 if username is taken
```
-
-Passwords are hashed with Argon2id (iterations=3, memory=64 MB in dev;
-target 256 MB / ~500ms in production). Intentionally slow to resist offline attacks.
-
-### Login
-
-```
POST /v1/users/login
{"username": "yourname", "password": "yourpassword"}
→ {
@@ -109,12 +102,12 @@ POST /v1/users/login
"refresh_token": "opaque 256-bit token (30 days)",
"token_type": "bearer",
"expires_in": 3600,
- "keypair_bundle": "base64 AES-GCM blob (présent uniquement si enregistré via web)"
}
```
-Les clients web utilisent `keypair_bundle` pour récupérer leurs clés privées
-sur un nouvel appareil : déchiffrement local avec le mot de passe via `keyderive.js`.
+Le trousseau ne vient pas d'ici : chaque nœud conserve celui qui lui est propre,
+chiffré par votre phrase de passe, et un nouveau navigateur le récupère auprès du
+nœud auquel il se connecte.
```bash
curl -s -X POST https://meshbay.org/v1/users/login \
@@ -754,7 +747,7 @@ All hub endpoints are under `https://meshbay.org`. Node endpoints are under `htt
| Method | Path | Auth | Description |
|---|---|---|---|
-| POST | `/v1/users/register` | None | Register account. Body: `username, password, pk_user_ed25519, pk_user_x25519`. Returns `user_id`. |
+| POST | `/v1/users/register` | None | Register account. Body: `username, email, auth_key`. No keys — identity keypairs are per node. Returns `user_id`. |
| POST | `/v1/users/login` | None | Authenticate. Body: `username, password`. Returns `access_token, refresh_token`. |
| POST | `/v1/users/token/refresh` | Refresh token | Issue new access token. Body: `refresh_token`. |
| GET | `/v1/users/{username}/pubkeys` | Access token | Fetch `pk_ed25519` and `pk_x25519` for any user (used by group admins for GEK wrapping). |