diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-30 16:58:31 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-30 16:58:31 +0200 |
| commit | 6d167392f6f8ede37e2794a68a3738f8ba03131d (patch) | |
| tree | 9caacef15dd034c6425f4bb623e0cd50a28ec52a /packages/meshbay-client/src/argon2-wasm.js | |
| parent | 8926f163dad9d32dc06c3a142658a4e11d9c12c1 (diff) | |
| download | meshbay-6d167392f6f8ede37e2794a68a3738f8ba03131d.tar.gz | |
feat(client): the desktop application keeps M and every node identity in its main process
keyring.js derives, opens, mints, seals, signs and agrees there; the page gets
public keys and a handle. Argon2 comes from the page's own WebAssembly build
(Electron's crypto has none). Without OS key storage the page keeps its keys as
a browser does. A node's bundle is settled after connecting, re-sealed when the
key changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-client/src/argon2-wasm.js')
| -rw-r--r-- | packages/meshbay-client/src/argon2-wasm.js | 66 |
1 files changed, 66 insertions, 0 deletions
diff --git a/packages/meshbay-client/src/argon2-wasm.js b/packages/meshbay-client/src/argon2-wasm.js new file mode 100644 index 0000000..c68e994 --- /dev/null +++ b/packages/meshbay-client/src/argon2-wasm.js @@ -0,0 +1,66 @@ +/** + * Argon2id for the main process, from the page's own WebAssembly build. + * + * Electron's Node is built on BoringSSL, which has no Argon2: `crypto.argon2` + * exists there and refuses (`ERR_CRYPTO_ARGON2_NOT_SUPPORTED`) — found by + * signing in to the real application, since a plain Node has it. The vendored + * build the page already runs is the one implementation both sides can share, + * which also makes their agreement a matter of construction. + */ + +'use strict'; + +const fs = require('node:fs'); +const path = require('node:path'); + +let ready = null; + +/** + * The emscripten loader reads its options from a global `Module` (through + * `self`) when it is required, and again while the WebAssembly instantiates, + * which is asynchronous. Both globals are set for that time only — until the + * first derivation has run — so nothing else in this process sees them after. + */ +function load(vendorDir) { + if (ready) return ready; + const saved = {}; + for (const name of ['self', 'Module']) { + saved[name] = Object.prototype.hasOwnProperty.call(globalThis, name) + ? { value: globalThis[name] } : null; + } + const restore = () => { + for (const [name, was] of Object.entries(saved)) { + if (was) globalThis[name] = was.value; else delete globalThis[name]; + } + }; + globalThis.Module = { wasmBinary: fs.readFileSync(path.join(vendorDir, 'argon2.wasm')) }; + globalThis.self = globalThis; + ready = (async () => { + try { + const lib = require(path.join(vendorDir, 'argon2.min.js')); + // One derivation at the lowest cost: the instance exists once it returns. + await lib.hash({ pass: 'x', salt: new Uint8Array(8), time: 1, mem: 8, + hashLen: 16, type: lib.ArgonType.Argon2id }); + return lib; + } finally { + restore(); + } + })(); + ready.catch(() => { ready = null; }); + return ready; +} + +/** `(password, salt, params) => Promise<Buffer>` over the vendored build. */ +function wasmArgon2(vendorDir) { + return async (password, salt, { memory, passes, parallelism, tagLength }) => { + const a = await load(vendorDir); + const out = await a.hash({ + pass: String(password), salt: new Uint8Array(salt), + time: passes, mem: memory, parallelism, hashLen: tagLength, + type: a.ArgonType.Argon2id, + }); + return Buffer.from(out.hash); + }; +} + +module.exports = { wasmArgon2 }; |