From 611ba6fce006ee2e4e02f2a412adc66002ce6b01 Mon Sep 17 00:00:00 2001 From: vveerrgg Date: Sun, 15 Dec 2024 20:16:49 -0800 Subject: [PATCH] chore: bump version to 0.4.0 - Add comprehensive documentation and examples - Add Code of Conduct - Enhance development setup instructions - Add security considerations section - Update version history with v0.3.0 changes --- CODE_OF_CONDUCT.md | 110 +++++++++++++++++++++++++++++ README.md | 98 +++++++++++++++++++++++--- package.json | 2 +- src/index.ts | 172 ++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 354 insertions(+), 28 deletions(-) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..69f7037 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,110 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[conduct@humanjavaenterprises.org](mailto:conduct@humanjavaenterprises.org). +All complaints will be reviewed and investigated promptly and fairly. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +[homepage]: https://www.contributor-covenant.org diff --git a/README.md b/README.md index 3a53656..8dfecf6 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,18 @@ A comprehensive TypeScript library for managing Nostr keys with seed phrases, in npm install nostr-nsec-seedphrase ``` -## Usage +## Getting Started -### Key Generation and Management +This library provides a comprehensive set of tools for managing Nostr keys with seed phrases. Here's how to get started: + +### Prerequisites + +- Node.js 16.0.0 or later +- npm or yarn package manager + +### Basic Usage + +#### Key Generation and Management ```typescript import { generateKeyPairWithSeed, seedPhraseToKeyPair } from 'nostr-nsec-seedphrase'; @@ -81,14 +90,6 @@ const event = await createEvent( const isValidEvent = await verifyEvent(event); ``` -## Recent Updates - -### v0.2.0 -- 🔧 Fixed HMAC configuration for secp256k1 -- ✅ Added comprehensive test coverage -- 🎯 Improved TypeScript types -- 📚 Enhanced documentation - ## API Reference ### Key Management @@ -109,9 +110,84 @@ const isValidEvent = await verifyEvent(event); - `createEvent(content: string, kind: number, privateKey: string, tags?: string[][])`: Create a Nostr event - `verifyEvent(event: NostrEvent)`: Verify a Nostr event +## Development + +### Setting Up Development Environment + +1. Clone the repository +```bash +git clone https://github.com/vergelevans/nostr-nsec-seedphrase.git +cd nostr-nsec-seedphrase +``` + +2. Install dependencies +```bash +npm install +``` + +3. Build the project +```bash +npm run build +``` + +### Running Tests + +```bash +# Run all tests +npm test + +# Run tests with coverage +npm run test:coverage +``` + +### Code Style + +This project uses Prettier for code formatting. Format your code before committing: + +```bash +npm run format +``` + +## Security Considerations + +- Always keep your seed phrases and private keys secure +- Never share your private keys or seed phrases +- Be cautious when using this library in a browser environment +- Consider using a hardware wallet for additional security +- Validate all inputs and handle errors appropriately + +## Recent Updates + +### v0.4.0 +- 📚 Added comprehensive documentation and examples +- 📝 Added Code of Conduct +- 🔍 Enhanced development setup instructions +- 🛡️ Added security considerations section + +### v0.3.0 +- 🔧 Enhanced module resolution for better compatibility +- ✨ Improved testing infrastructure and mocks +- 📝 Enhanced TypeScript support and configurations +- 🔒 Enhanced cryptographic functionality +- 🎯 Updated ESLint and TypeScript configurations + +### v0.2.0 +- 🔧 Fixed HMAC configuration for secp256k1 +- ✅ Added comprehensive test coverage +- 🎯 Improved TypeScript types +- 📚 Enhanced documentation + ## Contributing -Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change. +We welcome contributions! Please follow these steps: + +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/amazing-feature`) +3. Commit your changes (`git commit -m 'feat: add amazing feature'`) +4. Push to the branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request + +Please read our [Code of Conduct](CODE_OF_CONDUCT.md) before contributing. ## License diff --git a/package.json b/package.json index aaf0456..7342972 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nostr-nsec-seedphrase", - "version": "0.3.1", + "version": "0.4.0", "description": "A TypeScript library for managing Nostr keys with seed phrases, including event signing, verification, and WebSocket utilities", "type": "module", "main": "./dist/index.js", diff --git a/src/index.ts b/src/index.ts index f07b507..d7de7e1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,41 +17,81 @@ const logger = pino({ }, }); +/** + * Represents a Nostr key pair with associated formats + * @interface + */ export interface KeyPair { + /** Private key in hex format */ privateKey: string; + /** Public key in hex format */ publicKey: string; + /** Private key in bech32 nsec format */ nsec: string; + /** Public key in bech32 npub format */ npub: string; + /** BIP39 seed phrase used to generate this key pair */ seedPhrase: string; } +/** + * Represents a signed Nostr event + * @interface + * @see https://github.com/nostr-protocol/nips/blob/master/01.md + */ export interface NostrEvent { + /** Event ID (32-bytes lowercase hex of the sha256 of the serialized event data) */ id: string; + /** Event creator's public key (32-bytes lowercase hex) */ pubkey: string; + /** Unix timestamp in seconds */ created_at: number; + /** Event kind (integer) */ kind: number; + /** Array of arrays of strings (event tags) */ tags: string[][]; + /** Event content (arbitrary string) */ content: string; + /** Event signature (64-bytes hex of the schnorr signature of the sha256 hash of the serialized event data) */ sig: string; } +/** + * Represents an unsigned Nostr event before signing + * @interface + */ export interface UnsignedEvent { + /** Event creator's public key (32-bytes lowercase hex) */ pubkey: string; + /** Unix timestamp in seconds */ created_at: number; + /** Event kind (integer) */ kind: number; + /** Array of arrays of strings (event tags) */ tags: string[][]; + /** Event content (arbitrary string) */ content: string; } /** - * Generate a new seed phrase + * Generates a new BIP39 seed phrase + * @returns {string} A random 12-word BIP39 mnemonic seed phrase + * @example + * const seedPhrase = generateSeedPhrase(); + * console.log(seedPhrase); // "witch collapse practice feed shame open despair creek road again ice least" */ export function generateSeedPhrase(): string { return generateMnemonic(); } /** - * Get entropy from seed phrase + * Converts a BIP39 seed phrase to its entropy value + * @param {string} seedPhrase - The BIP39 seed phrase to convert + * @returns {string} The hex-encoded entropy value + * @throws {Error} If the seed phrase is invalid + * @example + * const entropy = getEntropyFromSeedPhrase("witch collapse practice feed shame open despair creek road again ice least"); + * console.log(entropy); // "0123456789abcdef0123456789abcdef" */ export function getEntropyFromSeedPhrase(seedPhrase: string): string { if (!validateMnemonic(seedPhrase)) { @@ -62,7 +102,12 @@ export function getEntropyFromSeedPhrase(seedPhrase: string): string { } /** - * Validate a seed phrase + * Validates a BIP39 seed phrase + * @param {string} seedPhrase - The seed phrase to validate + * @returns {boolean} True if the seed phrase is valid, false otherwise + * @example + * const isValid = validateSeedPhrase("witch collapse practice feed shame open despair creek road again ice least"); + * console.log(isValid); // true */ export function validateSeedPhrase(seedPhrase: string): boolean { try { @@ -76,7 +121,16 @@ export function validateSeedPhrase(seedPhrase: string): boolean { } /** - * Convert a seed phrase to a key pair + * 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: string): KeyPair { try { @@ -103,7 +157,13 @@ export function seedPhraseToKeyPair(seedPhrase: string): KeyPair { } /** - * Generate a new key pair with a seed phrase + * 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(): KeyPair { try { @@ -117,7 +177,15 @@ export function generateKeyPairWithSeed(): KeyPair { } /** - * Create a key pair from a hex private key + * 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: string): KeyPair { try { @@ -147,7 +215,13 @@ export function fromHex(privateKeyHex: string): KeyPair { } /** - * Convert nsec to hex format + * Converts a bech32 nsec private key to hex format + * @param {string} nsec - The bech32-encoded nsec private key + * @returns {string} The hex-encoded private key + * @throws {Error} If the nsec key is invalid + * @example + * const hex = nsecToHex("nsec1..."); + * console.log(hex); // "1234567890abcdef..." */ export function nsecToHex(nsec: string): string { try { @@ -164,7 +238,13 @@ export function nsecToHex(nsec: string): string { } /** - * Convert npub to hex format + * Converts a bech32 npub public key to hex format + * @param {string} npub - The bech32-encoded npub public key + * @returns {string} The hex-encoded public key + * @throws {Error} If the npub key is invalid + * @example + * const hex = npubToHex("npub1..."); + * console.log(hex); // "1234567890abcdef..." */ export function npubToHex(npub: string): string { try { @@ -181,7 +261,13 @@ export function npubToHex(npub: string): string { } /** - * Convert hex to npub format + * Converts a hex public key to bech32 npub format + * @param {string} publicKeyHex - The hex-encoded public key + * @returns {string} The bech32-encoded npub public key + * @throws {Error} If the public key is invalid + * @example + * const npub = hexToNpub("1234567890abcdef..."); + * console.log(npub); // "npub1..." */ export function hexToNpub(publicKeyHex: string): string { try { @@ -194,7 +280,13 @@ export function hexToNpub(publicKeyHex: string): string { } /** - * Convert hex to nsec format + * Converts a hex private key to bech32 nsec format + * @param {string} privateKeyHex - The hex-encoded private key + * @returns {string} The bech32-encoded nsec private key + * @throws {Error} If the private key is invalid + * @example + * const nsec = hexToNsec("1234567890abcdef..."); + * console.log(nsec); // "nsec1..." */ export function hexToNsec(privateKeyHex: string): string { try { @@ -207,7 +299,14 @@ export function hexToNsec(privateKeyHex: string): string { } /** - * Sign a message with a private key + * Signs a message with a private key + * @param {string} message - The message to sign + * @param {string} privateKey - The hex-encoded private key to sign with + * @returns {Promise} The hex-encoded signature + * @throws {Error} If signing fails + * @example + * const signature = await signMessage("Hello Nostr!", privateKey); + * console.log(signature); // hex-encoded signature */ export async function signMessage( message: string, @@ -226,7 +325,14 @@ export async function signMessage( } /** - * Verify a message signature + * Verifies a message signature + * @param {string} message - The original message + * @param {string} signature - The hex-encoded signature to verify + * @param {string} publicKey - The hex-encoded public key to verify against + * @returns {Promise} True if the signature is valid, false otherwise + * @example + * const isValid = await verifySignature("Hello Nostr!", signature, publicKey); + * console.log(isValid); // true or false */ export async function verifySignature( message: string, @@ -249,7 +355,19 @@ export async function verifySignature( } /** - * Sign a Nostr event + * Signs a Nostr event + * @param {UnsignedEvent} event - The event to sign + * @param {string} privateKey - The hex-encoded private key to sign with + * @returns {Promise} The hex-encoded signature + * @throws {Error} If signing fails + * @example + * const signature = await signEvent({ + * pubkey: "...", + * created_at: Math.floor(Date.now() / 1000), + * kind: 1, + * tags: [], + * content: "Hello Nostr!" + * }, privateKey); */ export async function signEvent( event: UnsignedEvent, @@ -270,7 +388,12 @@ export async function signEvent( } /** - * Verify a Nostr event signature + * Verifies a Nostr event signature + * @param {NostrEvent} event - The event to verify + * @returns {Promise} True if the signature is valid, false otherwise + * @example + * const isValid = await verifyEvent(event); + * console.log(isValid); // true or false */ export async function verifyEvent(event: NostrEvent): Promise { try { @@ -298,7 +421,10 @@ export async function verifyEvent(event: NostrEvent): Promise { } /** - * Configure secp256k1 with HMAC for WebSocket utilities + * Configures secp256k1 with HMAC for WebSocket utilities + * This is required for some WebSocket implementations + * @example + * configureHMAC(); */ export function configureHMAC(): void { const hmacFunction = ( @@ -334,7 +460,21 @@ export function configureHMAC(): void { } /** - * Create a new Nostr event + * 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} 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: string,