// Raw-nsec signing — an *unsafe* login fallback for when there's no NIP-07 // extension or NIP-46 signer available (testing, kiosk devices, etc). The // private key only ever lives in page memory for the duration of one sign // call; it's never persisted (no localStorage/sessionStorage), matching how // the NIP-07 path also never retains key material — only the resulting // session cookie survives. import { nip19 } from 'nostr-tools'; import { finalizeEvent, getPublicKey } from 'nostr-tools/pure'; import type { SignedEvent, UnsignedEvent } from './nip07'; export function isValidNsec(nsec: string): boolean { try { return nip19.decode(nsec.trim()).type === 'nsec'; } catch { return false; } } function decodeNsec(nsec: string): Uint8Array { const decoded = nip19.decode(nsec.trim()); if (decoded.type !== 'nsec') throw new Error('Not an nsec key'); return decoded.data; } export function nsecPubkey(nsec: string): string { return getPublicKey(decodeNsec(nsec)); } function signWithNsec(nsec: string, event: UnsignedEvent): SignedEvent { return finalizeEvent(event, decodeNsec(nsec)); } /** Sign a NIP-98 (kind 27235) event with a raw nsec and return the Authorization header value. */ export function buildNip98HeaderWithNsec(nsec: string, url: string, method: string): string { const event = signWithNsec(nsec, { kind: 27235, created_at: Math.floor(Date.now() / 1000), content: '', tags: [ ['u', url], ['method', method], ], }); return `Nostr ${btoa(JSON.stringify(event))}`; }