diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-09 11:49:11 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-09 11:49:11 +0200 |
| commit | 7b5ca7d7b68ea2324474f5aeec511abe01ff8013 (patch) | |
| tree | 7b98a8b384646a78bfe8d24aa9c437edcca9819f /docs/QUICKSTART.md | |
| parent | ebfece74d21ba6b9e2e896f54193927b93d90645 (diff) | |
| download | meshbay-7b5ca7d7b68ea2324474f5aeec511abe01ff8013.tar.gz | |
docs: add QUICKSTART.md and USERGUIDE.md
QUICKSTART (434 lines): 6-step guide tested against live
https://meshbay.org — demo accounts alice_test/bob_test,
real transfer of README.txt (23ms) and 1MB chunk (275ms recv,
2.4ms decrypt), exact Python commands with measured output.
USERGUIDE (785 lines): 11-section reference — architecture,
account management, group/node config, file sharing, HLS streaming,
security model, moderation/CSAM, troubleshooting, full API table.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'docs/QUICKSTART.md')
| -rw-r--r-- | docs/QUICKSTART.md | 434 |
1 files changed, 434 insertions, 0 deletions
diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md new file mode 100644 index 0000000..9840ee1 --- /dev/null +++ b/docs/QUICKSTART.md @@ -0,0 +1,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 |