summaryrefslogtreecommitdiffstats
path: root/poc
diff options
context:
space:
mode:
Diffstat (limited to 'poc')
-rw-r--r--poc/spike1_crypto.py18
-rw-r--r--poc/spike2_hub.py11
-rw-r--r--poc/spike3_node.py20
-rw-r--r--poc/spike4_nat.py33
-rw-r--r--poc/spike5_client.py19
-rw-r--r--poc/spike5_node.py19
-rw-r--r--poc/spike6_gek.py27
7 files changed, 97 insertions, 50 deletions
diff --git a/poc/spike1_crypto.py b/poc/spike1_crypto.py
index 335758c..4006506 100644
--- a/poc/spike1_crypto.py
+++ b/poc/spike1_crypto.py
@@ -4,17 +4,20 @@ MeshBay — Spike 1: Crypto Primitives
Validates the full cryptographic stack needed for MeshBay.
"""
-import os, time, base64, sys
+import base64
+import os
+import sys
+import time
+import blake3
+import jwt
+from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
+from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
-from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.kdf.argon2 import Argon2id
-from cryptography.hazmat.primitives import hashes, serialization
-from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
-import blake3
-import jwt
+from cryptography.hazmat.primitives.kdf.hkdf import HKDF
PASS = "✓"
FAIL = "✗"
@@ -240,7 +243,8 @@ print("\n=== Test 7: AES-256-GCM — Keystore encryption ===")
def test_aes_gcm_keystore():
# Derive an AES key from Argon2id (as done for keystore unlock)
salt = os.urandom(16)
- aes_key = Argon2id(salt=salt, length=32, iterations=3, lanes=4, memory_cost=65536).derive(b"password")
+ aes_key = Argon2id(salt=salt, length=32, iterations=3, lanes=4,
+ memory_cost=65536).derive(b"password")
# Encrypt a mock keystore blob
keystore_data = b'{"sk_user": "base64...", "sk_group": "base64..."}'
diff --git a/poc/spike2_hub.py b/poc/spike2_hub.py
index f8c1b08..a0ebd6f 100644
--- a/poc/spike2_hub.py
+++ b/poc/spike2_hub.py
@@ -4,10 +4,15 @@ Minimal FastAPI hub: user registration, JWT issuance, node announcement.
In-memory storage only — not persistent across restarts.
"""
-from fastapi import FastAPI, HTTPException, Depends, Header
-from pydantic import BaseModel
+import base64
+import os
+import time
+import uuid
+
+import jwt
from cryptography.hazmat.primitives.kdf.argon2 import Argon2id
-import jwt, uuid, os, time, base64
+from fastapi import Depends, FastAPI, Header, HTTPException
+from pydantic import BaseModel
app = FastAPI(title="MeshBay Hub POC", version="0.1.0")
diff --git a/poc/spike3_node.py b/poc/spike3_node.py
index c0ddde0..562239f 100644
--- a/poc/spike3_node.py
+++ b/poc/spike3_node.py
@@ -12,17 +12,23 @@ Sequence:
8. Simulate token refresh
"""
-import asyncio, httpx, base64, json, os, time
+import asyncio
+import base64
+import json
+import time
from pathlib import Path
+
+import httpx
+import jwt
+from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
-from cryptography.hazmat.primitives import serialization
-import jwt
HUB_URL = "http://meshbay.org"
STATE_FILE = Path("node_state.json") # persists keys and tokens between runs
-PASS = "✓"; FAIL = "✗"
+PASS = "✓"
+FAIL = "✗"
# ── Key helpers ───────────────────────────────────────────────────────────────
@@ -52,7 +58,7 @@ def _load_or_generate_keys(state: dict) -> tuple:
sk_x = X25519PrivateKey.generate()
state["sk_ed25519_b64"] = _sk_to_b64(sk_ed)
state["sk_x25519_b64"] = _sk_to_b64(sk_x)
- print(f" Generated new keypair")
+ print(" Generated new keypair")
return sk_ed, sk_x
# ── Main ──────────────────────────────────────────────────────────────────────
@@ -101,7 +107,7 @@ async def main():
state["user_id"] = r.json()["user_id"]
print(f" {PASS} Registered — user_id={state['user_id'][:8]}...")
elif r.status_code == 409:
- print(f" Already registered (409) — continuing with existing account")
+ print(" Already registered (409) — continuing with existing account")
else:
raise RuntimeError(f"Register failed: {r.status_code} {r.text}")
@@ -149,7 +155,7 @@ async def main():
node_id = r.json()["node_id"]
state["node_id"] = node_id
print(f" {PASS} Node announced — node_id={node_id[:8]}...")
- print(f" endpoint_hint : None (STUN/UPnP discovery in Spike 4)")
+ print(" endpoint_hint : None (STUN/UPnP discovery in Spike 4)")
# ── Step 7: Retrieve node record ──────────────────────────────────────
print("\n[ 7 ] Retrieve node record from hub")
diff --git a/poc/spike4_nat.py b/poc/spike4_nat.py
index 44bcdca..ee7485f 100644
--- a/poc/spike4_nat.py
+++ b/poc/spike4_nat.py
@@ -12,10 +12,18 @@ Tests:
E. Update hub endpoint_hint
"""
-import asyncio, socket, struct, os, json, httpx, time, subprocess
+import asyncio
+import json
+import os
+import socket
+import struct
from pathlib import Path
-PASS = "✓"; FAIL = "✗"; SKIP = "–"
+import httpx
+
+PASS = "✓"
+FAIL = "✗"
+SKIP = "–"
LOCAL_PORT = 19000
MESHBAY_IP = "164.132.246.44" # meshbay.org resolved
@@ -148,7 +156,7 @@ async def udp_hole_punch_test(local_port: int) -> tuple[bool, str|None, str|None
try:
await asyncio.wait_for(recv_event.wait(), timeout=8)
- except asyncio.TimeoutError:
+ except TimeoutError:
pass
finally:
transport.close()
@@ -192,7 +200,7 @@ async def main():
print(f" {FAIL} {nat_type}")
print(f" Cloudflare STUN : {stun_ip1}:{stun_port1}")
print(f" Google STUN : {stun_ip2}:{stun_port2}")
- print(f" → Hole punching unreliable; TURN relay required")
+ print(" → Hole punching unreliable; TURN relay required")
elif stun_ip1:
nat_type = "Unknown (only one STUN server responded)"
print(f" {SKIP} {nat_type}")
@@ -202,7 +210,7 @@ async def main():
print(f" {FAIL} {nat_type}")
# ── Phase C: Bidirectional UDP hole punching ───────────────────────────────
- print(f"\n[ C ] Bidirectional UDP hole punch (node → meshbay.org → echo back)")
+ print("\n[ C ] Bidirectional UDP hole punch (node → meshbay.org → echo back)")
print(f" Starting UDP echo server on meshbay.org:{MESHBAY_ECHO_PORT} ...")
udp_ok = False
seen_ext_addr = None
@@ -223,7 +231,8 @@ async def main():
# May differ from STUN if symmetric NAT
actual_ext = seen_ext_addr.replace("('", "").replace("'", "").replace(", ", ":")
if endpoint_hint and actual_ext != endpoint_hint:
- print(f" ⚠ STUN addr {endpoint_hint} ≠ actual {actual_ext} (symmetric NAT confirmed)")
+ print(f" ⚠ STUN addr {endpoint_hint} ≠ actual {actual_ext} "
+ f"(symmetric NAT confirmed)")
endpoint_hint = actual_ext
else:
print(f" {FAIL} No echo received (timeout)")
@@ -234,7 +243,7 @@ async def main():
print(f" {FAIL} Error: {e}")
# ── Phase D: UPnP ─────────────────────────────────────────────────────────
- print(f"\n[ D ] UPnP port mapping")
+ print("\n[ D ] UPnP port mapping")
upnp_ip, upnp_port = try_upnp(LOCAL_PORT)
if upnp_ip:
print(f" {PASS} Mapped {upnp_ip}:{upnp_port}")
@@ -243,7 +252,7 @@ async def main():
print(f" {FAIL} UPnP not available on this router")
# ── Phase E: Update hub ────────────────────────────────────────────────────
- print(f"\n[ E ] Update endpoint_hint on hub")
+ print("\n[ E ] Update endpoint_hint on hub")
if endpoint_hint and state.get("username"):
async with httpx.AsyncClient(timeout=10) as client:
r = await client.post(f"{HUB_URL}/v1/users/login", json={
@@ -270,10 +279,10 @@ async def main():
print(f" UPnP : {PASS + ' works' if upnp_ip else FAIL + ' disabled'}")
print(f" endpoint_hint : {endpoint_hint or 'none'}")
if udp_ok:
- print(f"\n P2P UDP is functional. QUIC transport will work.")
- print(f" Spike 4 COMPLETE.")
+ print("\n P2P UDP is functional. QUIC transport will work.")
+ print(" Spike 4 COMPLETE.")
else:
- print(f"\n P2P blocked — TURN relay needed for this configuration.")
- print(f" Spike 4 COMPLETE (with finding: relay required).")
+ print("\n P2P blocked — TURN relay needed for this configuration.")
+ print(" Spike 4 COMPLETE (with finding: relay required).")
asyncio.run(main())
diff --git a/poc/spike5_client.py b/poc/spike5_client.py
index 11b3ac0..cc79ebf 100644
--- a/poc/spike5_client.py
+++ b/poc/spike5_client.py
@@ -12,16 +12,23 @@ Acts as the "file requester":
- Reports timing and results
"""
-import asyncio, json, base64, struct, time, sys
+import asyncio
+import base64
+import json
+import struct
+import sys
+import time
+
+import blake3
+from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
-from cryptography.hazmat.primitives import hashes
-import blake3
PORT = 19003
CHUNK_INDEX = 0
-PASS = "✓"; FAIL = "✗"
+PASS = "✓"
+FAIL = "✗"
# ── Wire helpers ───────────────────────────────────────────────────────────────
@@ -115,7 +122,7 @@ async def handle_node(reader, writer):
total_ms = recv_ms + verify_ms
print(f"\n {'='*50}")
- print(f" Timings:")
+ print(" Timings:")
print(f" Network receive : {recv_ms:.0f} ms")
print(f" Verify+decrypt : {verify_ms:.1f} ms")
print(f" Total : {total_ms:.0f} ms")
@@ -133,7 +140,7 @@ async def main():
async with server:
try:
await asyncio.wait_for(server.serve_forever(), timeout=30)
- except asyncio.TimeoutError:
+ except TimeoutError:
print(f" {FAIL} Timeout — node did not connect within 30s")
sys.exit(1)
diff --git a/poc/spike5_node.py b/poc/spike5_node.py
index 6104e00..cded922 100644
--- a/poc/spike5_node.py
+++ b/poc/spike5_node.py
@@ -14,20 +14,27 @@ Note: node initiates the TCP connection (reversed for POC — in production,
the QUIC client connects to the node using the hole-punching from Spike 4).
"""
-import asyncio, json, base64, struct, os, time
+import asyncio
+import base64
+import json
+import os
+import struct
+import time
from pathlib import Path
+
+import blake3
+from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
-from cryptography.hazmat.primitives import hashes, serialization
-import blake3
MESHBAY_HOST = "meshbay.org"
MESHBAY_PORT = 19003
CHUNK_SIZE = 1024 * 1024 # 1 MB
TEST_FILE = Path("testfile.bin")
STATE_FILE = Path("node_state.json")
-PASS = "✓"; FAIL = "✗"
+PASS = "✓"
+FAIL = "✗"
# ── Wire helpers ───────────────────────────────────────────────────────────────
@@ -125,7 +132,7 @@ async def main():
print(f" {PASS} Test file: {TEST_FILE} ({file_size/1024/1024:.1f} MB)")
# Pre-compute file hash (used in HKDF info string — identifies the file)
- print(f" Computing file hash...")
+ print(" Computing file hash...")
t0 = time.perf_counter()
file_hash = blake3.blake3(TEST_FILE.read_bytes()).digest()
print(f" {PASS} File hash: {file_hash.hex()[:16]}... ({(time.perf_counter()-t0)*1000:.0f}ms)")
@@ -166,7 +173,7 @@ async def main():
print(f" {PASS} Sent {payload_kb:.1f} KB in {send_ms:.0f}ms ({throughput:.1f} MB/s)")
print(f"\n {'='*50}")
- print(f" Timings (node side):")
+ print(" Timings (node side):")
print(f" Encrypt + sign : {enc_ms:.1f} ms")
print(f" TCP send : {send_ms:.0f} ms")
print(f" Total : {total_ms:.0f} ms")
diff --git a/poc/spike6_gek.py b/poc/spike6_gek.py
index 89ab06c..e58823b 100644
--- a/poc/spike6_gek.py
+++ b/poc/spike6_gek.py
@@ -16,18 +16,25 @@ Validates the full GEK lifecycle:
The hub stores opaque encrypted blobs — it never sees the GEK in cleartext.
"""
-import asyncio, httpx, base64, os, json, time
+import asyncio
+import base64
+import json
+import os
+import time
from pathlib import Path
+
+import blake3
+import httpx
+from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
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
-import blake3
HUB_URL = "http://meshbay.org"
STATE_FILE = Path("node_state.json")
-PASS = "✓"; FAIL = "✗"
+PASS = "✓"
+FAIL = "✗"
# ── Key helpers ────────────────────────────────────────────────────────────────
@@ -194,7 +201,9 @@ async def main():
bundle_alice = wrap_gek(gek_raw, pk_alice_x_raw)
print(f" {PASS} Wrapped in {(time.perf_counter()-t0)*1000:.2f}ms")
print(f" pk_eph : {bundle_alice['pk_eph_b64'][:24]}...")
- print(f" wrapped : {bundle_alice['wrapped_b64'][:24]}... ({len(base64.b64decode(bundle_alice['wrapped_b64']))}B)")
+ wrapped = bundle_alice["wrapped_b64"]
+ print(f" wrapped : {wrapped[:24]}... "
+ f"({len(base64.b64decode(wrapped))}B)")
r = await c.post(
f"{HUB_URL}/v1/groups/{group_id}/members/{state.get('username','node_cbesson')}/gek",
@@ -218,7 +227,7 @@ async def main():
wrap_ms = (time.perf_counter()-t0)*1000
print(f" {PASS} Wrapped in {wrap_ms:.2f}ms")
print(f" pk_eph : {bundle_bob['pk_eph_b64'][:24]}...")
- print(f" (different from Alice's bundle — ephemeral key is unique)")
+ print(" (different from Alice's bundle — ephemeral key is unique)")
r = await c.post(
f"{HUB_URL}/v1/groups/{group_id}/members/bob_member/gek",
@@ -301,10 +310,10 @@ async def main():
print(f" {PASS} Wrong private key correctly rejected (AEAD auth failed)")
print("\n" + "="*55)
- print(f"Spike 6 COMPLETE — GEK distribution validated.")
+ print("Spike 6 COMPLETE — GEK distribution validated.")
print(f" wrap_gek : {wrap_ms:.2f} ms")
print(f" unwrap_gek : {unwrap_ms:.2f} ms")
- print(f" Hub role : stores opaque bundle, never sees GEK in clear")
- print(f" Security : wrong key rejected by AEAD authentication tag")
+ print(" Hub role : stores opaque bundle, never sees GEK in clear")
+ print(" Security : wrong key rejected by AEAD authentication tag")
asyncio.run(main())