From 18df062a099811355cafc2024e30a7c2b68c7984 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 26 Feb 2026 16:07:52 -0800 Subject: client side password auth --- src/lib/deriveAuthKey.ts | 23 +++++++++++++++++++++++ src/lib/utils.ts | 12 ++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 src/lib/deriveAuthKey.ts (limited to 'src/lib') diff --git a/src/lib/deriveAuthKey.ts b/src/lib/deriveAuthKey.ts new file mode 100644 index 00000000..9215b515 --- /dev/null +++ b/src/lib/deriveAuthKey.ts @@ -0,0 +1,23 @@ +import { scryptAsync } from "@noble/hashes/scrypt.js"; +import { bytesToHex } from "@noble/hashes/utils.js"; +import { getPublicKey } from "nostr-tools"; +import { rememberPrfKey, defaultRememberForMs } from "$lib/passwordCache"; + +export async function deriveAuthKeypair(username: string, password: string) { + const salt = `coinos:auth:${username.toLowerCase().replace(/\s/g, "")}`; + const sk = await scryptAsync(password, salt, { N: 2 ** 16, r: 8, p: 1, dkLen: 32 }); + const pubkey = getPublicKey(sk); + return { sk, pubkey, skHex: bytesToHex(sk) }; +} + +export async function deriveWalletEntropy(sk: Uint8Array): Promise { + const { finalizeEvent } = await import("nostr-tools"); + const event = { kind: 1, created_at: 0, content: "coinos-wallet-key-derivation", tags: [] }; + const signed = finalizeEvent(event as any, sk); + const sigBytes = new Uint8Array(signed.sig.length / 2); + for (let i = 0; i < signed.sig.length; i += 2) + sigBytes[i / 2] = parseInt(signed.sig.slice(i, i + 2), 16); + const entropy = await crypto.subtle.digest("SHA-256", sigBytes); + rememberPrfKey(entropy, defaultRememberForMs); + return entropy; +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 4bf96d6e..eee6903e 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -164,6 +164,7 @@ export const login = async ( token?: string; challenge?: string; recaptcha?: string; + authPubkey?: string; }, cookies, ip: string, @@ -509,6 +510,17 @@ export const register = async ( }; if (ip) headers["cf-connecting-ip"] = ip; + // Derive authPubkey if password provided + if (user.password && user.username) { + try { + const { deriveAuthKeypair } = await import("$lib/deriveAuthKey"); + const { pubkey: authPubkey } = await deriveAuthKeypair(user.username, user.password); + user.authPubkey = authPubkey; + } catch (e) { + console.log("Failed to derive authPubkey during registration", e); + } + } + let sk, token; try { ({ sk, token } = await post("/register", { user }, headers)); -- cgit v1.2.3