UNPKG

nostr-nsec-seedphrase

Version:

A comprehensive TypeScript library for Nostr key management with BIP39 seed phrases, supporting both ESM and CommonJS. Implements NIP-01, NIP-06, NIP-19, and NIP-26 with key generation, event signing, bech32 encoding/decoding, and secure cryptographic ope

161 lines (160 loc) 6.16 kB
import { secp256k1, schnorr } from "@noble/curves/secp256k1.js"; import { bytesToHex, hexToBytes } from "@noble/hashes/utils.js"; import { sha256 } from "@noble/hashes/sha2.js"; import { pino } from "pino"; import { generateSeedPhrase, validateSeedPhrase, getEntropyFromSeedPhrase, } from "../bips/bip39.js"; import { hexToNpub, hexToNsec } from "../nips/nip-19.js"; const logger = pino({ level: process.env.LOG_LEVEL || "info", transport: { target: "pino-pretty", options: { colorize: true, }, }, }); /** * Derives a public key from a private key * @param {string} privateKey - The hex-encoded private key * @returns {string} The hex-encoded public key * @example * const pubkey = getPublicKey("1234567890abcdef..."); * console.log(pubkey); // hex public key */ export function getPublicKey(privateKey) { try { // 32-byte x-only public key (BIP-340 / Nostr identity) const pubkey = bytesToHex(schnorr.getPublicKey(hexToBytes(privateKey))); return pubkey; } catch (error) { logger.error({ error }, "Failed to derive public key"); throw new Error("Failed to derive public key"); } } /** * Builds a multi-format PublicKeyDetails object from a hex private key. * `hex`, `schnorr`, and `npub` are the 32-byte x-only Nostr identity; * `compressed` is the 33-byte SEC1 encoding for non-Nostr interop only. */ function buildPublicKeyDetails(privateKeyHex) { const privateKeyBytes = hexToBytes(privateKeyHex); const xonlyBytes = schnorr.getPublicKey(privateKeyBytes); const compressedBytes = secp256k1.getPublicKey(privateKeyBytes, true); privateKeyBytes.fill(0); // zero sensitive material const xonlyHex = bytesToHex(xonlyBytes); return { hex: xonlyHex, compressed: compressedBytes, schnorr: xonlyBytes, npub: hexToNpub(xonlyHex), }; } /** * Creates a key pair from a hex private key * @param {string} privateKeyHex - The hex-encoded private key * @returns {KeyPair} A key pair containing private and public keys in various formats * @throws {Error} If the private key is invalid * @example * const keyPair = fromHex("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); * console.log(keyPair.publicKey); // corresponding public key * console.log(keyPair.nsec); // bech32 nsec private key * console.log(keyPair.npub); // bech32 npub public key */ export function fromHex(privateKeyHex) { try { if (!privateKeyHex || privateKeyHex.length !== 64) { throw new Error("Invalid private key format"); } const publicKey = buildPublicKeyDetails(privateKeyHex); const nsec = hexToNsec(privateKeyHex); return { privateKey: privateKeyHex, publicKey, nsec, seedPhrase: "", // No seed phrase for hex-derived keys }; } catch (error) { logger.error({ error }, "Failed to create key pair from hex"); throw new Error("Failed to create key pair from hex"); } } /** * Generates a new key pair with a random seed phrase * @returns {KeyPair} A new key pair containing private and public keys in various formats * @example * const keyPair = generateKeyPairWithSeed(); * console.log(keyPair.seedPhrase); // random seed phrase * console.log(keyPair.privateKey); // hex private key * console.log(keyPair.publicKey); // hex public key */ export function generateKeyPairWithSeed() { try { const seedPhrase = generateSeedPhrase(); if (!validateSeedPhrase(seedPhrase)) { throw new Error("Generated invalid seed phrase"); } return seedPhraseToKeyPair(seedPhrase); } catch (error) { logger.error({ error }, "Failed to generate key pair with seed"); throw new Error("Failed to generate key pair with seed"); } } /** * Converts a BIP39 seed phrase to a Nostr key pair * @param {string} seedPhrase - The BIP39 seed phrase to convert * @returns {KeyPair} A key pair containing private and public keys in various formats * @throws {Error} If the seed phrase is invalid or key generation fails * @example * const keyPair = seedPhraseToKeyPair("witch collapse practice feed shame open despair creek road again ice least"); * console.log(keyPair.privateKey); // hex private key * console.log(keyPair.publicKey); // hex public key * console.log(keyPair.nsec); // bech32 nsec private key * console.log(keyPair.npub); // bech32 npub public key */ export function seedPhraseToKeyPair(seedPhrase) { try { if (!validateSeedPhrase(seedPhrase)) { throw new Error("Invalid seed phrase"); } const privateKey = seedPhraseToPrivateKey(seedPhrase); const publicKey = buildPublicKeyDetails(privateKey); const nsec = hexToNsec(privateKey); return { privateKey, publicKey, nsec, seedPhrase, }; } catch (error) { logger.error({ error }, "Failed to convert seed phrase to key pair"); throw new Error("Failed to convert seed phrase to key pair"); } } /** * Converts a BIP39 seed phrase to a private key * @param {string} seedPhrase - The BIP39 seed phrase to convert * @returns {string} The hex-encoded private key * @throws {Error} If the seed phrase is invalid * @example * const privateKey = seedPhraseToPrivateKey("witch collapse practice feed shame open despair creek road again ice least"); * console.log(privateKey); // hex private key */ export function seedPhraseToPrivateKey(seedPhrase) { try { if (!validateSeedPhrase(seedPhrase)) { throw new Error("Invalid seed phrase"); } const entropy = getEntropyFromSeedPhrase(seedPhrase); const privateKey = bytesToHex(sha256(entropy)); entropy.fill(0); // zero sensitive material return privateKey; } catch (error) { logger.error({ error }, "Failed to convert seed phrase to private key"); throw new Error("Failed to convert seed phrase to private key"); } }