import { Event, nip04 } from 'nostr-tools' import { SimplePool } from 'nostr-tools' import { decryptArticleContent, type DecryptionKey } from './articleEncryption' const RELAY_URL = process.env.NEXT_PUBLIC_NOSTR_RELAY_URL ?? 'wss://relay.damus.io' function createPrivateMessageFilters(eventId: string, publicKey: string, authorPubkey: string) { return [ { kinds: [4], // Encrypted direct messages '#p': [publicKey], '#e': [eventId], // Filter by event ID to find relevant private messages authors: [authorPubkey], // Filter by author of the original article limit: 10, // Limit to recent messages }, ] } function decryptContent(privateKey: string, event: Event): Promise { return Promise.resolve(nip04.decrypt(privateKey, event.pubkey, event.content)).then((decrypted) => decrypted ? decrypted : null ) } /** * Get private content for an article (encrypted message from author) * This function now returns the decryption key instead of the full content */ export function getPrivateContent( pool: SimplePool, eventId: string, authorPubkey: string, privateKey: string, publicKey: string ): Promise { if (!privateKey || !pool || !publicKey) { throw new Error('Private key not set or pool not initialized') } return new Promise((resolve) => { let resolved = false const sub = pool.sub([RELAY_URL], createPrivateMessageFilters(eventId, publicKey, authorPubkey)) const finalize = (result: string | null) => { if (resolved) { return } resolved = true sub.unsub() resolve(result) } sub.on('event', (event: Event) => { void decryptContent(privateKey, event) .then((content) => { if (content) { finalize(content) } }) .catch((e) => { console.error('Error decrypting content:', e) }) }) sub.on('eose', () => finalize(null)) setTimeout(() => finalize(null), 5000) }) } /** * Get decryption key for an article from private messages * Returns the decryption key and IV if found */ export async function getDecryptionKey( pool: SimplePool, eventId: string, authorPubkey: string, recipientPrivateKey: string, recipientPublicKey: string ): Promise { if (!recipientPrivateKey || !pool || !recipientPublicKey) { throw new Error('Private key not set or pool not initialized') } return new Promise((resolve) => { let resolved = false const sub = pool.sub([RELAY_URL], createPrivateMessageFilters(eventId, recipientPublicKey, authorPubkey)) const finalize = (result: DecryptionKey | null) => { if (resolved) { return } resolved = true sub.unsub() resolve(result) } sub.on('event', async (event: Event) => { try { const decryptedContent = await decryptContent(recipientPrivateKey, event) if (decryptedContent) { try { // Try to parse as decryption key (new format) const keyData = JSON.parse(decryptedContent) as DecryptionKey if (keyData.key && keyData.iv) { finalize(keyData) return } } catch { // If parsing fails, it might be old format (full content) // Return null to indicate we need to use the old method } } } catch (e) { console.error('Error decrypting decryption key:', e) } }) sub.on('eose', () => finalize(null)) setTimeout(() => finalize(null), 5000) }) } /** * Decrypt article content using the decryption key from private message */ export async function decryptArticleContentWithKey( encryptedContent: string, decryptionKey: DecryptionKey ): Promise { return decryptArticleContent(encryptedContent, decryptionKey.key, decryptionKey.iv) }