Files
regress/frontend/src/lib/nsec.ts
T
ssmithx cd3d82d1c1 Add unsafe raw-nsec login fallback
Collapsed-by-default 'paste an nsec (unsafe)' option on the login screen,
signs the NIP-98 login event client-side with nostr-tools and never
persists the key (used once in memory, discarded) — for testing/kiosk
use without a NIP-07 extension. Clearly labeled as unsafe in the UI.
2026-08-05 13:56:50 +00:00

46 lines
1.5 KiB
TypeScript

// 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))}`;
}