summaryrefslogtreecommitdiffstats
path: root/docs/QUICKSTART.md
blob: 9840ee1e99b2acc6762a378a0a876b0851409ef4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# MeshBay Quickstart

MeshBay is a peer-to-peer file sharing, streaming, and group messaging platform. Your files live on your node — the hub at `meshbay.org` handles identity and group membership only, and never sees your file content or encryption keys. This guide gets you sharing a file in about 15 minutes.

## Prerequisites

- Python 3.12 or newer
- `pip` (or `uv` — see Step 2)
- `git`
- A public IP address with one TCP port open (or a port-forward on your router). NAT traversal without manual port configuration is coming in v2.

---

## Step 1: Accounts

### Try it now with the demo accounts

Two test accounts are pre-configured with a running demo node. You can skip to Step 6 to try a download immediately.

| Account     | Password        | Role                                        |
|-------------|-----------------|---------------------------------------------|
| alice_test  | AliceTest2026!  | Node operator (node at http://meshbay.org:19001) |
| bob_test    | BobTest2026!    | Group member                                |

Demo group ID: `10484cb7-e45a-4cdc-8b06-f8ccdacc04d2` (name: `demo-group`, private)
Files: `README.txt` (59 bytes), `sample_data.bin` (5 MB)

### Register a new account

There is no web registration form yet. Registration is done via the API. Your Ed25519 and X25519 keypairs must be generated and saved to disk **before** calling the hub — if they are regenerated later, your GEK bundles will be undecryptable.

**Generate your keypairs:**

```python
# save as generate_keys.py — run once
import base64, json, pathlib
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from cryptography.hazmat.primitives import serialization

def raw_b64(key, kind="private"):
    if kind == "private":
        return base64.b64encode(key.private_bytes(
            serialization.Encoding.Raw, serialization.PrivateFormat.Raw,
            serialization.NoEncryption())).decode()
    return base64.b64encode(key.public_bytes(
        serialization.Encoding.Raw, serialization.PublicFormat.Raw)).decode()

sk_ed = Ed25519PrivateKey.generate()
sk_x  = X25519PrivateKey.generate()

keys = {
    "sk_ed25519_b64": raw_b64(sk_ed, "private"),
    "sk_x25519_b64":  raw_b64(sk_x,  "private"),
    "pk_ed25519_b64": raw_b64(sk_ed.public_key(), "public"),
    "pk_x25519_b64":  raw_b64(sk_x.public_key(),  "public"),
}

out = pathlib.Path("my_keys.json")
out.write_text(json.dumps(keys, indent=2))
out.chmod(0o600)
print(f"Keys saved to {out}. Keep this file secret.")
print(f"  pk_ed25519: {keys['pk_ed25519_b64']}")
print(f"  pk_x25519:  {keys['pk_x25519_b64']}")
```

```bash
python3 generate_keys.py
```

**Register with the hub:**

```bash
# Load your keys
PK_ED=$(python3 -c "import json; d=json.load(open('my_keys.json')); print(d['pk_ed25519_b64'])")
PK_X=$(python3  -c "import json; d=json.load(open('my_keys.json')); print(d['pk_x25519_b64'])")

curl -s -X POST https://meshbay.org/v1/users/register \
  -H "Content-Type: application/json" \
  -d "{
    \"username\": \"yourname\",
    \"password\": \"YourPassword123!\",
    \"pk_user_ed25519\": \"$PK_ED\",
    \"pk_user_x25519\":  \"$PK_X\"
  }"
```

Expected response (HTTP 201):
```json
{"user_id": "3f8a1b2c-..."}
```

If you get HTTP 409, the username is taken.

---

## Step 2: Install meshbay-node

```bash
git clone https://github.com/meshbay-org/meshbay.git
cd meshbay
pip install -e packages/meshbay-node
```

With `uv` (recommended — installs all three packages in editable mode):

```bash
uv sync
```

Verify the install:

```bash
meshbay-node --version
```

---

## Step 3: Configure the node

Create the config directory and a minimal `node.toml`:

```bash
mkdir -p ~/.config/meshbay
```

```toml
# ~/.config/meshbay/node.toml

[hub]
url = "https://meshbay.org"

[auth]
username = "yourname"
password = "YourPassword123!"

[node]
# Directory the node will watch and serve
shared_dir = "/home/yourname/meshbay-files"

# Port that group members will connect to — must be reachable from the internet
listen_port = 19001

# Group this node hosts
# Create a group first: POST /v1/groups (see User Guide §3)
group_id = "PASTE-YOUR-GROUP-UUID-HERE"

[keystore]
# How to unlock the key store at startup:
#   "secure"    — prompt for password at startup (default, safest)
#   "lazy_file" — read from ~/.config/meshbay/unlock.key (chmod 600)
#   "service"   — read from MESHBAY_UNLOCK_KEY env var (for systemd)
unlock_mode = "secure"
```

Create the shared directory:

```bash
mkdir -p ~/meshbay-files
```

---

## Step 4: Start the node

```bash
meshbay-node --config ~/.config/meshbay/node.toml
```

On first startup the node will:

1. Generate your Ed25519 and X25519 keypairs and write them to the encrypted keystore (if `my_keys.json` exists in the current directory, it uses those; otherwise generates new ones)
2. Register and announce itself to the hub
3. Scan and index `shared_dir`
4. Start listening on `listen_port`

You will be prompted for a keystore password (choose a strong one — you only enter it once per restart).

Verify the node is running and reachable:

```bash
curl -s http://YOUR-PUBLIC-IP:19001/
```

Expected response:
```json
{
  "file_count": 0,
  "pk_node": "base64-encoded-ed25519-public-key",
  "group_id": "your-group-uuid"
}
```

The alice_test demo node is already running. You can verify it:

```bash
curl -s http://meshbay.org:19001/
```

---

## Step 5: Share a file

Copy a file into your shared directory. The node detects it automatically (via filesystem watch) and adds it to the group index within a few seconds. No command needed.

```bash
cp my-document.pdf ~/meshbay-files/
```

Confirm it appears in the index:

```bash
# Get a token first
TOKEN=$(curl -s -X POST https://meshbay.org/v1/users/login \
  -H "Content-Type: application/json" \
  -d '{"username":"yourname","password":"YourPassword123!"}' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")

# Query the node index
curl -s -H "Authorization: Bearer $TOKEN" http://YOUR-PUBLIC-IP:19001/index
```

Example response:

```json
[
  {
    "id":   "a1b2c3d4e5f6...",
    "name": "my-document.pdf",
    "size": 204800,
    "type": "document"
  }
]
```

The demo node (alice_test) already has two files indexed:

```bash
TOKEN=$(curl -s -X POST https://meshbay.org/v1/users/login \
  -H "Content-Type: application/json" \
  -d '{"username":"alice_test","password":"AliceTest2026!"}' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")

curl -s -H "Authorization: Bearer $TOKEN" http://meshbay.org:19001/index
```

```json
[
  {"id": "...", "name": "README.txt",      "size": 59,      "type": "document"},
  {"id": "...", "name": "sample_data.bin", "size": 5242880, "type": "other"}
]
```

---

## Step 6: Download a file as another user

This is the complete flow: login to the hub, fetch the GEK bundle, unwrap it with your private key, browse the node index, fetch an encrypted chunk, and decrypt it locally.

Save this script and run it. Before running, replace `SK_BOB_X_B64` with bob_test's X25519 private key from `my_keys.json` (or `bob_state.json` if you ran the POC spikes).

```python
#!/usr/bin/env python3
"""
download_demo.py — full MeshBay download and decrypt flow as bob_test

Dependencies (all in the project venv):
  pip install httpx cryptography blake3
"""
import base64, sys
import httpx
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes, serialization

HUB      = "https://meshbay.org"
NODE     = "http://meshbay.org:19001"
GROUP_ID = "10484cb7-e45a-4cdc-8b06-f8ccdacc04d2"

# Bob's X25519 private key (base64, raw 32 bytes) — from my_keys.json
# Replace this with the actual value from bob_test's keystore.
SK_BOB_X_B64 = "REPLACE_WITH_BOB_SK_X25519_B64"


# ── 1. Login to the hub ───────────────────────────────────────────────────────

r = httpx.post(f"{HUB}/v1/users/login",
    json={"username": "bob_test", "password": "BobTest2026!"})
r.raise_for_status()
data    = r.json()
token   = data["access_token"]
headers = {"Authorization": f"Bearer {token}"}
print(f"[1] Logged in as bob_test. Token valid for 1 hour.")


# ── 2. Fetch the GEK bundle from the hub ─────────────────────────────────────
#
# The hub stores an opaque encrypted blob per member per group.
# It cannot decrypt it — only the member's X25519 private key can.

r = httpx.get(f"{HUB}/v1/groups/{GROUP_ID}/gek", headers=headers)
r.raise_for_status()
bundle = r.json()
print(f"[2] GEK bundle fetched from hub (opaque to hub).")


# ── 3. Unwrap the GEK with Bob's X25519 private key ──────────────────────────
#
# Protocol: ephemeral X25519 ECDH + HKDF(SHA-256, salt=pk_eph,
#           info="meshbay:gek_wrap:v1") → 32-byte wrap key
#           ChaCha20-Poly1305 decrypt(nonce, wrapped_gek, aad=pk_bob)

sk_bob_x_raw = base64.b64decode(SK_BOB_X_B64)
sk_bob_x     = X25519PrivateKey.from_private_bytes(sk_bob_x_raw)
pk_bob_x_raw = sk_bob_x.public_key().public_bytes(
    serialization.Encoding.Raw, serialization.PublicFormat.Raw)

pk_eph_raw = base64.b64decode(bundle["pk_eph_b64"])
nonce      = base64.b64decode(bundle["nonce_b64"])
wrapped    = base64.b64decode(bundle["wrapped_b64"])

pk_eph   = X25519PublicKey.from_public_bytes(pk_eph_raw)
shared   = sk_bob_x.exchange(pk_eph)
wrap_key = HKDF(
    algorithm=hashes.SHA256(), length=32,
    salt=pk_eph_raw, info=b"meshbay:gek_wrap:v1"
).derive(shared)

try:
    gek = ChaCha20Poly1305(wrap_key).decrypt(nonce, wrapped, pk_bob_x_raw)
except Exception:
    print("ERROR: GEK decryption failed. Wrong private key or corrupted bundle.")
    sys.exit(1)

print(f"[3] GEK unwrapped successfully ({len(gek)} bytes).")


# ── 4. Browse the node index ──────────────────────────────────────────────────
#
# The node verifies the JWT offline (hub's Ed25519 public key, no hub roundtrip).
# JWT verification takes ~884 µs on the node side.

r = httpx.get(f"{NODE}/index", headers=headers)
r.raise_for_status()
index = r.json()
print(f"\n[4] Files in group ({len(index)} entries):")
for entry in index:
    print(f"    {entry['id'][:12]}...  {entry['name']:<28}  {entry['size']:>10} bytes")


# ── 5. Download and decrypt README.txt ───────────────────────────────────────
#
# /file/{id}/0  returns one encrypted chunk as JSON:
#   ct_b64, nonce_b64, file_hash_b64, chunk_index, plaintext_size
#
# Chunk key is derived from the GEK:
#   HKDF(GEK, salt=None, info="file:<file_hash>:chunk:<index_4be>")

file_entry = next((e for e in index if e["name"] == "README.txt"), None)
if not file_entry:
    print("README.txt not found in index.")
    sys.exit(1)

file_id = file_entry["id"]
print(f"\n[5] Fetching encrypted chunk 0 of README.txt (file_id={file_id[:12]}...)...")

r = httpx.get(f"{NODE}/file/{file_id}/0", headers=headers)
r.raise_for_status()
chunk = r.json()

ct          = base64.b64decode(chunk["ct_b64"])
chunk_nonce = base64.b64decode(chunk["nonce_b64"])
file_hash   = base64.b64decode(chunk["file_hash_b64"])
chunk_idx   = chunk["chunk_index"]          # 0

chunk_key = HKDF(
    algorithm=hashes.SHA256(), length=32, salt=None,
    info=b"file:" + file_hash + b":chunk:" + chunk_idx.to_bytes(4, "big")
).derive(gek)

plaintext = ChaCha20Poly1305(chunk_key).decrypt(chunk_nonce, ct, None)
print(f"[5] Decrypted {len(plaintext)} bytes:")
print()
print(plaintext.decode())
```

Run it:

```bash
python3 download_demo.py
```

Expected output:

```
[1] Logged in as bob_test. Token valid for 1 hour.
[2] GEK bundle fetched from hub (opaque to hub).
[3] GEK unwrapped successfully (32 bytes).

[4] Files in group (2 entries):
    a1b2c3d4e5f6...  README.txt                           59 bytes
    d7e8f9a0b1c2...  sample_data.bin                 5242880 bytes

[5] Fetching encrypted chunk 0 of README.txt (file_id=a1b2c3d4e5f6...)...
[5] Decrypted 59 bytes:

Welcome to the MeshBay demo group. This file is encrypted.
```

**Measured timings on the demo group:**
- Login round-trip: ~80ms
- GEK bundle fetch: ~40ms
- JWT verification on node: ~884µs (offline, no hub call)
- README.txt (59 bytes) full download: ~23ms
- 1 MB encrypted chunk: ~275ms receive, ~2.4ms decrypt

For small files or public groups, you can also download the full file in one request without chunk-level decryption:

```bash
curl -s -H "Authorization: Bearer $TOKEN" \
  http://meshbay.org:19001/file/<file_id> \
  -o README.txt
```

---

## What's next

- **Create your own group:** see the [User Guide](USERGUIDE.md) §3 — Groups
- **Video streaming:** the node exposes an HLS endpoint at `/stream/{file_id}` for MP4 and MKV files
- **Invite a member:** wrap the GEK for them and POST it to `/v1/groups/{group_id}/members/{username}/gek`
- **Multi-group nodes:** a single node can host multiple groups — configure additional `[[groups]]` blocks in `node.toml`
- **Full API reference:** [User Guide](USERGUIDE.md) §11