From 8588de25f16486b56ef0ad533013bd8cb48ff120 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 9 Jan 2025 23:44:40 -0800 Subject: nostr signing --- src/lib/nostr.ts | 95 ++++++++++++++++++++++++++++++++++++++++++++--------- src/lib/settings.ts | 14 ++++---- src/lib/store.ts | 13 ++++++-- src/lib/utils.ts | 20 ----------- 4 files changed, 96 insertions(+), 46 deletions(-) (limited to 'src/lib') diff --git a/src/lib/nostr.ts b/src/lib/nostr.ts index 77c1d60a..85f1bdaa 100644 --- a/src/lib/nostr.ts +++ b/src/lib/nostr.ts @@ -2,18 +2,21 @@ import { Buffer } from "buffer"; import { browser } from "$app/environment"; import { decrypted, + eventToSign, password as pw, passwordPrompt, - signaturePrompt, + signer as $signer, } from "$lib/store"; import { post, wait } from "$lib/utils"; import { type EventTemplate, finalizeEvent, + getEventHash, getPublicKey, nip04, nip19, } from "nostr-tools"; +import { Relay } from "nostr-tools/relay"; import { get } from "svelte/store"; import { @@ -21,8 +24,6 @@ import { encrypt as nip49encrypt, } from "nostr-tools/nip49"; -import { bech32m } from "@scure/base"; - type User = { [key: string]: any; }; @@ -101,19 +102,17 @@ export const encryptNsec = async (nsec: string) => { return nip49encrypt(d, await getPassword()); }; -type SignParams = { - event: EventTemplate; - user: User; -}; - -export const sign = async ({ event, user }: SignParams) => { - let sk; - if (user.nsec) { - sk = await getPrivateKey(user); - event = finalizeEvent(event, sk); - } else { - signaturePrompt.set(event); - } +export const sign = async (event) => { + eventToSign.set(event); + let unsubscribe; + const { method, params } = await new Promise((r) => { + unsubscribe = $signer.subscribe((v) => v?.ready && r(v)); + }); + await unsubscribe(); + const signedEvent = await signingMethods[method](event, params); + eventToSign.set(signedEvent); + setTimeout(() => eventToSign.set(null), 1000); + return signedEvent; }; export const send = (event: EventTemplate) => { @@ -125,3 +124,67 @@ const getPassword = async (): Promise => { await wait(() => !!get(pw)); return get(pw) || ""; }; + +export const nostrConnectRelay = "wss://relay.nsec.app"; + +const signingMethods = { + async connect(event, { sk, pk, pubkey }) { + const id = crypto.randomUUID(); + const signEvent = { + id, + method: "sign_event", + params: [JSON.stringify(event)], + }; + + const content = await nip04.encrypt(sk, pk, JSON.stringify(signEvent)); + + const signedSignEvent = finalizeEvent( + { + created_at: Math.round(Date.now() / 1000), + kind: 24133, + pubkey, + content, + tags: [["p", pk]], + }, + sk, + ); + + const relay = await Relay.connect(nostrConnectRelay); + relay.publish(signedSignEvent); + + return new Promise((resolve) => { + relay.subscribe([{ kinds: [24133], "#p": [pubkey] }], { + async onevent(event) { + pk = event.pubkey; + try { + const response = JSON.parse( + await nip04.decrypt(sk, pk, event.content), + ); + + if (response.id === id) resolve(JSON.parse(response.result)); + } catch (e) { + console.log("failed to parse nostr connect event", e); + } + }, + }); + }); + }, + + async nsec(event, { sk }) { + return finalizeEvent(event, sk); + }, + + async extension(event) { + return window.nostr.signEvent(event); + }, + + async signer(event, { pubkey, sig }) { + event.pubkey = pubkey; + const signedEvent = { + ...event, + id: getEventHash(event), + sig, + }; + return signedEvent; + }, +}; diff --git a/src/lib/settings.ts b/src/lib/settings.ts index 3ce1edd5..53a25e18 100644 --- a/src/lib/settings.ts +++ b/src/lib/settings.ts @@ -1,4 +1,4 @@ -import { auth, fd, get, post } from "$lib/utils"; +import { auth, fd, post } from "$lib/utils"; import { fail } from "@sveltejs/kit"; type Error = { @@ -16,16 +16,14 @@ export default async ({ cookies, request }) => { form.autowithdraw = form.autowithdraw === "on"; } - let user = { ...(await get("/me", auth(cookies))), ...form }; - try { - ({ user } = await post("/user", user, auth(cookies))); + const { user } = await post("/user", form, auth(cookies)); + + if (user.language) cookies.set("lang", user.language, { path: "/" }); + + return { user, success: true }; } catch (e) { const { message } = e as Error; return fail(400, { message }); } - - if (user.language) cookies.set("lang", user.language, { path: "/" }); - - return { user, success: true }; }; diff --git a/src/lib/store.ts b/src/lib/store.ts index 0aba3d46..23b38ace 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -28,7 +28,13 @@ const persistLocal = (key, defaultValue: any = undefined) => { s.subscribe((v) => { try { - browser && localStorage.setItem(key, JSON.stringify(v)); + if (browser) { + if (v) { + localStorage.setItem(key, JSON.stringify(v)); + } else { + localStorage.removeItem(key); + } + } } catch (e) { console.log("problem setting key", v); console.log(e); @@ -54,7 +60,10 @@ export const mnemonic = persistSession("mnemonic"); export const newPayment = persistLocal("newPayment"); export const password = writable(); export const passwordPrompt = writable(); -export const signaturePrompt = writable(); +export const eventToSign = writable(); +export const pubkey = persistLocal("pubkey"); +export const signer = persistLocal("signer"); +export const selectSigner = writable(); export const pin = persistLocal("pin"); export const rate = writable(); export const request = writable(); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index b21ce176..e39c2adc 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -338,26 +338,6 @@ export const wait = async ( return f(); }; -export const stretch = async (password: string, salt: Uint8Array) => - crypto.subtle.deriveKey( - { - name: "PBKDF2", - salt, - iterations: 100000, - hash: "SHA-256", - }, - await crypto.subtle.importKey( - "raw", - new TextEncoder().encode(password), - { name: "PBKDF2" }, - false, - ["deriveBits", "deriveKey"], - ), - { name: "AES-GCM", length: 256 }, - true, - ["encrypt", "decrypt"], - ); - export const types = { bitcoin: "bitcoin", liquid: "liquid", -- cgit v1.2.3