summaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/keyderive.js15
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/vendor/PROVENANCE.md2
-rw-r--r--packages/meshbay-hub/tests/test_bundle_kdf_parity.py2
-rw-r--r--packages/meshbay-node/src/meshbay_node/hub_client.py17
4 files changed, 25 insertions, 11 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js b/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js
index afa5d27..af119c7 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js
@@ -75,11 +75,12 @@ async function generateKeypairs() {
// is exactly what a GPU is good at, so 600k iterations bought far less than the
// wall-clock time suggested.
//
-// 64 MB / t=3 / p=1 measured at ~320 ms through this WASM build on a desktop, so
-// roughly a second on a modest phone — the most that belongs in a login. Memory
-// is what matters here: at 64 MB per guess, a 24 GB card holds a few hundred in
-// parallel instead of the effectively unbounded number PBKDF2 allows.
-const ARGON2_MEM_KIB = 65536; // 64 MB
+// 128 MB / t=3 / p=1 measured at ~640 ms through this WASM build on a desktop.
+// Memory is the lever, not time: each guess must hold 128 MB, so a 24 GB card
+// fits ~187 in parallel and its bandwidth caps it near 2k guesses/s, against no
+// ceiling at all for PBKDF2. 256 MB would double that again at ~1.3 s, which is
+// too much to ask of a phone for something paid at every sign-in.
+const ARGON2_MEM_KIB = 131072; // 128 MB
const ARGON2_TIME = 3;
const ARGON2_LANES = 1;
@@ -273,7 +274,9 @@ async function loginAndRecover(username, password) {
&& localStorage.getItem(`meshbay_kp_${username}`)) || null;
if (bundleEnc) {
- const keys = await decryptBundle(bundleEnc, password, username);
+ // Reuse the keys just derived — decryptBundle() would run the KDF again,
+ // and at these parameters that is another 0.6 s for nothing.
+ const keys = await decryptBundleWithKey(bundleEnc, result.bundleKey);
result.skEdB64 = keys.skEd;
result.skXB64 = keys.skX;
result.keypairBundleEnc = bundleEnc;
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/vendor/PROVENANCE.md b/packages/meshbay-hub/src/meshbay_hub/static/vendor/PROVENANCE.md
index 35d742b..6935e91 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/vendor/PROVENANCE.md
+++ b/packages/meshbay-hub/src/meshbay_hub/static/vendor/PROVENANCE.md
@@ -22,7 +22,7 @@ request and nothing to locate at runtime.
keypair bundle is protected by the passphrase alone and rests on every node whose
group its owner joins (finding C4), so PBKDF2 — compute-only, and therefore cheap
on a GPU — was the wrong tool for it. Measured through this build on the dev
-machine: Argon2id 64 MB / t=3 / p=1 takes ~320 ms, against ~240 ms for
+machine: Argon2id 128 MB / t=3 / p=1 takes ~640 ms, against ~240 ms for
PBKDF2-SHA512 at 600k, for a memory cost a GPU cannot ignore.
### argon2.wasm
diff --git a/packages/meshbay-hub/tests/test_bundle_kdf_parity.py b/packages/meshbay-hub/tests/test_bundle_kdf_parity.py
index c3c8ff9..27e10d4 100644
--- a/packages/meshbay-hub/tests/test_bundle_kdf_parity.py
+++ b/packages/meshbay-hub/tests/test_bundle_kdf_parity.py
@@ -41,7 +41,7 @@ pytestmark = pytest.mark.skipif(
# Parameters must match keyderive.js. If someone tunes them there and not here,
# this test fails — which is the point: changing them silently orphans every
# bundle already written.
-MEM_KIB, TIME_COST, LANES = 65536, 3, 1
+MEM_KIB, TIME_COST, LANES = 131072, 3, 1
CASES = ["alice", "grenet", "utilisateur-é", ""]
PASSWORDS = ["correct horse battery staple", "p", "üñïçø∂é ✓ 🔐"]
diff --git a/packages/meshbay-node/src/meshbay_node/hub_client.py b/packages/meshbay-node/src/meshbay_node/hub_client.py
index 691ac7c..334107d 100644
--- a/packages/meshbay-node/src/meshbay_node/hub_client.py
+++ b/packages/meshbay-node/src/meshbay_node/hub_client.py
@@ -241,6 +241,7 @@ class HubClient:
# working one until someone notices the node has vanished.
async with websockets.connect(
ws_url, ping_interval=20, ping_timeout=20, close_timeout=5,
+ open_timeout=15,
) as ws:
auth_msg = {
"type": "auth",
@@ -250,10 +251,20 @@ class HubClient:
if group_ids:
auth_msg["group_ids"] = group_ids
await ws.send(json.dumps(auth_msg))
- auth_resp = json.loads(await ws.recv())
+ # Bounded: a hub that accepts the socket and then says nothing
+ # — which is what it does for a few seconds while restarting —
+ # would otherwise park this task here forever, with the node
+ # running, silent, and invisible to everyone.
+ auth_resp = json.loads(
+ await asyncio.wait_for(ws.recv(), timeout=15))
if auth_resp.get("type") != "auth_ok":
- log.error("WS auth failed: %s", auth_resp)
- return
+ # Not fatal: the token may simply have expired while we
+ # were disconnected. Refresh on the next pass rather than
+ # ending the task, which used to strand the node for good.
+ log.warning("WS auth refused: %s — retrying in 5s", auth_resp)
+ await asyncio.sleep(5)
+ await self.ensure_fresh_token()
+ continue
self._ws = ws
log.info("Hub WS connected")