lib.ts (3663B)
1 import { schnorr } from "@noble/curves/secp256k1"; 2 import { hexToBytes } from "@noble/hashes/utils"; 3 import { makeEvent } from "@welshman/util"; 4 import { KIND_POST } from "@radroots/events-bindings"; 5 import { time_now_ms, time_now_s, uuidv4 } from "@radroots/utils"; 6 import { 7 finalizeEvent, 8 getEventHash, 9 nip19, 10 type NostrEvent as NostrToolsEvent, 11 } from "nostr-tools"; 12 import { NostrEventSign, NostrEventTags, NostrNeventEncode } from "../types/lib.js"; 13 import type { NostrEvent, NostrEventFigure, NostrSignedEvent } from "../types/nostr.js"; 14 import { nostr_tag_client } from "../utils/tags.js"; 15 import type { NostrEventBasis } from "./subscription.js"; 16 17 export const get_event_tag = (tags: NostrEventTags, key: string): string => 18 tags.find(t => t[0] === key)?.[1] ?? ""; 19 20 export const get_event_tags = (tags: NostrEventTags, key: string): NostrEventTags => 21 tags.filter(t => t[0] === key); 22 23 export const parse_nostr_event_basis = <T extends number>( 24 event: NostrEvent, 25 kind: T, 26 ): NostrEventBasis<T> | undefined => { 27 if (!event || typeof event.created_at !== "number" || event.kind !== kind) return undefined; 28 return { 29 id: event.id, 30 published_at: event.created_at, 31 author: event.pubkey, 32 kind: event.kind as T, 33 }; 34 }; 35 36 export const nostr_event_verify = (event: NostrToolsEvent): boolean => { 37 const hash = getEventHash(event); 38 if (hash !== event.id) return false; 39 const valid = schnorr.verify(event.sig, hash, event.pubkey); 40 return valid; 41 }; 42 43 export const nostr_event_sign = (opts: NostrEventSign): NostrToolsEvent => { 44 return finalizeEvent(opts.event, hexToBytes(opts.secret_key)); 45 }; 46 47 export const nostr_event_sign_attest = (secret_key: string): NostrToolsEvent => { 48 return nostr_event_sign({ 49 secret_key, 50 event: { 51 kind: KIND_POST, 52 created_at: time_now_s(), 53 tags: [], 54 content: uuidv4(), 55 }, 56 }); 57 }; 58 59 export const nostr_event_verify_serialized = async ( 60 event_serialized: string, 61 ): Promise<{ public_key: string } | undefined> => { 62 try { 63 const event = JSON.parse(event_serialized) as NostrToolsEvent; 64 const hash = getEventHash(event); 65 if (hash !== event.id) return undefined; 66 const valid = schnorr.verify(event.sig, hash, event.pubkey); 67 if (valid) return { public_key: String(event.pubkey) }; 68 return undefined; 69 } catch { 70 return undefined; 71 } 72 }; 73 74 export const nostr_nevent_encode = (opts: NostrNeventEncode): string => { 75 return nip19.neventEncode(opts); 76 }; 77 78 export const nostr_event_create = async ( 79 opts: NostrEventFigure<{ 80 basis: { 81 kind: number; 82 content: string; 83 tags?: NostrEventTags; 84 }; 85 }>, 86 ): Promise<NostrSignedEvent | undefined> => { 87 try { 88 const time_now = time_now_ms(); 89 const published_at = opts.date_published 90 ? Math.floor(opts.date_published.getTime() / 1000).toString() 91 : time_now.toString(); 92 const tags: NostrEventTags = [["published_at", published_at]]; 93 if (opts.basis.tags?.length) tags.push(...opts.basis.tags); 94 if (opts.client) { 95 const d_tag = tags.find(tag => tag[0] === "d")?.[1] 96 ?? tags.find(tag => tag[0] === "d_tag")?.[1]; 97 tags.push(nostr_tag_client(opts.client, d_tag)); 98 } 99 const ev = makeEvent(opts.basis.kind, { 100 content: opts.basis.content, 101 tags, 102 created_at: time_now, 103 }); 104 return await opts.signer.sign(ev); 105 } catch { 106 return undefined; 107 } 108 };