From 757a59c5c9c4b56120446cae1ac495773b9905c8 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Wed, 8 Apr 2026 22:33:43 -0700 Subject: breez style passkey derivation --- src/routes/(app)/settings/security/+page.svelte | 53 ++++++++++++++++++++++++- src/routes/(static)/login/+page.svelte | 4 +- 2 files changed, 53 insertions(+), 4 deletions(-) (limited to 'src/routes') diff --git a/src/routes/(app)/settings/security/+page.svelte b/src/routes/(app)/settings/security/+page.svelte index 80d09ace..894536dd 100644 --- a/src/routes/(app)/settings/security/+page.svelte +++ b/src/routes/(app)/settings/security/+page.svelte @@ -4,10 +4,14 @@ import PhNumpadBold from "virtual:icons/ph/numpad-bold"; import PhDeviceMobileBold from "virtual:icons/ph/device-mobile-bold"; import PhFingerprintBold from "virtual:icons/ph/fingerprint-bold"; + import PhCopyBold from "virtual:icons/ph/copy-bold"; + import PhEyeBold from "virtual:icons/ph/eye-bold"; + import PhEyeSlashBold from "virtual:icons/ph/eye-slash-bold"; import { getContext } from "svelte"; import { tick } from "svelte"; import { t } from "$lib/translations"; import Pin from "$comp/Pin.svelte"; + import Mnemonic from "$comp/Mnemonic.svelte"; import Qr from "$comp/Qr.svelte"; import Spinner from "$comp/Spinner.svelte"; import { copy, post, success, fail, hashPin } from "$lib/utils"; @@ -35,7 +39,8 @@ nsec = $state(), otp: any = $state(), pin: any = $state(""), - revealSeed, + recoveryMnemonic: string | undefined = $state(), + revealSeed = $state(false), revealNsec = $state(), token: any = $state(""), setting2fa = $state(), @@ -58,10 +63,30 @@ }; let toggleSeed = async () => { + if (revealSeed) { + revealSeed = false; + recoveryMnemonic = undefined; + return; + } try { - revealSeed = !revealSeed; + let prfKey = getCachedPrfKey(); + if (!prfKey) { + const { getWalletEntropy } = await import("$lib/walletEntropy"); + prfKey = await getWalletEntropy(); + } + if (!prfKey) { + fail($t("accounts.failedToDecryptSeed")); + return; + } + const [{ entropyToMnemonic }, { wordlist }] = await Promise.all([ + import("@scure/bip39"), + import("@scure/bip39/wordlists/english.js"), + ]); + recoveryMnemonic = entropyToMnemonic(new Uint8Array(prfKey).slice(0, 16), wordlist); + revealSeed = true; } catch (e) { console.log(e); + fail($t("accounts.failedToDecryptSeed")); } }; @@ -268,3 +293,27 @@ Add Passkey + +
+ {$t("user.settings.recoveryPhrase")} +

{$t("user.settings.recoveryPhraseDescription")}

+ + + {#if revealSeed && recoveryMnemonic} +
+ + +
+ {/if} +
diff --git a/src/routes/(static)/login/+page.svelte b/src/routes/(static)/login/+page.svelte index 0132ac75..e73902ea 100644 --- a/src/routes/(static)/login/+page.svelte +++ b/src/routes/(static)/login/+page.svelte @@ -253,7 +253,7 @@ let passkeyLogin = async () => { passkeyLoading = true; try { - const { credential, challengeId, prfKey } = await loginWithPasskey(); + const { credential, challengeId, prfKey, legacyKey } = await loginWithPasskey(); const formData = new FormData(); formData.append("credential", JSON.stringify(credential)); @@ -273,7 +273,7 @@ passkeyMethodId(credential.id), prfKey, result.data?.encryptedKeys, - prfKey, + legacyKey, ); } catch (e) { console.log("Canonical key resolution failed", e); -- cgit v1.2.3