import 'package:nostr_tools/nostr_tools.dart'; /// Represents a Nostr keypair (private and public keys). class NostrKeyPair { /// Private key in hex format (32 bytes, 64 hex characters). final String privateKey; /// Public key in hex format (32 bytes, 64 hex characters). final String publicKey; /// Key API instance for key operations. static final _keyApi = KeyApi(); /// NIP-19 API instance for bech32 encoding/decoding. static final _nip19 = Nip19(); /// Creates a [NostrKeyPair] with the provided keys. /// /// [privateKey] - Private key in hex format. /// [publicKey] - Public key in hex format. NostrKeyPair({ required this.privateKey, required this.publicKey, }); /// Generates a new Nostr keypair using nostr_tools. /// /// Returns a new [NostrKeyPair] with random private and public keys. factory NostrKeyPair.generate() { final privateKey = _keyApi.generatePrivateKey(); final publicKey = _keyApi.getPublicKey(privateKey); return NostrKeyPair( privateKey: privateKey, publicKey: publicKey, ); } /// Creates a [NostrKeyPair] from nsec (private key in bech32 format). /// /// [nsec] - Private key in nsec format (e.g., 'nsec1...'). /// /// Returns a [NostrKeyPair] with the decoded private key and derived public key. /// /// Throws [FormatException] if nsec is invalid. factory NostrKeyPair.fromNsec(String nsec) { try { final decoded = _nip19.decode(nsec); if (decoded['type'] != 'nsec') { throw FormatException('Invalid nsec format: expected "nsec" type'); } final privateKey = decoded['data'] as String; final publicKey = _keyApi.getPublicKey(privateKey); return NostrKeyPair( privateKey: privateKey, publicKey: publicKey, ); } catch (e) { throw FormatException('Failed to parse nsec: $e'); } } /// Creates a [NostrKeyPair] from npub (public key in bech32 format). /// /// Note: This creates a keypair with only the public key. Private key operations won't work. /// /// [npub] - Public key in npub format (e.g., 'npub1...'). /// /// Returns a [NostrKeyPair] with the decoded public key and empty private key. /// /// Throws [FormatException] if npub is invalid. factory NostrKeyPair.fromNpub(String npub) { try { final decoded = _nip19.decode(npub); if (decoded['type'] != 'npub') { throw FormatException('Invalid npub format: expected "npub" type'); } final publicKey = decoded['data'] as String; // No private key available when importing from npub return NostrKeyPair( privateKey: '', // Empty private key - can't sign events publicKey: publicKey, ); } catch (e) { throw FormatException('Failed to parse npub: $e'); } } /// Creates a [NostrKeyPair] from a hex private key. /// /// [hexPrivateKey] - Private key in hex format (64 hex characters). /// /// Returns a [NostrKeyPair] with the provided private key and derived public key. factory NostrKeyPair.fromHexPrivateKey(String hexPrivateKey) { if (hexPrivateKey.length != 64) { throw FormatException('Invalid hex private key: expected 64 hex characters'); } final publicKey = _keyApi.getPublicKey(hexPrivateKey); return NostrKeyPair( privateKey: hexPrivateKey, publicKey: publicKey, ); } /// Creates a [NostrKeyPair] from a JSON map. factory NostrKeyPair.fromJson(Map json) { return NostrKeyPair( privateKey: json['privateKey'] as String, publicKey: json['publicKey'] as String, ); } /// Converts the [NostrKeyPair] to a JSON map. Map toJson() { return { 'privateKey': privateKey, 'publicKey': publicKey, }; } /// Encodes the private key to nsec format. String toNsec() { if (privateKey.isEmpty) { throw StateError('Cannot encode empty private key to nsec'); } return _nip19.nsecEncode(privateKey); } /// Encodes the public key to npub format. String toNpub() { return _nip19.npubEncode(publicKey); } @override String toString() { return 'NostrKeyPair(publicKey: $publicKey)'; } }