A comprehensive cryptographic utility library for Nostr protocol implementation, focusing on core security operations and cross-platform compatibility.
This library provides essential cryptographic operations and utilities required for Nostr protocol implementation, with a focus on:
- Schnorr signatures for the Nostr protocol
- Key management and validation
- Event signing and verification
- Encrypted direct messages (NIP-04)
- Bech32-encoded entities (NIP-19)
- Delegated event signing (NIP-26)
- Authentication protocol (NIP-42)
- Key generation and validation using Schnorr signatures
- Event signing and verification (NIP-01 compliant)
- NIP-04 encryption/decryption
- Shared secret computation
- Delegation token handling (NIP-26)
- Authentication protocol (NIP-42)
- Hex encoding/decoding
- Base64 encoding/decoding
- UTF-8 encoding/decoding
- Binary data handling
- Bech32 encoding/decoding (NIP-19)
- NIP-01: Basic protocol flow
- NIP-04: Encrypted Direct Messages
- NIP-19: Bech32-Encoded Entities
- NIP-26: Delegated Event Signing
- NIP-42: Authentication Protocol
βββ src/
β βββ crypto.ts # Core cryptographic operations
β βββ encoding/ # Encoding utilities
β β βββ base64.ts
β β βββ hex.ts
β β βββ index.ts
β βββ types/ # Type definitions
β β βββ base.ts
β β βββ messages.ts
β β βββ protocol.ts
β β βββ guards.ts
β βββ nips/ # NIP implementations
β βββ utils/ # Utility functions
- Node.js 18 or higher
- TypeScript with "bundler" moduleResolution
- Supports both ESM and CJS formats
- Cross-platform compatible (Node.js and browser environments)
npm version TypeScript MIT License Documentation Build Status Coverage Status Known Vulnerabilities
If you discover a security vulnerability, please follow our Security Policy and report it through GitHub's Security Advisory feature.
- Lightweight: Only 208.7KB unpacked, focusing on essential cryptographic operations
- Type-First: Built from the ground up with TypeScript for maximum type safety
- Minimal Dependencies: Reduced attack surface and easier auditing
- Modular Design: Perfect for projects that need cryptographic operations without full client functionality
- Complementary: Works seamlessly with nostr-nsec-seedphrase for complete key management
- Security Focused: Strict validation and comprehensive test coverage
- Complete NIP Compliance: Implements all required cryptographic operations according to Nostr Implementation Possibilities (NIPs)
- Type Safety: Full TypeScript support with comprehensive type definitions
- Event Handling: Create, sign, and validate Nostr events
- Message Formatting: Protocol-compliant message formatting for relay communication
- Encryption: Secure encryption and decryption for direct messages (NIP-04)
- Validation: Comprehensive validation for events, filters, and subscriptions
- Cross-Platform: Works in both Node.js and browser environments
- Zero Dependencies: Minimal external dependencies for better security
This library implements the following Nostr Implementation Possibilities (NIPs):
| NIP | Title | Description | Status |
|---|---|---|---|
| NIP-01 | Basic Protocol Flow Description | Core protocol functionality including event creation, signing, and verification | β Complete |
| NIP-04 | Encrypted Direct Messages | Secure, end-to-end encrypted direct messaging between users | β Complete |
| NIP-19 | bech32-encoded Entities | Human-readable encoding for keys, events, and other entities | β Complete |
| NIP-26 | Delegated Event Signing | Create and verify delegated event signing capabilities | β Complete |
| NIP-42 | Authentication | Client-relay authentication protocol | β Complete |
- Event creation and serialization
- Event signing and verification
- Event ID calculation
- Basic protocol validation
- End-to-end encrypted direct messages
- Secure key exchange
- Message encryption/decryption
- Bech32 encoding/decoding for:
- Public keys (
npub) - Private keys (
nsec) - Note IDs (
note) - Profile references (
nprofile) - Event references (
nevent) - Addressable entities (
naddr) - Relay URLs (
nrelay)
- Public keys (
- Comprehensive validation and error handling
- Support for multiple relay URLs
- TLV (Type-Length-Value) encoding for complex entities
- Improved type exports and function accessibility (v0.4.5+)
- Enhanced error messages and validation
- Cross-platform compatibility guaranteed
import { npubEncode, nsecEncode, noteEncode, nprofileEncode, neventEncode, naddrEncode, nrelayEncode, decode } from '@humanjavaenterprises/nostr-crypto-utils'; // Encode a public key const npub = npubEncode('7f3b6c2444c526fc7b3a48b0a1e38fb6a5a4062d4a097c9e96feb3c1df2f36d0'); // npub1xtscya34g58tk0z6v2g0r6w5gpqrxdgkz9xeav // Encode a private key const nsec = nsecEncode('7f3b6c2444c526fc7b3a48b0a1e38fb6a5a4062d4a097c9e96feb3c1df2f36d0'); // nsec1xtscya34g58tk0z6v2g0r6w5gpqrxdgkz9xeav // Encode a note ID const note = noteEncode('7f3b6c2444c526fc7b3a48b0a1e38fb6a5a4062d4a097c9e96feb3c1df2f36d0'); // note1xtscya34g58tk0z6v2g0r6w5gpqrxdgkz9xeav // Encode a profile with multiple relays const nprofile = nprofileEncode( '7f3b6c2444c526fc7b3a48b0a1e38fb6a5a4062d4a097c9e96feb3c1df2f36d0', ['wss://relay1.example.com', 'wss://relay2.example.com'] ); // Encode an event with author and kind const nevent = neventEncode( '7f3b6c2444c526fc7b3a48b0a1e38fb6a5a4062d4a097c9e96feb3c1df2f36d0', ['wss://relay.example.com'], '7f3b6c2444c526fc7b3a48b0a1e38fb6a5a4062d4a097c9e96feb3c1df2f36d0', 1 ); // Encode an addressable entity (NIP-33) const naddr = naddrEncode( '7f3b6c2444c526fc7b3a48b0a1e38fb6a5a4062d4a097c9e96feb3c1df2f36d0', 30023, 'my-article', ['wss://relay.example.com'] ); // Encode a relay URL const nrelay = nrelayEncode('wss://relay.example.com'); // Decode any bech32-encoded entity const decoded = decode(nprofile); console.log(decoded); // { // type: 'nprofile', // data: '7f3b6c2444c526fc7b3a48b0a1e38fb6a5a4062d4a097c9e96feb3c1df2f36d0', // relays: ['wss://relay1.example.com', 'wss://relay2.example.com'] // } // All functions include validation try { const invalidNpub = npubEncode('invalid-hex'); } catch (error) { console.error(error); // Error: Invalid hex string } ### Type System #### Event Types The library provides a comprehensive type system for Nostr events: ```typescript // Create an unsigned event const unsignedEvent: UnsignedNostrEvent = { kind: NostrEventKind.TEXT_NOTE, content: "Hello Nostr!", tags: [], created_at: Math.floor(Date.now() / 1000) }; // Sign the event const signedEvent = await signEvent(unsignedEvent, privateKey);
Support for NIP-42 authentication:
// Create an auth event const authEvent: UnsignedNostrEvent = { kind: NostrEventKind.AUTH, content: "challenge-response", tags: [["challenge", challenge]], created_at: Math.floor(Date.now() / 1000) }; // Sign and send the auth event const signedAuthEvent = await signEvent(authEvent, privateKey);
Support for arbitrary tags:
// Create a filter with custom tags const filter: NostrFilter = { kinds: [NostrEventKind.TEXT_NOTE], "#t": ["nostr", "crypto"], // Filter by custom tag limit: 10 };
npm install nostr-crypto-utils
import { npubEncode, nsecEncode, noteEncode } from 'nostr-crypto-utils'; // Convert a hex public key to npub format const hexPubkey = '12xyl6w6aacmqa3gmmzwrr9m3u0ldx3dwqhczuascswvew9am9q4sfg99cx'; const npub = npubEncode(hexPubkey); console.log(npub); // npub1... // Convert a hex private key to nsec format const hexPrivkey = 'your-hex-private-key'; const nsec = nsecEncode(hexPrivkey); console.log(nsec); // nsec1... // Convert an event ID to note format const eventId = 'your-event-id'; const note = noteEncode(eventId); console.log(note); // note1...
import { generateKeyPair, createEvent, signEvent } from 'nostr-crypto-utils'; // Generate a new key pair const keyPair = generateKeyPair(); console.log(keyPair.publicKey, keyPair.privateKey); // Create and sign an event const event = createEvent({ pubkey: keyPair.publicKey, kind: 1, content: 'Hello Nostr!' }); const signedEvent = signEvent(event, keyPair.privateKey);
The library provides robust support for Schnorr signatures, which are fundamental to Nostr's cryptographic operations. Here are some common use cases:
import { schnorrSign, schnorrVerify } from 'nostr-crypto-utils'; // Sign a message const message = 'Hello Nostr!'; const privateKey = 'your-private-key'; const signature = await schnorrSign(message, privateKey); // Verify the signature const publicKey = 'corresponding-public-key'; const isValid = await schnorrVerify(signature, message, publicKey); console.log('Signature valid:', isValid); // true
import { schnorrSign } from 'nostr-crypto-utils'; // Create a delegation token const delegateePubkey = 'delegatee-public-key'; const delegatorPrivkey = 'delegator-private-key'; const conditions = { kind: 1, // Only allow text notes until: Math.floor(Date.now() / 1000) + 86400 // 24 hours from now }; // Format conditions string const conditionsString = Object.entries(conditions) .map(([key, value]) => `${key}=${value}`) .sort() .join('&'); // Create and sign the delegation message const message = `nostr:delegation:${delegateePubkey}:${conditionsString}`; const token = await schnorrSign(message, delegatorPrivkey);
These examples demonstrate the library's type-safe approach to Schnorr signatures, ensuring both security and ease of use.
The library provides robust support for encrypted direct messages following the NIP-04 specification:
import { encryptMessage, decryptMessage } from 'nostr-crypto-utils'; // Encrypting a direct message async function createEncryptedDM( content: string, recipientPubkey: string, senderPrivkey: string ) { try { const encryptedContent = await encryptMessage( content, recipientPubkey, senderPrivkey ); return { kind: 4, // NIP-04 Encrypted Direct Message content: encryptedContent, tags: [['p', recipientPubkey]] }; } catch (error) { throw new Error(`Failed to create encrypted DM: ${error}`); } } // Decrypting a received message async function decryptDM( encryptedContent: string, senderPubkey: string, recipientPrivkey: string ) { try { const decryptedContent = await decryptMessage( encryptedContent, senderPubkey, recipientPrivkey ); return decryptedContent; } catch (error) { throw new Error(`Failed to decrypt DM: ${error}`); } }
The library provides utilities for validating Nostr events and verifying signatures:
import { validateEvent, verifySignature } from 'nostr-crypto-utils'; import type { NostrEvent, SignedNostrEvent } from './types'; async function validateSignedMessage(event: NostrEvent): Promise<boolean> { try { // First validate the event structure if (!validateEvent(event)) { console.debug('Invalid event format'); return false; } // Then verify the signature const isValid = await verifySignature(event as SignedNostrEvent); if (!isValid) { console.debug('Invalid signature'); return false; } return true; } catch (error) { console.error('Error validating signed message:', error); return false; } }
These examples demonstrate real-world usage patterns from production Nostr applications, showcasing both the security features and ease of use of the library.
- Create delegation tokens
- Sign events with delegation
- Validate delegated events
- Conditional delegations with:
- Time constraints
- Event kind restrictions
- Tag restrictions
| Feature | Status | Description |
|---|---|---|
| Key Management | β | Generate, validate, and manage Nostr keypairs |
| Event Creation | β | Create and validate Nostr events |
| Event Signing | β | Sign events with schnorr signatures |
| Event Verification | β | Verify event signatures and validate event structure |
| Direct Messages (NIP-04) | β | Encrypt and decrypt direct messages |
| Message Formatting | β | Format messages for relay communication |
| Type Safety | β | Full TypeScript support with strict typing |
| Browser Support | β | Works in modern browsers with Web Crypto API |
| Node.js Support | β | Full support for Node.js environments |
This library is designed to work seamlessly with @humanjavaenterprises/nostr-nsec-seedphrase to provide a complete solution for Nostr key management and cryptographic operations:
import { generateSeedPhrase } from '@humanjavaenterprises/nostr-nsec-seedphrase'; import { createTextNoteEvent, signEvent } from '@humanjavaenterprises/nostr-crypto-utils'; // Generate keys using nostr-nsec-seedphrase const seedPhrase = generateSeedPhrase(); const keyPair = seedPhraseToKeyPair(seedPhrase); // Use nostr-crypto-utils for event creation and signing const event = createTextNoteEvent({ content: 'Hello Nostr!', pubkey: keyPair.publicKey, created_at: Math.floor(Date.now() / 1000) }); const signedEvent = signEvent(event, keyPair.privateKey);
Together, these libraries provide:
- Secure key generation and recovery through seed phrases
- Type-safe cryptographic operations
- Comprehensive event handling
- Minimal bundle size and dependencies
You can use this library to create delegate tokens for use on web servers or other applications. This implements NIP-26 for delegation of signing authority.
import { createDelegation, validateDelegation } from '@humanjavaenterprises/nostr-crypto-utils'; // Create a delegation token (delegator's perspective) const delegatorKeyPair = await generateKeyPair(); const delegateePubkey = 'npub1...'; // The public key you're delegating to const delegation = await createDelegation({ delegatorPrivkey: delegatorKeyPair.privateKey, delegateePubkey, conditions: { kind: 1, // Only allow text notes until: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60 // 30 days } }); // Validate a delegation token (delegatee's perspective) const isValid = await validateDelegation({ token: delegation.token, delegatorPubkey: delegatorKeyPair.publicKey, delegateePubkey, kind: 1 });
Here's how to use delegation tokens in a web server context:
import { createEvent, signEventWithDelegation } from '@humanjavaenterprises/nostr-crypto-utils'; // On your server, store these securely const DELEGATE_PRIVKEY = 'nsec1...'; // Your server's private key const DELEGATION_TOKEN = 'nostr:delegation:...'; // Token from the delegator const DELEGATOR_PUBKEY = 'npub1...'; // Delegator's public key // Create and sign an event on behalf of the delegator const event = await createEvent({ kind: 1, content: 'Posted via delegation!', pubkey: DELEGATOR_PUBKEY, // Original delegator's pubkey created_at: Math.floor(Date.now() / 1000) }); const signedEvent = await signEventWithDelegation({ event, delegatePrivkey: DELEGATE_PRIVKEY, delegation: { token: DELEGATION_TOKEN, conditions: { kind: 1, until: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60 } } });
You can create more specific delegations with conditions:
// Create a delegation with multiple conditions const delegation = await createDelegation({ delegatorPrivkey: delegatorKeyPair.privateKey, delegateePubkey, conditions: { kinds: [1, 6], // Allow only text notes and reposts until: Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60, // 1 week since: Math.floor(Date.now() / 1000), // Starting from now tags: [ ['t', 'nostr'], // Only allow posts with tag 't' = 'nostr' ['p', delegateePubkey] // Only allow mentions of the delegatee ] } });
When working with delegated tokens:
- Store Securely: Always store delegation tokens and private keys securely
- Check Expiration: Validate the delegation's time constraints before using
- Validate Conditions: Check all conditions before signing or accepting delegated events
- Limit Scope: Only delegate the minimum required permissions
- Monitor Usage: Keep track of how delegated tokens are being used
For more details on delegation, see the NIP-26 specification.
- Consolidated Type Definitions: Improved consistency and safety through unified type definitions
- Better Type Inference: Enhanced type inference for easier development and better code completion
- Stricter Type Checks: Improved type safety with stricter checks for better error prevention
- Better JSDoc Comments: Improved documentation with clear and concise JSDoc comments
- NIP References: Added references to relevant NIPs for better understanding of the underlying protocol
Enable debug mode to get detailed logging:
import { setDebugLevel } from '@humanjavaenterprises/nostr-crypto-utils'; // Enable debug logging setDebugLevel('debug'); // Or for even more detail setDebugLevel('trace');
| Error Message | Likely Cause | Solution |
|---|---|---|
| "Invalid delegation token" | Token expired or malformed | Check token expiration and format |
| "Signature verification failed" | Wrong key format or corrupted signature | Verify key formats and conversion |
| "Condition check failed" | Event doesn't match delegation conditions | Check event kind and other conditions |
| "Invalid pubkey format" | Using bech32 instead of hex | Convert pubkey to correct format |
| "Token expired" | Delegation token past expiration | Create new delegation with future expiration |
When troubleshooting, verify these common points:
- Private keys should be in hex format
- Public keys should be in hex format (not bech32)
- Signatures should be 64 bytes in hex format
- Token expiration should be in the future
- Check system clock synchronization
- Use UTC timestamps consistently
- Verify event kinds match delegation conditions
- Check any tag restrictions
- Confirm time window restrictions
- Verify relay connections
- Check for rate limiting
- Confirm proper websocket handling
// Test delegation validity const testDelegation = async (delegation) => { const result = await validateDelegation({ token: delegation.token, delegatorPubkey: delegation.delegator, delegateePubkey: delegation.delegatee, kind: 1 }); console.log('Delegation valid:', result.isValid); if (!result.isValid) { console.error('Validation error:', result.error); } return result; }; // Test event signing const testEventSigning = async (event, delegation) => { try { const signed = await signEventWithDelegation({ event, delegatePrivkey: delegation.privateKey, delegation: delegation.token }); console.log('Event signed successfully:', signed.id); return true; } catch (error) { console.error('Signing failed:', error); return false; } };
For more help, join our Discord community or open an issue.
npm install @humanjavaenterprises/nostr-crypto-utils
import { createKeyPair, createTextNoteEvent, signEvent } from '@humanjavaenterprises/nostr-crypto-utils'; // Generate a new key pair const keyPair = createKeyPair(); // Create a text note event const event = createTextNoteEvent({ content: 'Hello Nostr!', pubkey: keyPair.publicKey, created_at: Math.floor(Date.now() / 1000) }); // Sign the event const signedEvent = signEvent(event, keyPair.privateKey);
Comprehensive documentation is available at https://humanjavaenterprises.github.io/nostr-crypto-utils/
This library is written in TypeScript and provides comprehensive type definitions for all functions and data structures. Type checking is enforced at compile time to catch potential errors early.
import { NostrEvent, NostrFilter, ValidationResult } from '@humanjavaenterprises/nostr-crypto-utils'; // All types are properly defined const filter: NostrFilter = { kinds: [NostrEventKind.TEXT_NOTE], "#t": ["nostr", "crypto"], // Filter by custom tag limit: 10 }; // Validation results include type information const result: ValidationResult = validateEvent(event);
We welcome contributions! Please see our Contributing Guidelines for details.
This project is licensed under the MIT License - see the LICENSE file for details.
- π Improved type safety with stricter TypeScript checks
- π Fixed crypto implementation for cross-platform compatibility
- β¨ Added comprehensive validation for all message types
- π Updated documentation with more examples
- π Initial public release
- β¨ Added support for NIP-01 and NIP-04
- π Implemented key pair generation and management
- π Added comprehensive documentation
- π Documentation
- π Issue Tracker
- π¬ Discussions
- @humanjavaenterprises/nostr-nsec-seedphrase - Generate and manage Nostr private keys using BIP-39 seed phrases