f72ae21b49475ce567732e123c43a70be6549d81044f4c1ac3c68a0d2fc73860.json raw
1 {"ast":null,"code":"import { bech32 } from '@scure/base';\nimport * as utils from '@noble/curves/abstract/utils';\nimport { getPublicKey, generateSecretKey } from 'nostr-tools';\n/**\n * Value object encapsulating a Nostr keypair.\n * Provides type-safe access to public key operations while protecting the private key.\n *\n * The private key is never exposed directly - all operations that need it\n * are performed through methods on this class.\n */\nexport class NostrKeyPair {\n _privateKeyHex;\n _publicKeyHex;\n constructor(privateKeyHex, publicKeyHex) {\n this._privateKeyHex = privateKeyHex;\n this._publicKeyHex = publicKeyHex;\n }\n /**\n * Generate a new random keypair.\n */\n static generate() {\n const privateKeyBytes = generateSecretKey();\n const privateKeyHex = utils.bytesToHex(privateKeyBytes);\n const publicKeyHex = getPublicKey(privateKeyBytes);\n return new NostrKeyPair(privateKeyHex, publicKeyHex);\n }\n /**\n * Create a keypair from an existing private key.\n * Accepts either hex or nsec format.\n *\n * @throws InvalidNostrKeyError if the key is invalid\n */\n static fromPrivateKey(privateKey) {\n try {\n const hex = NostrKeyPair.normalizeToHex(privateKey);\n NostrKeyPair.validateHexKey(hex);\n const publicKeyHex = NostrKeyPair.derivePublicKey(hex);\n return new NostrKeyPair(hex, publicKeyHex);\n } catch (error) {\n throw new InvalidNostrKeyError(`Invalid private key: ${error instanceof Error ? error.message : 'unknown error'}`);\n }\n }\n /**\n * Reconstitute a keypair from storage.\n * Assumes the stored hex is valid (from trusted source).\n */\n static fromStorage(privateKeyHex) {\n const publicKeyHex = NostrKeyPair.derivePublicKey(privateKeyHex);\n return new NostrKeyPair(privateKeyHex, publicKeyHex);\n }\n /**\n * Get the public key in hex format.\n */\n get publicKeyHex() {\n return this._publicKeyHex;\n }\n /**\n * Get the public key in npub (bech32) format.\n */\n get npub() {\n const data = utils.hexToBytes(this._publicKeyHex);\n const words = bech32.toWords(data);\n return bech32.encode('npub', words, 5000);\n }\n /**\n * Get the private key in nsec (bech32) format.\n * Use with caution - only for display/export purposes.\n */\n get nsec() {\n const data = utils.hexToBytes(this._privateKeyHex);\n const words = bech32.toWords(data);\n return bech32.encode('nsec', words, 5000);\n }\n /**\n * Get the private key bytes for cryptographic operations.\n * Internal use only - required for signing and encryption.\n */\n getPrivateKeyBytes() {\n return utils.hexToBytes(this._privateKeyHex);\n }\n /**\n * Get the private key hex for storage.\n * This should only be used when persisting to encrypted storage.\n */\n toStorageHex() {\n return this._privateKeyHex;\n }\n /**\n * Check if this keypair has the same public key as another.\n */\n hasSamePublicKey(other) {\n return this._publicKeyHex === other._publicKeyHex;\n }\n /**\n * Check if this keypair matches a given public key.\n */\n matchesPublicKey(publicKeyHex) {\n return this._publicKeyHex === publicKeyHex;\n }\n /**\n * Value equality based on public key.\n * Two keypairs are equal if they represent the same identity.\n */\n equals(other) {\n return this._publicKeyHex === other._publicKeyHex;\n }\n // ─────────────────────────────────────────────────────────────────────────\n // Private helpers\n // ─────────────────────────────────────────────────────────────────────────\n static normalizeToHex(privateKey) {\n if (privateKey.startsWith('nsec')) {\n return NostrKeyPair.nsecToHex(privateKey);\n }\n return privateKey;\n }\n static nsecToHex(nsec) {\n const {\n prefix,\n words\n } = bech32.decode(nsec, 5000);\n if (prefix !== 'nsec') {\n throw new Error('Invalid nsec prefix');\n }\n const data = new Uint8Array(bech32.fromWords(words));\n return utils.bytesToHex(data);\n }\n static validateHexKey(hex) {\n if (!/^[0-9a-fA-F]{64}$/.test(hex)) {\n throw new Error('Private key must be 64 hex characters');\n }\n }\n static derivePublicKey(privateKeyHex) {\n const privateKeyBytes = utils.hexToBytes(privateKeyHex);\n return getPublicKey(privateKeyBytes);\n }\n}\n/**\n * Error thrown when a Nostr key is invalid.\n */\nexport class InvalidNostrKeyError extends Error {\n constructor(message) {\n super(message);\n this.name = 'InvalidNostrKeyError';\n }\n}\n/**\n * Utility functions for public key operations (no private key needed).\n */\nexport class NostrPublicKey {\n _hex;\n constructor(_hex) {\n this._hex = _hex;\n }\n /**\n * Create from hex or npub format.\n */\n static from(publicKey) {\n if (publicKey.startsWith('npub')) {\n const hex = NostrPublicKey.npubToHex(publicKey);\n return new NostrPublicKey(hex);\n }\n NostrPublicKey.validateHex(publicKey);\n return new NostrPublicKey(publicKey);\n }\n get hex() {\n return this._hex;\n }\n get npub() {\n const data = utils.hexToBytes(this._hex);\n const words = bech32.toWords(data);\n return bech32.encode('npub', words, 5000);\n }\n /**\n * Get a shortened display version of the public key.\n */\n shortened(prefixLength = 8, suffixLength = 4) {\n const npub = this.npub;\n return `${npub.slice(0, prefixLength)}...${npub.slice(-suffixLength)}`;\n }\n equals(other) {\n return this._hex === other._hex;\n }\n toString() {\n return this._hex;\n }\n static npubToHex(npub) {\n const {\n prefix,\n words\n } = bech32.decode(npub, 5000);\n if (prefix !== 'npub') {\n throw new InvalidNostrKeyError('Invalid npub prefix');\n }\n const data = new Uint8Array(bech32.fromWords(words));\n return utils.bytesToHex(data);\n }\n static validateHex(hex) {\n if (!/^[0-9a-fA-F]{64}$/.test(hex)) {\n throw new InvalidNostrKeyError('Public key must be 64 hex characters');\n }\n }\n}","map":null,"metadata":{},"sourceType":"module","externalDependencies":[]}