aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-02-27 14:46:27 -0800
committerAdam Soltys <[email protected]>2026-02-27 14:46:27 -0800
commit8ffcf769052461e03a2c69220392cb37a623f3f4 (patch)
tree5b0b16269a729fbe9db44c65b3a0da3d95c11b78 /src/lib
parent6ba952f16fc60fc56f0834e5b1d08671885618c5 (diff)
downloadcoinos-ui-8ffcf769052461e03a2c69220392cb37a623f3f4.tar.gz
coinos-ui-8ffcf769052461e03a2c69220392cb37a623f3f4.zip
2 step register
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/deriveAuthKey.ts5
-rw-r--r--src/lib/keyWrapping.ts61
-rw-r--r--src/lib/passkey.ts7
-rw-r--r--src/lib/utils.ts2
-rw-r--r--src/lib/walletEntropy.ts10
5 files changed, 72 insertions, 13 deletions
diff --git a/src/lib/deriveAuthKey.ts b/src/lib/deriveAuthKey.ts
index 9215b515..eb498e0a 100644
--- a/src/lib/deriveAuthKey.ts
+++ b/src/lib/deriveAuthKey.ts
@@ -1,7 +1,6 @@
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, "")}`;
@@ -17,7 +16,5 @@ export async function deriveWalletEntropy(sk: Uint8Array): Promise<ArrayBuffer>
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;
+ return crypto.subtle.digest("SHA-256", sigBytes);
}
diff --git a/src/lib/keyWrapping.ts b/src/lib/keyWrapping.ts
new file mode 100644
index 00000000..b176e741
--- /dev/null
+++ b/src/lib/keyWrapping.ts
@@ -0,0 +1,61 @@
+import { prfEncrypt, prfDecrypt } from "$lib/crypto";
+import { rememberPrfKey, defaultRememberForMs, getCachedPrfKey } from "$lib/passwordCache";
+import { post } from "$lib/utils";
+
+export function generateCanonicalKey(): ArrayBuffer {
+ return crypto.getRandomValues(new Uint8Array(32)).buffer as ArrayBuffer;
+}
+
+export async function wrapCanonicalKey(
+ wrappingKey: ArrayBuffer,
+ canonicalKey: ArrayBuffer,
+): Promise<string> {
+ return prfEncrypt(wrappingKey, new Uint8Array(canonicalKey));
+}
+
+export async function unwrapCanonicalKey(
+ wrappingKey: ArrayBuffer,
+ encrypted: string,
+): Promise<ArrayBuffer> {
+ const bytes = await prfDecrypt(wrappingKey, encrypted);
+ return bytes.buffer as ArrayBuffer;
+}
+
+export async function resolveCanonicalKey(
+ methodId: string,
+ wrappingKey: ArrayBuffer,
+ encryptedKeys: Record<string, string> | null | undefined,
+ oldPrfKey: ArrayBuffer,
+): Promise<{ canonicalKey: ArrayBuffer; encryptedKeys: Record<string, string> }> {
+ if (encryptedKeys && encryptedKeys[methodId]) {
+ const canonicalKey = await unwrapCanonicalKey(wrappingKey, encryptedKeys[methodId]);
+ rememberPrfKey(canonicalKey, defaultRememberForMs);
+ return { canonicalKey, encryptedKeys };
+ }
+
+ // Migration: old PRF key becomes canonical key
+ const canonicalKey = oldPrfKey;
+ const wrapped = await wrapCanonicalKey(wrappingKey, canonicalKey);
+ const updated = { ...(encryptedKeys || {}), [methodId]: wrapped };
+ await saveEncryptedKeys(updated);
+ rememberPrfKey(canonicalKey, defaultRememberForMs);
+ return { canonicalKey, encryptedKeys: updated };
+}
+
+export async function saveEncryptedKeys(
+ encryptedKeys: Record<string, string>,
+): Promise<void> {
+ await post("/post/user", { encryptedKeys });
+}
+
+export function passwordMethodId(): string {
+ return "password";
+}
+
+export function passkeyMethodId(credentialId: string): string {
+ return `passkey:${credentialId}`;
+}
+
+export function nostrMethodId(pubkey: string): string {
+ return `nostr:${pubkey}`;
+}
diff --git a/src/lib/passkey.ts b/src/lib/passkey.ts
index 053eb06a..7c9e98cc 100644
--- a/src/lib/passkey.ts
+++ b/src/lib/passkey.ts
@@ -10,12 +10,15 @@ function authHeader(token: string) {
return { authorization: `Bearer ${token}` };
}
-export async function registerPasskey(token?: string): Promise<ArrayBuffer> {
+export async function registerPasskey(
+ token?: string,
+): Promise<{ prfKey: ArrayBuffer; credentialId: string }> {
const headers = token ? authHeader(token) : undefined;
const options = await post("/passkey/register/options", {}, headers);
const registration = await startRegistration({ optionsJSON: options });
await post("/passkey/register/verify", registration, headers);
- return deriveKey(registration.id);
+ const prfKey = await deriveKey(registration.id);
+ return { prfKey, credentialId: registration.id };
}
export async function loginWithPasskey(): Promise<{
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index eee6903e..e37556e3 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -214,6 +214,8 @@ export const login = async (
});
} catch {}
}
+
+ return u;
};
export const auth = (cookies) => ({
diff --git a/src/lib/walletEntropy.ts b/src/lib/walletEntropy.ts
index 25bd49b8..95b3e903 100644
--- a/src/lib/walletEntropy.ts
+++ b/src/lib/walletEntropy.ts
@@ -1,5 +1,5 @@
import { browser } from "$app/environment";
-import { getCachedPrfKey, rememberPrfKey, defaultRememberForMs } from "$lib/passwordCache";
+import { getCachedPrfKey } from "$lib/passwordCache";
const DERIVATION_EVENT = {
kind: 1,
@@ -27,9 +27,7 @@ export async function tryDeriveFromNsec(): Promise<ArrayBuffer | null> {
const { nip19, finalizeEvent } = await import("nostr-tools");
const sk = nip19.decode(nsec).data as Uint8Array;
const signed = finalizeEvent({ ...DERIVATION_EVENT } as any, sk);
- const entropy = await hashSignature(signed.sig);
- rememberPrfKey(entropy, defaultRememberForMs);
- return entropy;
+ return await hashSignature(signed.sig);
} catch (e) {
console.log("nsec entropy derivation failed", e);
return null;
@@ -60,7 +58,5 @@ export async function deriveNostrEntropy(): Promise<ArrayBuffer> {
const { sign } = await import("$lib/nostr");
const signed = await sign({ ...DERIVATION_EVENT });
- const entropy = await hashSignature(signed.sig);
- rememberPrfKey(entropy, defaultRememberForMs);
- return entropy;
+ return hashSignature(signed.sig);
}