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
140 lines (139 loc) • 4.38 kB
JavaScript
import { pino } from 'pino';
import { signEvent, getEventHash } from './crypto.js';
import { getPublicKey } from './keys.js';
const logger = pino({
level: process.env.LOG_LEVEL || "info",
transport: {
target: "pino-pretty",
options: {
colorize: true,
},
},
});
/**
* Creates a new signed Nostr event
* @param {string} content - The event content
* @param {number} kind - The event kind (1 for text note, etc.)
* @param {string} privateKey - The hex-encoded private key to sign with
* @param {string[][]} [tags=[]] - Optional event tags
* @returns {Promise<NostrEvent>} The signed event
* @throws {Error} If event creation or signing fails
* @example
* const event = await createEvent(
* "Hello Nostr!",
* 1,
* privateKey,
* [["t", "nostr"]]
* );
* console.log(event); // complete signed event
*/
export async function createEvent(content, kind, privateKey, tags = []) {
try {
// Create unsigned event
const unsignedEvent = {
pubkey: getPublicKey(privateKey),
created_at: Math.floor(Date.now() / 1000),
kind,
tags,
content,
};
// Get event hash and signature
const id = getEventHash(unsignedEvent);
const sig = await signEvent(unsignedEvent, privateKey);
// Return complete signed event
return {
...unsignedEvent,
id,
sig,
};
}
catch (error) {
logger.error('Failed to create event:', error);
throw new Error('Failed to create event');
}
}
/**
* Validates a Nostr event's structure and data types
* @param {NostrEvent} event - The event to validate
* @returns {boolean} True if the event is valid, false otherwise
*/
export function validateEventStructure(event) {
try {
// Check required fields exist
if (!event.id || !event.pubkey || !event.sig) {
return false;
}
// Validate field types
if (typeof event.id !== 'string' ||
typeof event.pubkey !== 'string' ||
typeof event.created_at !== 'number' ||
typeof event.kind !== 'number' ||
!Array.isArray(event.tags) ||
typeof event.content !== 'string' ||
typeof event.sig !== 'string') {
return false;
}
// Validate field formats
if (!/^[0-9a-f]{64}$/.test(event.id) ||
!/^[0-9a-f]{64}$/.test(event.pubkey) ||
!/^[0-9a-f]{128}$/.test(event.sig)) {
return false;
}
// Validate tags structure
if (!event.tags.every(tag => Array.isArray(tag) && tag.every(item => typeof item === 'string'))) {
return false;
}
return true;
}
catch (error) {
logger.error('Failed to validate event structure:', error);
return false;
}
}
/**
* Serializes an event for hashing or transmission
* @param {UnsignedEvent} event - The event to serialize
* @returns {string} JSON string of the event in the Nostr format
*/
export function serializeEvent(event) {
try {
return JSON.stringify([
0,
event.pubkey,
event.created_at,
event.kind,
event.tags,
event.content,
]);
}
catch (error) {
logger.error('Failed to serialize event:', error);
throw new Error('Failed to serialize event');
}
}
/**
* Gets the creation date of an event as a Date object
* @param {NostrEvent} event - The event to get the date from
* @returns {Date} The creation date
*/
export function getEventDate(event) {
return new Date(event.created_at * 1000);
}
/**
* Extracts tags of a specific type from an event
* @param {NostrEvent} event - The event to extract tags from
* @param {string} tagName - The tag identifier to look for
* @returns {string[][]} Array of matching tags
*/
export function getEventTags(event, tagName) {
return event.tags.filter(tag => tag[0] === tagName);
}
/**
* Checks if an event has a specific tag
* @param {NostrEvent} event - The event to check
* @param {string} tagName - The tag identifier to look for
* @returns {boolean} True if the event has the tag
*/
export function hasEventTag(event, tagName) {
return event.tags.some(tag => tag[0] === tagName);
}