diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-14 14:25:44 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-14 14:25:44 +0200 |
| commit | 1c96aceb54d66cae1b48aa0eb8887f68e53f9e24 (patch) | |
| tree | 4af4bc59bc231c7cef0237a3fb6dfd7ea38fd823 /packages/meshbay-hub/src/meshbay_hub/static/keyderive.js | |
| parent | 86563d5db0ae19fc58336b71a5c29a8712973590 (diff) | |
| download | meshbay-1c96aceb54d66cae1b48aa0eb8887f68e53f9e24.tar.gz | |
perf(client): bundle KDF to 128 MB, and derive it once per sign-in
Argon2id memory 64 → 128 MB. Memory is the lever, not time: it caps how many
guesses a card can hold at once, so the ceiling on one high-end GPU moves from
roughly 4k to roughly 2k guesses/s and its 24 GB fits ~187 lanes instead of ~375.
Measured through the vendored build: 640 ms, against 322 ms at 64 MB.
While measuring the real cost of a sign-in, found the SPA deriving the bundle key
twice — once for the key pair kept for the session, then again inside
decryptBundle() for the local bundle. At these parameters that is 0.6 s of pure
waste. Measured now, end to end:
auth_key (PBKDF2 600k) 239 ms
bundle v1 (PBKDF2 600k) 240 ms legacy, until every bundle is upgraded
bundle v2 (Argon2id 128MB) 650 ms
-----------------------------------
sign-in 1 129 ms (889 ms once no v1 bundles remain)
Once per sign-in, and only then: reopening a group, downloading, streaming and
reloading the page all reuse the key, which lives in IndexedDB from login.
Also bounds two waits in the node's hub WebSocket, found because the node went
silent again mid-deploy. It had reconnected after the hub restart, sent its auth
frame, and waited for a reply that never came — `ws.recv()` had no timeout, so a
hub that accepts a socket and then says nothing for a few seconds while starting
up parks the task forever: node running, logging nothing, invisible to everyone.
The auth exchange now times out at 15 s, connect at 15 s, and a refused auth
retries with a fresh token instead of ending the task for good.
QE harness signs in once per account and reuses the token — several clients there
stand for several browsers of one person, and what tells them apart is which keys
they hold, not which token, while the hub quite rightly rate-limits repeated
logins from one address.
Tests: 341, plus the live workflow.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/keyderive.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/keyderive.js | 15 |
1 files changed, 9 insertions, 6 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; |