From 2463081949df388e388f0144643557b332be73fe Mon Sep 17 00:00:00 2001 From: byGalax Date: Sat, 16 May 2026 16:30:52 +0200 Subject: [PATCH] feat(shared): extend CryptoBackend with pwhash + scalarMultBase --- .../src/crypto/backend.contract.test.ts | 43 +++++++++++++++++++ packages/shared/src/crypto/backend.ts | 24 +++++++++++ packages/shared/src/crypto/testBackend.ts | 8 ++++ 3 files changed, 75 insertions(+) create mode 100644 packages/shared/src/crypto/backend.contract.test.ts diff --git a/packages/shared/src/crypto/backend.contract.test.ts b/packages/shared/src/crypto/backend.contract.test.ts new file mode 100644 index 0000000..cdc993d --- /dev/null +++ b/packages/shared/src/crypto/backend.contract.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from 'vitest'; + +import type { CryptoBackend } from './backend'; +import { makeWasmTestBackend } from './testBackend'; + +describe('CryptoBackend contract — extended (pwhash + scalarMultBase)', () => { + it('pwhashConsts exposes the constants userKey.ts needs', async () => { + const backend: CryptoBackend = await makeWasmTestBackend(); + expect(backend.pwhashConsts.OPSLIMIT_MODERATE).toBeGreaterThan(0); + expect(backend.pwhashConsts.MEMLIMIT_MODERATE).toBeGreaterThan(0); + expect(backend.pwhashConsts.ALG_ARGON2ID13).toBeGreaterThan(0); + }); + + it('pwhash is deterministic for fixed input + salt', async () => { + const backend: CryptoBackend = await makeWasmTestBackend(); + const salt = new Uint8Array(16).fill(7); + const a = backend.pwhash( + 32, + 'hunter2', + salt, + backend.pwhashConsts.OPSLIMIT_MODERATE, + backend.pwhashConsts.MEMLIMIT_MODERATE, + backend.pwhashConsts.ALG_ARGON2ID13, + ); + const b = backend.pwhash( + 32, + 'hunter2', + salt, + backend.pwhashConsts.OPSLIMIT_MODERATE, + backend.pwhashConsts.MEMLIMIT_MODERATE, + backend.pwhashConsts.ALG_ARGON2ID13, + ); + expect(a).toEqual(b); + expect(a.length).toBe(32); + }); + + it('scalarMultBase maps the private key of a generated keypair to its public key', async () => { + const backend: CryptoBackend = await makeWasmTestBackend(); + const kp = backend.generateKeyPair(); + const derived = backend.scalarMultBase(kp.privateKey); + expect(derived).toEqual(kp.publicKey); + }); +}); diff --git a/packages/shared/src/crypto/backend.ts b/packages/shared/src/crypto/backend.ts index 39ae1da..22fcc2f 100644 --- a/packages/shared/src/crypto/backend.ts +++ b/packages/shared/src/crypto/backend.ts @@ -47,6 +47,30 @@ export interface CryptoBackend { // distributed per recipient via crypto_box envelopes. secretbox(plaintext: Uint8Array, nonce: Uint8Array, key: Uint8Array): Uint8Array; secretboxOpen(ciphertext: Uint8Array, nonce: Uint8Array, key: Uint8Array): Uint8Array; + + // --------------------------------------------------------------------- + // Key derivation + public-key derivation. Added so the user-key flow + // (Argon2id KDF, scalarmult-base for public-key derivation from a cached + // private key) does not have to import libsodium-wrappers-sumo at module + // top level — that import does not load in React Native's Hermes runtime. + // --------------------------------------------------------------------- + + readonly pwhashConsts: { + OPSLIMIT_MODERATE: number; + MEMLIMIT_MODERATE: number; + ALG_ARGON2ID13: number; + }; + + pwhash( + outLen: number, + password: string, + salt: Uint8Array, + opslimit: number, + memlimit: number, + alg: number, + ): Uint8Array; + + scalarMultBase(privateKey: Uint8Array): Uint8Array; } let current: CryptoBackend | null = null; diff --git a/packages/shared/src/crypto/testBackend.ts b/packages/shared/src/crypto/testBackend.ts index d5f88c0..d84eb33 100644 --- a/packages/shared/src/crypto/testBackend.ts +++ b/packages/shared/src/crypto/testBackend.ts @@ -22,5 +22,13 @@ export async function makeWasmTestBackend(): Promise { boxOpen: (c, n, pk, sk) => sodium.crypto_box_open_easy(c, n, pk, sk), secretbox: (m, n, k) => sodium.crypto_secretbox_easy(m, n, k), secretboxOpen: (c, n, k) => sodium.crypto_secretbox_open_easy(c, n, k), + pwhashConsts: { + OPSLIMIT_MODERATE: sodium.crypto_pwhash_OPSLIMIT_MODERATE, + MEMLIMIT_MODERATE: sodium.crypto_pwhash_MEMLIMIT_MODERATE, + ALG_ARGON2ID13: sodium.crypto_pwhash_ALG_ARGON2ID13, + }, + pwhash: (outLen, password, salt, opslimit, memlimit, alg) => + sodium.crypto_pwhash(outLen, password, salt, opslimit, memlimit, alg), + scalarMultBase: (priv) => sodium.crypto_scalarmult_base(priv), }; }