/** * Nsec key storage (NIP-49 encrypted) * Stores encrypted nsec keys in IndexedDB */ import { getDB } from './indexeddb-store.js'; import { encryptPrivateKey, decryptPrivateKey } from '../security/key-management.js'; export interface StoredNsecKey { id: string; // pubkey ncryptsec: string; // NIP-49 encrypted key pubkey: string; // Public key for identification created_at: number; keyType?: 'nsec' | 'anonymous'; // Distinguish between nsec and anonymous keys } /** * Store an nsec key (encrypted) * NEVER log the nsec or password - they are sensitive */ export async function storeNsecKey( nsec: string, password: string, pubkey: string ): Promise { // Encrypt the private key - never store plaintext const ncryptsec = await encryptPrivateKey(nsec, password); const db = await getDB(); // Check if there's an existing key with this pubkey // If so, verify it matches before overwriting const existing = await db.get('keys', pubkey); if (existing) { const existingKey = existing as StoredNsecKey; // If the existing key is an nsec key, verify it matches if (existingKey.keyType === 'nsec' && existingKey.pubkey === pubkey) { // Key exists and matches - we'll overwrite it // This is fine, we're updating with the same pubkey } } const stored: StoredNsecKey = { id: pubkey, ncryptsec, pubkey, created_at: Date.now(), keyType: 'nsec' }; // Store encrypted key - never log the nsec or password await db.put('keys', stored); // Note: Verification is done in authenticateWithNsec after storage // to ensure the key is committed to IndexedDB and can be retrieved } /** * Retrieve and decrypt an nsec key * NEVER log the password or decrypted nsec */ export async function getNsecKey( pubkey: string, password: string ): Promise { const db = await getDB(); const stored = await db.get('keys', pubkey); if (!stored) return null; const key = stored as StoredNsecKey; if (!key.ncryptsec || typeof key.ncryptsec !== 'string') { throw new Error('Stored nsec key has invalid ncryptsec format - key may be corrupted'); } // Validate ncryptsec format before attempting decryption if (!key.ncryptsec.startsWith('ncryptsec1')) { throw new Error('Stored nsec key has invalid encryption format - key may need to be re-saved'); } // Decrypt and return - never log the result try { return await decryptPrivateKey(key.ncryptsec, password); } catch (error) { // Provide helpful error without exposing sensitive data if (error instanceof Error && error.message.includes('Invalid password')) { throw error; // Re-throw password errors } throw new Error('Failed to decrypt stored nsec key - the key may be corrupted or the password is incorrect'); } } /** * Check if an nsec key exists for a pubkey */ export async function hasNsecKey(pubkey: string): Promise { const db = await getDB(); const stored = await db.get('keys', pubkey); return stored !== undefined; } /** * Delete an nsec key */ export async function deleteNsecKey(pubkey: string): Promise { const db = await getDB(); await db.delete('keys', pubkey); } /** * List all stored nsec keys (pubkeys only) */ export async function listNsecKeys(): Promise> { const db = await getDB(); const keys: Array<{ pubkey: string; created_at: number; keyType?: 'nsec' | 'anonymous' }> = []; const tx = db.transaction('keys', 'readonly'); for await (const cursor of tx.store.iterate()) { const key = cursor.value as StoredNsecKey; // Only include nsec keys (not anonymous, which are handled separately) if (!key.keyType || key.keyType === 'nsec') { keys.push({ pubkey: key.pubkey, created_at: key.created_at, keyType: key.keyType || 'nsec' }); } } await tx.done; return keys; }