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
JavaScript
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");
}
}