aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-client/src/argon2-wasm.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-client/src/argon2-wasm.js')
-rw-r--r--packages/meshbay-client/src/argon2-wasm.js66
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 };