import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools' import { SecureStorage } from '@/lib/crypto/encryption' import { bytesToHex, hexToBytes } from '@/lib/utils/crypto' export interface NostrIdentity { privateKey: string publicKey: string npub: string nsec: string } export interface NostrProfile { name?: string display_name?: string about?: string picture?: string banner?: string website?: string nip05?: string lud16?: string // Lightning address } export class IdentityManager { private static readonly STORAGE_KEY = 'nostr_identity' private static readonly PROFILE_KEY = 'nostr_profile' /** * Generate a new Nostr identity */ static generateIdentity(): NostrIdentity { const privateKey = generateSecretKey() const publicKey = getPublicKey(privateKey) return { privateKey: bytesToHex(privateKey), publicKey, npub: nip19.npubEncode(publicKey), nsec: nip19.nsecEncode(privateKey) } } /** * Import identity from private key (hex or nsec format) */ static importIdentity(privateKeyInput: string): NostrIdentity { let privateKeyBytes: Uint8Array try { // Try to decode as nsec first if (privateKeyInput.startsWith('nsec')) { const decoded = nip19.decode(privateKeyInput) if (decoded.type === 'nsec') { privateKeyBytes = decoded.data } else { throw new Error('Invalid nsec format') } } else { // Try as hex string if (privateKeyInput.length !== 64) { throw new Error('Private key must be 64 hex characters') } privateKeyBytes = hexToBytes(privateKeyInput) } if (privateKeyBytes.length !== 32) { throw new Error('Private key must be 32 bytes') } const publicKey = getPublicKey(privateKeyBytes) return { privateKey: bytesToHex(privateKeyBytes), publicKey, npub: nip19.npubEncode(publicKey), nsec: nip19.nsecEncode(privateKeyBytes) } } catch (error) { throw new Error(`Failed to import identity: ${error instanceof Error ? error.message : 'Unknown error'}`) } } /** * Save identity to localStorage (encrypted) */ static async saveIdentity(identity: NostrIdentity, password?: string): Promise { try { let dataToStore: any = identity if (password) { if (!SecureStorage.isSupported()) { throw new Error('Secure encryption is not supported in this browser') } // Encrypt sensitive data using Web Crypto API const sensitiveData = JSON.stringify({ privateKey: identity.privateKey, nsec: identity.nsec }) const encryptedData = await SecureStorage.encrypt(sensitiveData, password) dataToStore = { publicKey: identity.publicKey, npub: identity.npub, encrypted: true, encryptedData } } localStorage.setItem(this.STORAGE_KEY, JSON.stringify(dataToStore)) } catch (error) { throw new Error(`Failed to save identity: ${error instanceof Error ? error.message : 'Unknown error'}`) } } /** * Load identity from localStorage */ static async loadIdentity(password?: string): Promise { try { const stored = localStorage.getItem(this.STORAGE_KEY) if (!stored) return null const storedData = JSON.parse(stored) if (storedData.encrypted && password) { if (!SecureStorage.isSupported()) { throw new Error('Secure encryption is not supported in this browser') } // Decrypt sensitive data const decryptedSensitiveData = await SecureStorage.decrypt(storedData.encryptedData, password) const { privateKey, nsec } = JSON.parse(decryptedSensitiveData) return { privateKey, publicKey: storedData.publicKey, npub: storedData.npub, nsec } } else if (storedData.encrypted && !password) { throw new Error('Password required to decrypt stored identity') } // Non-encrypted identity (legacy support) const identity = storedData as NostrIdentity // Validate the loaded identity if (!identity.privateKey || !identity.publicKey) { throw new Error('Invalid stored identity') } return identity } catch (error) { console.error('Failed to load identity:', error) throw error } } /** * Clear stored identity */ static clearIdentity(): void { localStorage.removeItem(this.STORAGE_KEY) localStorage.removeItem(this.PROFILE_KEY) } /** * Check if identity exists in storage */ static hasStoredIdentity(): boolean { return !!localStorage.getItem(this.STORAGE_KEY) } /** * Check if stored identity is encrypted */ static isStoredIdentityEncrypted(): boolean { try { const stored = localStorage.getItem(this.STORAGE_KEY) if (!stored) return false const storedData = JSON.parse(stored) return !!storedData.encrypted } catch { return false } } /** * Save user profile */ static saveProfile(profile: NostrProfile): void { localStorage.setItem(this.PROFILE_KEY, JSON.stringify(profile)) } /** * Load user profile */ static loadProfile(): NostrProfile | null { try { const stored = localStorage.getItem(this.PROFILE_KEY) return stored ? JSON.parse(stored) : null } catch (error) { console.error('Failed to load profile:', error) return null } } }