diff --git a/.claude/skills/nostr/README.md b/.claude/skills/nostr/README.md new file mode 100644 index 0000000..6806b77 --- /dev/null +++ b/.claude/skills/nostr/README.md @@ -0,0 +1,162 @@ +# Nostr Protocol Skill + +A comprehensive Claude skill for working with the Nostr protocol and implementing Nostr clients and relays. + +## Overview + +This skill provides expert-level knowledge of the Nostr protocol, including: +- Complete NIP (Nostr Implementation Possibilities) reference +- Event structure and cryptographic operations +- Client-relay WebSocket communication +- Event kinds and their behaviors +- Best practices and common pitfalls + +## Contents + +### SKILL.md +The main skill file containing: +- Core protocol concepts +- Event structure and signing +- WebSocket communication patterns +- Cryptographic operations +- Common implementation patterns +- Quick reference guides + +### Reference Files + +#### references/nips-overview.md +Comprehensive documentation of all standard NIPs including: +- Core protocol NIPs (NIP-01, NIP-02, etc.) +- Social features (reactions, reposts, channels) +- Identity and discovery (NIP-05, NIP-65) +- Security and privacy (NIP-44, NIP-42) +- Lightning integration (NIP-47, NIP-57) +- Advanced features + +#### references/event-kinds.md +Complete reference for all Nostr event kinds: +- Core events (0-999) +- Regular events (1000-9999) +- Replaceable events (10000-19999) +- Ephemeral events (20000-29999) +- Parameterized replaceable events (30000-39999) +- Event lifecycle behaviors +- Common patterns and examples + +#### references/common-mistakes.md +Detailed guide on implementation pitfalls: +- Event creation and signing errors +- WebSocket communication issues +- Filter query problems +- Threading mistakes +- Relay management errors +- Security vulnerabilities +- UX considerations +- Testing strategies + +## When to Use + +Use this skill when: +- Implementing Nostr clients or relays +- Working with Nostr events and messages +- Handling cryptographic signatures and keys +- Implementing any NIP +- Building social features on Nostr +- Debugging Nostr applications +- Discussing Nostr protocol architecture + +## Key Features + +### Complete NIP Coverage +All standard NIPs documented with: +- Purpose and status +- Implementation details +- Code examples +- Usage patterns +- Interoperability notes + +### Cryptographic Operations +Detailed guidance on: +- Event signing with Schnorr signatures +- Event ID calculation +- Signature verification +- Key management (BIP-39, NIP-06) +- Encryption (NIP-04, NIP-44) + +### WebSocket Protocol +Complete reference for: +- Message types (EVENT, REQ, CLOSE, OK, EOSE, etc.) +- Filter queries and optimization +- Subscription management +- Connection handling +- Error handling + +### Event Lifecycle +Understanding of: +- Regular events (immutable) +- Replaceable events (latest only) +- Ephemeral events (real-time only) +- Parameterized replaceable events (by identifier) + +### Best Practices +Comprehensive guidance on: +- Multi-relay architecture +- NIP-65 relay lists +- Event caching +- Optimistic UI +- Security considerations +- Performance optimization + +## Quick Start Examples + +### Publishing a Note +```javascript +const event = { + pubkey: userPublicKey, + created_at: Math.floor(Date.now() / 1000), + kind: 1, + tags: [], + content: "Hello Nostr!" +} +event.id = calculateId(event) +event.sig = signEvent(event, privateKey) +ws.send(JSON.stringify(["EVENT", event])) +``` + +### Subscribing to Events +```javascript +const filter = { + kinds: [1], + authors: [followedPubkey], + limit: 50 +} +ws.send(JSON.stringify(["REQ", "sub-id", filter])) +``` + +### Replying to a Note +```javascript +const reply = { + kind: 1, + tags: [ + ["e", originalEventId, "", "root"], + ["p", originalAuthorPubkey] + ], + content: "Great post!" +} +``` + +## Official Resources + +- **NIPs Repository**: https://github.com/nostr-protocol/nips +- **Nostr Website**: https://nostr.com +- **Nostr Documentation**: https://nostr.how +- **NIP Status**: https://nostr-nips.com + +## Skill Maintenance + +This skill is based on the official Nostr NIPs repository. As new NIPs are proposed and implemented, this skill should be updated to reflect the latest standards and best practices. + +## License + +Based on public Nostr protocol specifications (MIT License). + diff --git a/.claude/skills/nostr/SKILL.md b/.claude/skills/nostr/SKILL.md new file mode 100644 index 0000000..6499097 --- /dev/null +++ b/.claude/skills/nostr/SKILL.md @@ -0,0 +1,449 @@ +--- +name: nostr +description: This skill should be used when working with the Nostr protocol, implementing Nostr clients or relays, handling Nostr events, or discussing Nostr Implementation Possibilities (NIPs). Provides comprehensive knowledge of Nostr's decentralized protocol, event structure, cryptographic operations, and all standard NIPs. +--- + +# Nostr Protocol Expert + +## Purpose + +This skill provides expert-level assistance with the Nostr protocol, a simple, open protocol for global, decentralized, and censorship-resistant social networks. The protocol is built on relays and cryptographic keys, enabling direct peer-to-peer communication without central servers. + +## When to Use + +Activate this skill when: +- Implementing Nostr clients or relays +- Working with Nostr events and messages +- Handling cryptographic signatures and keys (schnorr signatures on secp256k1) +- Implementing any Nostr Implementation Possibility (NIP) +- Building social networking features on Nostr +- Querying or filtering Nostr events +- Discussing Nostr protocol architecture +- Implementing WebSocket communication with relays + +## Core Concepts + +### The Protocol Foundation + +Nostr operates on two main components: +1. **Clients** - Applications users run to read/write data +2. **Relays** - Servers that store and forward messages + +Key principles: +- Everyone runs a client +- Anyone can run a relay +- Users identified by public keys +- Messages signed with private keys +- No central authority or trusted servers + +### Events Structure + +All data in Nostr is represented as events. An event is a JSON object with this structure: + +```json +{ + "id": "<32-bytes lowercase hex-encoded sha256 of the serialized event data>", + "pubkey": "<32-bytes lowercase hex-encoded public key of the event creator>", + "created_at": "", + "kind": "", + "tags": [ + ["", "", "", "..."] + ], + "content": "", + "sig": "<64-bytes lowercase hex of the schnorr signature of the sha256 hash of the serialized event data>" +} +``` + +### Event Kinds + +Standard event kinds (from various NIPs): +- `0` - Metadata (user profile) +- `1` - Text note (short post) +- `2` - Recommend relay +- `3` - Contacts (following list) +- `4` - Encrypted direct messages +- `5` - Event deletion +- `6` - Repost +- `7` - Reaction (like, emoji reaction) +- `40` - Channel creation +- `41` - Channel metadata +- `42` - Channel message +- `43` - Channel hide message +- `44` - Channel mute user +- `1000-9999` - Regular events +- `10000-19999` - Replaceable events +- `20000-29999` - Ephemeral events +- `30000-39999` - Parameterized replaceable events + +### Tags + +Common tag types: +- `["e", "", "", ""]` - Reference to an event +- `["p", "", ""]` - Reference to a user +- `["a", "::", ""]` - Reference to a replaceable event +- `["d", ""]` - Identifier for parameterized replaceable events +- `["r", ""]` - Reference/link to a web resource +- `["t", ""]` - Hashtag +- `["g", ""]` - Geolocation +- `["nonce", "", ""]` - Proof of work +- `["subject", ""]` - Subject/title +- `["client", ""]` - Client application used + +## Key NIPs Reference + +For detailed specifications, refer to **references/nips-overview.md**. + +### Core Protocol NIPs + +#### NIP-01: Basic Protocol Flow +The foundation of Nostr. Defines: +- Event structure and validation +- Event ID calculation (SHA256 of serialized event) +- Signature verification (schnorr signatures) +- Client-relay communication via WebSocket +- Message types: EVENT, REQ, CLOSE, EOSE, OK, NOTICE + +#### NIP-02: Contact List and Petnames +Event kind `3` for following lists: +- Each `p` tag represents a followed user +- Optional relay URL and petname in tag +- Replaceable event (latest overwrites) + +#### NIP-04: Encrypted Direct Messages +Event kind `4` for private messages: +- Content encrypted with shared secret (ECDH) +- `p` tag for recipient pubkey +- Deprecated in favor of NIP-44 + +#### NIP-05: Mapping Nostr Keys to DNS +Internet identifier format: `name@domain.com` +- `.well-known/nostr.json` endpoint +- Maps names to pubkeys +- Optional relay list + +#### NIP-09: Event Deletion +Event kind `5` to request deletion: +- Contains `e` tags for events to delete +- Relays should delete referenced events +- Only works for own events + +#### NIP-10: Text Note References (Threads) +Conventions for `e` and `p` tags in replies: +- Root event reference +- Reply event reference +- Mentions +- Marker types: "root", "reply", "mention" + +#### NIP-11: Relay Information Document +HTTP endpoint for relay metadata: +- GET request to relay URL +- Returns JSON with relay information +- Supported NIPs, software, limitations + +### Social Features NIPs + +#### NIP-25: Reactions +Event kind `7` for reactions: +- Content usually "+" (like) or emoji +- `e` tag for reacted event +- `p` tag for event author + +#### NIP-42: Authentication +Client authentication to relays: +- AUTH message from relay +- Client responds with event kind `22242` +- Proves key ownership + +#### NIP-50: Search +Query filter extension for full-text search: +- `search` field in REQ filters +- Implementation-defined behavior + +### Advanced NIPs + +#### NIP-19: bech32-encoded Entities +Human-readable identifiers: +- `npub`: public key +- `nsec`: private key (sensitive!) +- `note`: note/event ID +- `nprofile`: profile with relay hints +- `nevent`: event with relay hints +- `naddr`: replaceable event coordinate + +#### NIP-44: Encrypted Payloads +Improved encryption for direct messages: +- Versioned encryption scheme +- Better security than NIP-04 +- ChaCha20-Poly1305 AEAD + +#### NIP-65: Relay List Metadata +Event kind `10002` for relay lists: +- Read/write relay preferences +- Optimizes relay discovery +- Replaceable event + +## Client-Relay Communication + +### WebSocket Messages + +#### From Client to Relay + +**EVENT** - Publish an event: +```json +["EVENT", ] +``` + +**REQ** - Request events (subscription): +```json +["REQ", , , , ...] +``` + +**CLOSE** - Stop a subscription: +```json +["CLOSE", ] +``` + +**AUTH** - Respond to auth challenge: +```json +["AUTH", ] +``` + +#### From Relay to Client + +**EVENT** - Send event to client: +```json +["EVENT", , ] +``` + +**OK** - Acceptance/rejection notice: +```json +["OK", , , ] +``` + +**EOSE** - End of stored events: +```json +["EOSE", ] +``` + +**CLOSED** - Subscription closed: +```json +["CLOSED", , ] +``` + +**NOTICE** - Human-readable message: +```json +["NOTICE", ] +``` + +**AUTH** - Authentication challenge: +```json +["AUTH", ] +``` + +### Filter Objects + +Filters select events in REQ messages: + +```json +{ + "ids": ["", ...], + "authors": ["", ...], + "kinds": [, ...], + "#e": ["", ...], + "#p": ["", ...], + "#a": ["", ...], + "#t": ["", ...], + "since": , + "until": , + "limit": +} +``` + +Filtering rules: +- Arrays are ORed together +- Different fields are ANDed +- Tag filters: `#` matches tag values +- Prefix matching allowed for `ids` and `authors` + +## Cryptographic Operations + +### Key Management + +- **Private Key**: 32-byte random value, keep secure +- **Public Key**: Derived via secp256k1 +- **Encoding**: Hex (lowercase) or bech32 + +### Event Signing (schnorr) + +Steps to create a signed event: +1. Set all fields except `id` and `sig` +2. Serialize event data to JSON (specific order) +3. Calculate SHA256 hash → `id` +4. Sign `id` with schnorr signature → `sig` + +Serialization format for ID calculation: +```json +[ + 0, + , + , + , + , + +] +``` + +### Event Verification + +Steps to verify an event: +1. Verify ID matches SHA256 of serialized data +2. Verify signature is valid schnorr signature +3. Check created_at is reasonable (not far future) +4. Validate event structure and required fields + +## Implementation Best Practices + +### For Clients + +1. **Connect to Multiple Relays**: Don't rely on single relay +2. **Cache Events**: Reduce redundant relay queries +3. **Verify Signatures**: Always verify event signatures +4. **Handle Replaceable Events**: Keep only latest version +5. **Respect User Privacy**: Careful with sensitive data +6. **Implement NIP-65**: Use user's preferred relays +7. **Proper Error Handling**: Handle relay disconnections +8. **Pagination**: Use `limit`, `since`, `until` for queries + +### For Relays + +1. **Validate Events**: Check signatures, IDs, structure +2. **Rate Limiting**: Prevent spam and abuse +3. **Storage Management**: Ephemeral events, retention policies +4. **Implement NIP-11**: Provide relay information +5. **WebSocket Optimization**: Handle many connections +6. **Filter Optimization**: Efficient event querying +7. **Consider NIP-42**: Authentication for write access +8. **Performance**: Index by pubkey, kind, tags, timestamp + +### Security Considerations + +1. **Never Expose Private Keys**: Handle nsec carefully +2. **Validate All Input**: Prevent injection attacks +3. **Use NIP-44**: For encrypted messages (not NIP-04) +4. **Check Event Timestamps**: Reject far-future events +5. **Implement Proof of Work**: NIP-13 for spam prevention +6. **Sanitize Content**: XSS prevention in displayed content +7. **Relay Trust**: Don't trust single relay for critical data + +## Common Patterns + +### Publishing a Note + +```javascript +const event = { + pubkey: userPublicKey, + created_at: Math.floor(Date.now() / 1000), + kind: 1, + tags: [], + content: "Hello Nostr!", +} +// Calculate ID and sign +event.id = calculateId(event) +event.sig = signEvent(event, privateKey) +// Publish to relay +ws.send(JSON.stringify(["EVENT", event])) +``` + +### Subscribing to Notes + +```javascript +const filter = { + kinds: [1], + authors: [followedPubkey1, followedPubkey2], + limit: 50 +} +ws.send(JSON.stringify(["REQ", "my-sub", filter])) +``` + +### Replying to a Note + +```javascript +const reply = { + kind: 1, + tags: [ + ["e", originalEventId, relayUrl, "root"], + ["p", originalAuthorPubkey] + ], + content: "Great post!", + // ... other fields +} +``` + +### Reacting to a Note + +```javascript +const reaction = { + kind: 7, + tags: [ + ["e", eventId], + ["p", eventAuthorPubkey] + ], + content: "+", // or emoji + // ... other fields +} +``` + +## Development Resources + +### Essential NIPs for Beginners + +Start with these NIPs in order: +1. **NIP-01** - Basic protocol (MUST read) +2. **NIP-19** - Bech32 identifiers +3. **NIP-02** - Following lists +4. **NIP-10** - Threaded conversations +5. **NIP-25** - Reactions +6. **NIP-65** - Relay lists + +### Testing and Development + +- **Relay Implementations**: nostream, strfry, relay.py +- **Test Relays**: wss://relay.damus.io, wss://nos.lol +- **Libraries**: nostr-tools (JS), rust-nostr (Rust), python-nostr (Python) +- **Development Tools**: NostrDebug, Nostr Army Knife, nostril +- **Reference Clients**: Damus (iOS), Amethyst (Android), Snort (Web) + +### Key Repositories + +- **NIPs Repository**: https://github.com/nostr-protocol/nips +- **Awesome Nostr**: https://github.com/aljazceru/awesome-nostr +- **Nostr Resources**: https://nostr.how + +## Reference Files + +For comprehensive NIP details, see: +- **references/nips-overview.md** - Detailed descriptions of all standard NIPs +- **references/event-kinds.md** - Complete event kinds reference +- **references/common-mistakes.md** - Pitfalls and how to avoid them + +## Quick Checklist + +When implementing Nostr: +- [ ] Events have all required fields (id, pubkey, created_at, kind, tags, content, sig) +- [ ] Event IDs calculated correctly (SHA256 of serialization) +- [ ] Signatures verified (schnorr on secp256k1) +- [ ] WebSocket messages properly formatted +- [ ] Filter queries optimized with appropriate limits +- [ ] Handling replaceable events correctly +- [ ] Connected to multiple relays for redundancy +- [ ] Following relevant NIPs for features implemented +- [ ] Private keys never exposed or transmitted +- [ ] Event timestamps validated + +## Official Resources + +- **NIPs Repository**: https://github.com/nostr-protocol/nips +- **Nostr Website**: https://nostr.com +- **Nostr Documentation**: https://nostr.how +- **NIP Status**: https://nostr-nips.com + diff --git a/.claude/skills/nostr/references/common-mistakes.md b/.claude/skills/nostr/references/common-mistakes.md new file mode 100644 index 0000000..569a144 --- /dev/null +++ b/.claude/skills/nostr/references/common-mistakes.md @@ -0,0 +1,657 @@ +# Common Nostr Implementation Mistakes and How to Avoid Them + +This document highlights frequent errors made when implementing Nostr clients and relays, along with solutions. + +## Event Creation and Signing + +### Mistake 1: Incorrect Event ID Calculation + +**Problem**: Wrong serialization order or missing fields when calculating SHA256. + +**Correct Serialization**: +```json +[ + 0, // Must be integer 0 + , // Lowercase hex string + , // Unix timestamp integer + , // Integer + , // Array of arrays + // String +] +``` + +**Common errors**: +- Using string "0" instead of integer 0 +- Including `id` or `sig` fields in serialization +- Wrong field order +- Not using compact JSON (no spaces) +- Using uppercase hex + +**Fix**: Serialize exactly as shown, compact JSON, SHA256 the UTF-8 bytes. + +### Mistake 2: Wrong Signature Algorithm + +**Problem**: Using ECDSA instead of Schnorr signatures. + +**Correct**: +- Use Schnorr signatures (BIP-340) +- Curve: secp256k1 +- Sign the 32-byte event ID + +**Libraries**: +- JavaScript: noble-secp256k1 +- Rust: secp256k1 +- Go: btcsuite/btcd/btcec/v2/schnorr +- Python: secp256k1-py + +### Mistake 3: Invalid created_at Timestamps + +**Problem**: Events with far-future timestamps or very old timestamps. + +**Best practices**: +- Use current Unix time: `Math.floor(Date.now() / 1000)` +- Relays often reject if `created_at > now + 15 minutes` +- Don't backdate events to manipulate ordering + +**Fix**: Always use current time when creating events. + +### Mistake 4: Malformed Tags + +**Problem**: Tags that aren't arrays or have wrong structure. + +**Correct format**: +```json +{ + "tags": [ + ["e", "event-id", "relay-url", "marker"], + ["p", "pubkey", "relay-url"], + ["t", "hashtag"] + ] +} +``` + +**Common errors**: +- Using objects instead of arrays: `{"e": "..."}` ❌ +- Missing inner arrays: `["e", "event-id"]` when nested in tags is wrong +- Wrong nesting depth +- Non-string values (except for specific NIPs) + +### Mistake 5: Not Handling Replaceable Events + +**Problem**: Showing multiple versions of replaceable events. + +**Event types**: +- **Replaceable (10000-19999)**: Same author + kind → replace +- **Parameterized Replaceable (30000-39999)**: Same author + kind + d-tag → replace + +**Fix**: +```javascript +// For replaceable events +const key = `${event.pubkey}:${event.kind}` +if (latestEvents[key]?.created_at < event.created_at) { + latestEvents[key] = event +} + +// For parameterized replaceable events +const dTag = event.tags.find(t => t[0] === 'd')?.[1] || '' +const key = `${event.pubkey}:${event.kind}:${dTag}` +if (latestEvents[key]?.created_at < event.created_at) { + latestEvents[key] = event +} +``` + +## WebSocket Communication + +### Mistake 6: Not Handling EOSE + +**Problem**: Loading indicators never finish or show wrong state. + +**Solution**: +```javascript +const receivedEvents = new Set() +let eoseReceived = false + +ws.onmessage = (msg) => { + const [type, ...rest] = JSON.parse(msg.data) + + if (type === 'EVENT') { + const [subId, event] = rest + receivedEvents.add(event.id) + displayEvent(event) + } + + if (type === 'EOSE') { + eoseReceived = true + hideLoadingSpinner() + } +} +``` + +### Mistake 7: Not Closing Subscriptions + +**Problem**: Memory leaks and wasted bandwidth from unclosed subscriptions. + +**Fix**: Always send CLOSE when done: +```javascript +ws.send(JSON.stringify(['CLOSE', subId])) +``` + +**Best practices**: +- Close when component unmounts +- Close before opening new subscription with same ID +- Use unique subscription IDs +- Track active subscriptions + +### Mistake 8: Ignoring OK Messages + +**Problem**: Not knowing if events were accepted or rejected. + +**Solution**: +```javascript +ws.onmessage = (msg) => { + const [type, eventId, accepted, message] = JSON.parse(msg.data) + + if (type === 'OK') { + if (!accepted) { + console.error(`Event ${eventId} rejected: ${message}`) + handleRejection(eventId, message) + } + } +} +``` + +**Common rejection reasons**: +- `pow:` - Insufficient proof of work +- `blocked:` - Pubkey or content blocked +- `rate-limited:` - Too many requests +- `invalid:` - Failed validation + +### Mistake 9: Sending Events Before WebSocket Ready + +**Problem**: Events lost because WebSocket not connected. + +**Fix**: +```javascript +const sendWhenReady = (ws, message) => { + if (ws.readyState === WebSocket.OPEN) { + ws.send(message) + } else { + ws.addEventListener('open', () => ws.send(message), { once: true }) + } +} +``` + +### Mistake 10: Not Handling WebSocket Disconnections + +**Problem**: App breaks when relay goes offline. + +**Solution**: Implement reconnection with exponential backoff: +```javascript +let reconnectDelay = 1000 +const maxDelay = 30000 + +const connect = () => { + const ws = new WebSocket(relayUrl) + + ws.onclose = () => { + setTimeout(() => { + reconnectDelay = Math.min(reconnectDelay * 2, maxDelay) + connect() + }, reconnectDelay) + } + + ws.onopen = () => { + reconnectDelay = 1000 // Reset on successful connection + resubscribe() // Re-establish subscriptions + } +} +``` + +## Filter Queries + +### Mistake 11: Overly Broad Filters + +**Problem**: Requesting too many events, overwhelming relay and client. + +**Bad**: +```json +{ + "kinds": [1], + "limit": 10000 +} +``` + +**Good**: +```json +{ + "kinds": [1], + "authors": [""], + "limit": 50, + "since": 1234567890 +} +``` + +**Best practices**: +- Always set reasonable `limit` (50-500) +- Filter by `authors` when possible +- Use `since`/`until` for time ranges +- Be specific with `kinds` +- Multiple smaller queries > one huge query + +### Mistake 12: Not Using Prefix Matching + +**Problem**: Full hex strings in filters unnecessarily. + +**Optimization**: +```json +{ + "ids": ["abc12345"], // 8 chars enough for uniqueness + "authors": ["def67890"] +} +``` + +Relays support prefix matching for `ids` and `authors`. + +### Mistake 13: Duplicate Filter Fields + +**Problem**: Redundant filter conditions. + +**Bad**: +```json +{ + "authors": ["pubkey1", "pubkey1"], + "kinds": [1, 1] +} +``` + +**Good**: +```json +{ + "authors": ["pubkey1"], + "kinds": [1] +} +``` + +Deduplicate filter arrays. + +## Threading and References + +### Mistake 14: Incorrect Thread Structure + +**Problem**: Missing root/reply markers or wrong tag order. + +**Correct reply structure** (NIP-10): +```json +{ + "kind": 1, + "tags": [ + ["e", "", "", "root"], + ["e", "", "", "reply"], + ["p", ""], + ["p", ""] + ] +} +``` + +**Key points**: +- Root event should have "root" marker +- Direct parent should have "reply" marker +- Include `p` tags for all mentioned users +- Relay hints are optional but helpful + +### Mistake 15: Missing p Tags in Replies + +**Problem**: Authors not notified of replies. + +**Fix**: Always add `p` tag for: +- Original author +- Authors mentioned in content +- Authors in the thread chain + +```json +{ + "tags": [ + ["e", "event-id", "", "reply"], + ["p", "original-author"], + ["p", "mentioned-user1"], + ["p", "mentioned-user2"] + ] +} +``` + +### Mistake 16: Not Using Markers + +**Problem**: Ambiguous thread structure. + +**Solution**: Always use markers in `e` tags: +- `root` - Root of thread +- `reply` - Direct parent +- `mention` - Referenced but not replied to + +Without markers, clients must guess thread structure. + +## Relay Management + +### Mistake 17: Relying on Single Relay + +**Problem**: Single point of failure, censorship vulnerability. + +**Solution**: Connect to multiple relays (5-15 common): +```javascript +const relays = [ + 'wss://relay1.com', + 'wss://relay2.com', + 'wss://relay3.com' +] + +const connections = relays.map(url => connect(url)) +``` + +**Best practices**: +- Publish to 3-5 write relays +- Read from 5-10 read relays +- Use NIP-65 for user's preferred relays +- Fall back to NIP-05 relays +- Implement relay rotation on failure + +### Mistake 18: Not Implementing NIP-65 + +**Problem**: Querying wrong relays, missing user's events. + +**Correct flow**: +1. Fetch user's kind `10002` event (relay list) +2. Connect to their read relays to fetch their content +3. Connect to their write relays to send them messages + +```javascript +async function getUserRelays(pubkey) { + // Fetch kind 10002 + const relayList = await fetchEvent({ + kinds: [10002], + authors: [pubkey] + }) + + const readRelays = [] + const writeRelays = [] + + relayList.tags.forEach(([tag, url, mode]) => { + if (tag === 'r') { + if (!mode || mode === 'read') readRelays.push(url) + if (!mode || mode === 'write') writeRelays.push(url) + } + }) + + return { readRelays, writeRelays } +} +``` + +### Mistake 19: Not Respecting Relay Limitations + +**Problem**: Violating relay policies, getting rate limited or banned. + +**Solution**: Fetch and respect NIP-11 relay info: +```javascript +const getRelayInfo = async (relayUrl) => { + const url = relayUrl.replace('wss://', 'https://').replace('ws://', 'http://') + const response = await fetch(url, { + headers: { 'Accept': 'application/nostr+json' } + }) + return response.json() +} + +// Respect limitations +const info = await getRelayInfo(relayUrl) +const maxLimit = info.limitation?.max_limit || 500 +const maxFilters = info.limitation?.max_filters || 10 +``` + +## Security + +### Mistake 20: Exposing Private Keys + +**Problem**: Including nsec in client code, logs, or network requests. + +**Never**: +- Store nsec in localStorage without encryption +- Log private keys +- Send nsec over network +- Display nsec to user unless explicitly requested +- Hard-code private keys + +**Best practices**: +- Use NIP-07 (browser extension) when possible +- Encrypt keys at rest +- Use NIP-46 (remote signing) for web apps +- Warn users when showing nsec + +### Mistake 21: Not Verifying Signatures + +**Problem**: Accepting invalid events, vulnerability to attacks. + +**Always verify**: +```javascript +const verifyEvent = (event) => { + // 1. Verify ID + const calculatedId = sha256(serializeEvent(event)) + if (calculatedId !== event.id) return false + + // 2. Verify signature + const signatureValid = schnorr.verify( + event.sig, + event.id, + event.pubkey + ) + if (!signatureValid) return false + + // 3. Check timestamp + const now = Math.floor(Date.now() / 1000) + if (event.created_at > now + 900) return false // 15 min future + + return true +} +``` + +**Verify before**: +- Displaying to user +- Storing in database +- Using event data for logic + +### Mistake 22: Using NIP-04 Encryption + +**Problem**: Weak encryption, vulnerable to attacks. + +**Solution**: Use NIP-44 instead: +- Modern authenticated encryption +- ChaCha20-Poly1305 AEAD +- Proper key derivation +- Version byte for upgradability + +**Migration**: Update to NIP-44 for all new encrypted messages. + +### Mistake 23: Not Sanitizing Content + +**Problem**: XSS vulnerabilities in displayed content. + +**Solution**: Sanitize before rendering: +```javascript +import DOMPurify from 'dompurify' + +const safeContent = DOMPurify.sanitize(event.content, { + ALLOWED_TAGS: ['b', 'i', 'u', 'a', 'code', 'pre'], + ALLOWED_ATTR: ['href', 'target', 'rel'] +}) +``` + +**Especially critical for**: +- Markdown rendering +- Link parsing +- Image URLs +- User-provided HTML + +## User Experience + +### Mistake 24: Not Caching Events + +**Problem**: Re-fetching same events repeatedly, poor performance. + +**Solution**: Implement event cache: +```javascript +const eventCache = new Map() + +const cacheEvent = (event) => { + eventCache.set(event.id, event) +} + +const getCachedEvent = (eventId) => { + return eventCache.get(eventId) +} +``` + +**Cache strategies**: +- LRU eviction for memory management +- IndexedDB for persistence +- Invalidate replaceable events on update +- Cache metadata (kind 0) aggressively + +### Mistake 25: Not Implementing Optimistic UI + +**Problem**: Slow feeling app, waiting for relay confirmation. + +**Solution**: Show user's events immediately: +```javascript +const publishEvent = async (event) => { + // Immediately show to user + displayEvent(event, { pending: true }) + + // Publish to relays + const results = await Promise.all( + relays.map(relay => relay.publish(event)) + ) + + // Update status based on results + const success = results.some(r => r.accepted) + displayEvent(event, { pending: false, success }) +} +``` + +### Mistake 26: Poor Loading States + +**Problem**: User doesn't know if app is working. + +**Solution**: Clear loading indicators: +- Show spinner until EOSE +- Display "Loading..." placeholder +- Show how many relays responded +- Indicate connection status per relay + +### Mistake 27: Not Handling Large Threads + +**Problem**: Loading entire thread at once, performance issues. + +**Solution**: Implement pagination: +```javascript +const loadThread = async (eventId, cursor = null) => { + const filter = { + "#e": [eventId], + kinds: [1], + limit: 20, + until: cursor + } + + const replies = await fetchEvents(filter) + return { replies, nextCursor: replies[replies.length - 1]?.created_at } +} +``` + +## Testing + +### Mistake 28: Not Testing with Multiple Relays + +**Problem**: App works with one relay but fails with others. + +**Solution**: Test with: +- Fast relays +- Slow relays +- Unreliable relays +- Paid relays (auth required) +- Relays with different NIP support + +### Mistake 29: Not Testing Edge Cases + +**Critical tests**: +- Empty filter results +- WebSocket disconnections +- Malformed events +- Very long content +- Invalid signatures +- Relay errors +- Rate limiting +- Concurrent operations + +### Mistake 30: Not Monitoring Performance + +**Metrics to track**: +- Event verification time +- WebSocket latency per relay +- Events per second processed +- Memory usage (event cache) +- Subscription count +- Failed publishes + +## Best Practices Checklist + +**Event Creation**: +- [ ] Correct serialization for ID +- [ ] Schnorr signatures +- [ ] Current timestamp +- [ ] Valid tag structure +- [ ] Handle replaceable events + +**WebSocket**: +- [ ] Handle EOSE +- [ ] Close subscriptions +- [ ] Process OK messages +- [ ] Check WebSocket state +- [ ] Reconnection logic + +**Filters**: +- [ ] Set reasonable limits +- [ ] Specific queries +- [ ] Deduplicate arrays +- [ ] Use prefix matching + +**Threading**: +- [ ] Use root/reply markers +- [ ] Include all p tags +- [ ] Proper thread structure + +**Relays**: +- [ ] Multiple relays +- [ ] Implement NIP-65 +- [ ] Respect limitations +- [ ] Handle failures + +**Security**: +- [ ] Never expose nsec +- [ ] Verify all signatures +- [ ] Use NIP-44 encryption +- [ ] Sanitize content + +**UX**: +- [ ] Cache events +- [ ] Optimistic UI +- [ ] Loading states +- [ ] Pagination + +**Testing**: +- [ ] Multiple relays +- [ ] Edge cases +- [ ] Monitor performance + +## Resources + +- **nostr-tools**: JavaScript library with best practices +- **rust-nostr**: Rust implementation with strong typing +- **NIPs Repository**: Official specifications +- **Nostr Dev**: Community resources and help + diff --git a/.claude/skills/nostr/references/event-kinds.md b/.claude/skills/nostr/references/event-kinds.md new file mode 100644 index 0000000..8b587da --- /dev/null +++ b/.claude/skills/nostr/references/event-kinds.md @@ -0,0 +1,361 @@ +# Nostr Event Kinds - Complete Reference + +This document provides a comprehensive list of all standard and commonly-used Nostr event kinds. + +## Standard Event Kinds + +### Core Events (0-999) + +#### Metadata and Profile +- **0**: `Metadata` - User profile information (name, about, picture, etc.) + - Replaceable + - Content: JSON with profile fields + +#### Text Content +- **1**: `Text Note` - Short-form post (like a tweet) + - Regular event (not replaceable) + - Most common event type + +#### Relay Recommendations +- **2**: `Recommend Relay` - Deprecated, use NIP-65 instead + +#### Contact Lists +- **3**: `Contacts` - Following list with optional relay hints + - Replaceable + - Tags: `p` tags for each followed user + +#### Encrypted Messages +- **4**: `Encrypted Direct Message` - Private message (NIP-04, deprecated) + - Regular event + - Use NIP-44 instead for better security + +#### Content Management +- **5**: `Event Deletion` - Request to delete events + - Tags: `e` tags for events to delete + - Only works for own events + +#### Sharing +- **6**: `Repost` - Share another event + - Tags: `e` for reposted event, `p` for original author + - May include original event in content + +#### Reactions +- **7**: `Reaction` - Like, emoji reaction to event + - Content: "+" or emoji + - Tags: `e` for reacted event, `p` for author + +### Channel Events (40-49) + +- **40**: `Channel Creation` - Create a public chat channel +- **41**: `Channel Metadata` - Set channel name, about, picture +- **42**: `Channel Message` - Post message in channel +- **43**: `Channel Hide Message` - Hide a message in channel +- **44**: `Channel Mute User` - Mute a user in channel + +### Regular Events (1000-9999) + +Regular events are never deleted or replaced. All versions are kept. + +- **1000**: `Example regular event` +- **1063**: `File Metadata` (NIP-94) - Metadata for shared files + - Tags: url, MIME type, hash, size, dimensions + +### Replaceable Events (10000-19999) + +Only the latest event of each kind is kept per pubkey. + +- **10000**: `Mute List` - List of muted users/content +- **10001**: `Pin List` - Pinned events +- **10002**: `Relay List Metadata` (NIP-65) - User's preferred relays + - Critical for routing + - Tags: `r` with relay URLs and read/write markers + +### Ephemeral Events (20000-29999) + +Not stored by relays, only forwarded once. + +- **20000**: `Example ephemeral event` +- **21000**: `Typing Indicator` - User is typing +- **22242**: `Client Authentication` (NIP-42) - Auth response to relay + +### Parameterized Replaceable Events (30000-39999) + +Replaced based on `d` tag value. + +#### Lists (30000-30009) +- **30000**: `Categorized People List` - Custom people lists + - `d` tag: list identifier + - `p` tags: people in list + +- **30001**: `Categorized Bookmark List` - Bookmark collections + - `d` tag: list identifier + - `e` or `a` tags: bookmarked items + +- **30008**: `Badge Definition` (NIP-58) - Define a badge/achievement + - `d` tag: badge ID + - Tags: name, description, image + +- **30009**: `Profile Badges` (NIP-58) - Badges displayed on profile + - `d` tag: badge ID + - `e` or `a` tags: badge awards + +#### Long-form Content (30023) +- **30023**: `Long-form Article` (NIP-23) - Blog post, article + - `d` tag: article identifier (slug) + - Tags: title, summary, published_at, image + - Content: Markdown + +#### Application Data (30078) +- **30078**: `Application-specific Data` (NIP-78) + - `d` tag: app-name:data-key + - Content: app-specific data (may be encrypted) + +#### Other Parameterized Replaceables +- **31989**: `Application Handler Information` (NIP-89) + - Declares app can handle certain event kinds + +- **31990**: `Handler Recommendation` (NIP-89) + - User's preferred apps for event kinds + +## Special Event Kinds + +### Authentication & Signing +- **22242**: `Client Authentication` - Prove key ownership to relay +- **24133**: `Nostr Connect` - Remote signer protocol (NIP-46) + +### Lightning & Payments +- **9734**: `Zap Request` (NIP-57) - Request Lightning payment + - Not published to regular relays + - Sent to LNURL provider + +- **9735**: `Zap Receipt` (NIP-57) - Proof of Lightning payment + - Published by LNURL provider + - Proves zap was paid + +- **23194**: `Wallet Request` (NIP-47) - Request wallet operation +- **23195**: `Wallet Response` (NIP-47) - Response to wallet request + +### Content & Annotations +- **1984**: `Reporting` (NIP-56) - Report content/users + - Tags: reason (spam, illegal, etc.) + +- **9802**: `Highlights` (NIP-84) - Highlight text + - Content: highlighted text + - Tags: context, source event + +### Badges & Reputation +- **8**: `Badge Award` (NIP-58) - Award a badge to someone + - Tags: `a` for badge definition, `p` for recipient + +### Generic Events +- **16**: `Generic Repost` (NIP-18) - Repost any event kind + - More flexible than kind 6 + +- **27235**: `HTTP Auth` (NIP-98) - Authenticate HTTP requests + - Tags: URL, method + +## Event Kind Ranges Summary + +| Range | Type | Behavior | Examples | +|-------|------|----------|----------| +| 0-999 | Core | Varies | Metadata, notes, reactions | +| 1000-9999 | Regular | Immutable, all kept | File metadata | +| 10000-19999 | Replaceable | Only latest kept | Mute list, relay list | +| 20000-29999 | Ephemeral | Not stored | Typing, presence | +| 30000-39999 | Parameterized Replaceable | Replaced by `d` tag | Articles, lists, badges | + +## Event Lifecycle + +### Regular Events (1000-9999) +``` +Event A published → Stored +Event A' published → Both A and A' stored +``` + +### Replaceable Events (10000-19999) +``` +Event A published → Stored +Event A' published (same kind, same pubkey) → A deleted, A' stored +``` + +### Parameterized Replaceable Events (30000-39999) +``` +Event A (d="foo") published → Stored +Event B (d="bar") published → Both stored (different d) +Event A' (d="foo") published → A deleted, A' stored (same d) +``` + +### Ephemeral Events (20000-29999) +``` +Event A published → Forwarded to subscribers, NOT stored +``` + +## Common Patterns + +### Metadata (Kind 0) +```json +{ + "kind": 0, + "content": "{\"name\":\"Alice\",\"about\":\"Nostr user\",\"picture\":\"https://...\",\"nip05\":\"alice@example.com\"}", + "tags": [] +} +``` + +### Text Note (Kind 1) +```json +{ + "kind": 1, + "content": "Hello Nostr!", + "tags": [ + ["t", "nostr"], + ["t", "hello"] + ] +} +``` + +### Reply (Kind 1 with thread tags) +```json +{ + "kind": 1, + "content": "Great post!", + "tags": [ + ["e", "", "", "root"], + ["e", "", "", "reply"], + ["p", ""] + ] +} +``` + +### Reaction (Kind 7) +```json +{ + "kind": 7, + "content": "+", + "tags": [ + ["e", ""], + ["p", ""], + ["k", "1"] + ] +} +``` + +### Long-form Article (Kind 30023) +```json +{ + "kind": 30023, + "content": "# My Article\n\nContent here...", + "tags": [ + ["d", "my-article-slug"], + ["title", "My Article"], + ["summary", "This is about..."], + ["published_at", "1234567890"], + ["t", "nostr"], + ["image", "https://..."] + ] +} +``` + +### Relay List (Kind 10002) +```json +{ + "kind": 10002, + "content": "", + "tags": [ + ["r", "wss://relay1.com"], + ["r", "wss://relay2.com", "write"], + ["r", "wss://relay3.com", "read"] + ] +} +``` + +### Zap Request (Kind 9734) +```json +{ + "kind": 9734, + "content": "", + "tags": [ + ["relays", "wss://relay1.com", "wss://relay2.com"], + ["amount", "21000"], + ["lnurl", "lnurl..."], + ["p", ""], + ["e", ""] + ] +} +``` + +### File Metadata (Kind 1063) +```json +{ + "kind": 1063, + "content": "My photo from the trip", + "tags": [ + ["url", "https://cdn.example.com/image.jpg"], + ["m", "image/jpeg"], + ["x", "abc123..."], + ["size", "524288"], + ["dim", "1920x1080"], + ["blurhash", "LEHV6n..."] + ] +} +``` + +### Report (Kind 1984) +```json +{ + "kind": 1984, + "content": "This is spam", + "tags": [ + ["e", "", ""], + ["p", ""], + ["report", "spam"] + ] +} +``` + +## Future Event Kinds + +The event kind space is open-ended. New NIPs may define new event kinds. + +**Guidelines for new event kinds**: +1. Use appropriate range for desired behavior +2. Document in a NIP +3. Implement in at least 2 clients and 1 relay +4. Ensure backwards compatibility +5. Don't overlap with existing kinds + +**Custom event kinds**: +- Applications can use undefined event kinds +- Document behavior for interoperability +- Consider proposing as a NIP if useful broadly + +## Event Kind Selection Guide + +**Choose based on lifecycle needs**: + +- **Regular (1000-9999)**: When you need history + - User posts, comments, reactions + - Payment records, receipts + - Immutable records + +- **Replaceable (10000-19999)**: When you need latest state + - User settings, preferences + - Mute/block lists + - Current status + +- **Ephemeral (20000-29999)**: When you need real-time only + - Typing indicators + - Online presence + - Temporary notifications + +- **Parameterized Replaceable (30000-39999)**: When you need multiple latest states + - Articles (one per slug) + - Product listings (one per product ID) + - Configuration sets (one per setting name) + +## References + +- NIPs Repository: https://github.com/nostr-protocol/nips +- NIP-16: Event Treatment +- NIP-01: Event structure +- Various feature NIPs for specific kinds + diff --git a/.claude/skills/nostr/references/nips-overview.md b/.claude/skills/nostr/references/nips-overview.md new file mode 100644 index 0000000..bcf3e96 --- /dev/null +++ b/.claude/skills/nostr/references/nips-overview.md @@ -0,0 +1,1170 @@ +# Nostr Implementation Possibilities (NIPs) - Complete Overview + +This document provides detailed descriptions of all standard NIPs from the nostr-protocol/nips repository. + +## Core Protocol NIPs + +### NIP-01: Basic Protocol Flow Description + +**Status**: Mandatory for all implementations + +The foundational NIP that defines the entire Nostr protocol. + +#### Events + +Events are the only object type in Nostr. Structure: + +```json +{ + "id": "<32-bytes lowercase hex>", + "pubkey": "<32-bytes lowercase hex>", + "created_at": "", + "kind": "", + "tags": [["", "", ...]], + "content": "", + "sig": "<64-bytes hex>" +} +``` + +**Event ID Calculation**: +1. Serialize to JSON array: `[0, pubkey, created_at, kind, tags, content]` +2. UTF-8 encode +3. Calculate SHA256 hash +4. Result is the event ID + +**Signature**: +- Schnorr signature of the event ID +- Uses secp256k1 curve +- 64-byte hex-encoded + +#### Communication Protocol + +All communication happens over WebSocket. + +**Client Messages**: + +1. `["EVENT", ]` - Publish event +2. `["REQ", , , ...]` - Subscribe +3. `["CLOSE", ]` - Unsubscribe + +**Relay Messages**: + +1. `["EVENT", , ]` - Send event +2. `["OK", , , ]` - Command result +3. `["EOSE", ]` - End of stored events +4. `["CLOSED", , ]` - Forced close +5. `["NOTICE", ]` - Human-readable notice + +#### Filters + +Filter object fields (all optional): +- `ids`: List of event IDs (prefix match) +- `authors`: List of pubkeys (prefix match) +- `kinds`: List of event kinds +- `#`: Tag queries +- `since`: Unix timestamp (events after) +- `until`: Unix timestamp (events before) +- `limit`: Maximum events to return + +A filter matches if ALL conditions are met. Within arrays, conditions are ORed. + +#### Basic Event Kinds + +- `0`: Metadata (user profile) +- `1`: Text note +- `2`: Recommend relay (deprecated) + +### NIP-02: Contact List and Petnames + +**Status**: Widely implemented + +Defines event kind `3` for user contact lists (following lists). + +**Format**: +```json +{ + "kind": 3, + "tags": [ + ["p", "", "", ""] + ], + "content": "" +} +``` + +**Characteristics**: +- Replaceable event (latest version is authoritative) +- Each `p` tag is a followed user +- Relay URL (optional): where to find this user +- Petname (optional): user's chosen name for contact +- Content may contain JSON relay list (deprecated, use NIP-65) + +**Usage**: +- Clients fetch kind 3 to build following list +- Always replace old version with new +- Use for social graph discovery + +### NIP-03: OpenTimestamps Attestations + +**Status**: Optional + +Allows embedding OpenTimestamps proofs in events. + +**Format**: +```json +{ + "tags": [ + ["ots", ""] + ] +} +``` + +Used to prove an event existed at a specific time via Bitcoin blockchain timestamps. + +### NIP-04: Encrypted Direct Messages + +**Status**: Deprecated (use NIP-44) + +Event kind `4` for encrypted private messages. + +**Encryption**: +- ECDH shared secret between sender/receiver +- AES-256-CBC encryption +- Base64 encoded result + +**Format**: +```json +{ + "kind": 4, + "tags": [ + ["p", ""] + ], + "content": "" +} +``` + +**Security Issues**: +- Vulnerable to certain attacks +- No forward secrecy +- Use NIP-44 instead + +### NIP-05: Mapping Nostr Keys to DNS-based Internet Identifiers + +**Status**: Widely implemented + +Allows verification of identity via domain names (like email addresses). + +**Format**: `name@domain.com` + +**Implementation**: + +1. User adds `"nip05": "alice@example.com"` to metadata (kind 0) +2. Domain serves `/.well-known/nostr.json`: + +```json +{ + "names": { + "alice": "" + }, + "relays": { + "": ["wss://relay1.com", "wss://relay2.com"] + } +} +``` + +3. Clients verify by fetching and checking pubkey match + +**Benefits**: +- Human-readable identifiers +- Domain-based verification +- Optional relay hints +- Spam prevention (verified users) + +### NIP-06: Basic Key Derivation from Mnemonic Seed Phrase + +**Status**: Optional + +Derives Nostr keys from BIP39 mnemonic phrases. + +**Derivation Path**: `m/44'/1237'/0'/0/0` +- 1237 is the coin type for Nostr +- Allows HD wallet-style key management + +**Benefits**: +- Backup with 12/24 words +- Multiple accounts from one seed +- Compatible with BIP39 tools + +### NIP-07: window.nostr Capability for Web Browsers + +**Status**: Browser extension standard + +Defines browser API for Nostr key management. + +**API Methods**: + +```javascript +window.nostr.getPublicKey(): Promise +window.nostr.signEvent(event): Promise +window.nostr.getRelays(): Promise<{[url]: {read: boolean, write: boolean}}> +window.nostr.nip04.encrypt(pubkey, plaintext): Promise +window.nostr.nip04.decrypt(pubkey, ciphertext): Promise +``` + +**Usage**: +- Web apps request signatures from extension +- Private keys never leave extension +- User approves each action +- Popular extensions: nos2x, Alby, Flamingo + +### NIP-08: Handling Mentions + +**Status**: Core convention + +Defines how to mention users and events in notes. + +**Format**: +- Add `p` or `e` tags for mentions +- Reference in content with `#[index]` + +```json +{ + "kind": 1, + "tags": [ + ["p", "<pubkey>", "<relay>"], + ["e", "<event-id>", "<relay>"] + ], + "content": "Hello #[0], check out #[1]" +} +``` + +Clients replace `#[0]`, `#[1]` with user-friendly displays. + +### NIP-09: Event Deletion + +**Status**: Widely implemented + +Event kind `5` requests deletion of events. + +**Format**: +```json +{ + "kind": 5, + "tags": [ + ["e", "<event-id-to-delete>"], + ["e", "<another-event-id>"] + ], + "content": "Reason for deletion (optional)" +} +``` + +**Behavior**: +- Only author can delete their events +- Relays SHOULD delete referenced events +- Not guaranteed (relays may ignore) +- Some clients show deletion notice + +### NIP-10: Text Note References (Reply, Threads) + +**Status**: Core threading standard + +Conventions for `e` and `p` tags in threaded conversations. + +**Markers**: +- `root`: The root event of the thread +- `reply`: Direct parent being replied to +- `mention`: Mentioned but not replied to + +**Format**: +```json +{ + "kind": 1, + "tags": [ + ["e", "<root-event-id>", "<relay>", "root"], + ["e", "<parent-event-id>", "<relay>", "reply"], + ["e", "<mentioned-event-id>", "<relay>", "mention"], + ["p", "<author1-pubkey>"], + ["p", "<author2-pubkey>"] + ] +} +``` + +**Best Practices**: +- Always include root marker for thread context +- Include reply marker for direct parent +- Add p tags for all mentioned users +- Maintains thread integrity + +### NIP-11: Relay Information Document + +**Status**: Standard + +HTTP endpoint for relay metadata. + +**Implementation**: +- HTTP GET to relay URL (not WebSocket) +- Accept header: `application/nostr+json` + +**Response Example**: +```json +{ + "name": "Example Relay", + "description": "A Nostr relay", + "pubkey": "<admin-pubkey>", + "contact": "admin@example.com", + "supported_nips": [1, 2, 9, 11, 12, 15, 16, 20, 22], + "software": "git+https://github.com/...", + "version": "1.0.0", + "limitation": { + "max_message_length": 16384, + "max_subscriptions": 20, + "max_filters": 100, + "max_limit": 5000, + "max_subid_length": 100, + "min_prefix": 4, + "max_event_tags": 100, + "max_content_length": 8196, + "min_pow_difficulty": 30, + "auth_required": false, + "payment_required": false + }, + "relay_countries": ["US", "CA"], + "language_tags": ["en", "es"], + "tags": ["adult-content", "no-spam"], + "posting_policy": "https://example.com/policy", + "payments_url": "https://example.com/pay", + "fees": { + "admission": [{"amount": 5000000, "unit": "msats"}], + "subscription": [{"amount": 1000000, "unit": "msats", "period": 2592000}], + "publication": [] + }, + "icon": "https://example.com/icon.png" +} +``` + +**Usage**: +- Clients discover relay capabilities +- Check NIP support before using features +- Display relay info to users +- Respect limitations + +### NIP-12: Generic Tag Queries + +**Status**: Core functionality + +Extends filtering to support any single-letter tag. + +**Syntax**: `#<letter>: [<value>, ...]` + +**Examples**: +```json +{ + "#t": ["bitcoin", "nostr"], + "#p": ["pubkey1", "pubkey2"], + "#e": ["eventid1"] +} +``` + +Matches events with specified tag values. + +### NIP-13: Proof of Work + +**Status**: Spam prevention + +Requires computational work for event publication. + +**Implementation**: +- Add `nonce` tag: `["nonce", "<number>", "<target-difficulty>"]` +- Hash event ID until leading zero bits >= difficulty +- Increment nonce until condition met + +**Example**: +```json +{ + "tags": [ + ["nonce", "12345", "20"] + ], + "id": "00000abcd..." // 20+ leading zero bits +} +``` + +**Difficulty Levels**: +- 0-10: Very easy +- 20: Moderate +- 30+: Difficult +- 40+: Very difficult + +Relays can require minimum PoW for acceptance. + +### NIP-14: Subject Tag + +**Status**: Convenience + +Adds `subject` tag for event titles/subjects. + +**Format**: +```json +{ + "tags": [ + ["subject", "My Post Title"] + ] +} +``` + +Used for long-form content, discussions, emails-style messages. + +### NIP-15: End of Stored Events (EOSE) + +**Status**: Core protocol + +Relay sends `EOSE` after sending all stored events matching a subscription. + +**Format**: `["EOSE", <subscription_id>]` + +**Usage**: +- Clients know when historical events are complete +- Can show "loading" state until EOSE +- New events after EOSE are real-time + +### NIP-16: Event Treatment + +**Status**: Event lifecycle + +Defines three event categories: + +1. **Regular Events** (1000-9999): + - Immutable + - All versions kept + - Examples: notes, reactions + +2. **Replaceable Events** (10000-19999): + - Only latest kept + - Same author + kind → replace + - Examples: metadata, contacts + +3. **Ephemeral Events** (20000-29999): + - Not stored + - Forwarded once + - Examples: typing indicators, presence + +4. **Parameterized Replaceable Events** (30000-39999): + - Replaced based on `d` tag + - Same author + kind + d-tag → replace + - Examples: long-form posts, product listings + +### NIP-18: Reposts + +**Status**: Social feature + +Event kind `6` for reposting/sharing events. + +**Format**: +```json +{ + "kind": 6, + "tags": [ + ["e", "<reposted-event-id>", "<relay>"], + ["p", "<original-author-pubkey>"] + ], + "content": "" // or reposted event JSON +} +``` + +**Generic Repost** (kind 16): +- Can repost any event kind +- Preserves original context + +### NIP-19: bech32-encoded Entities + +**Status**: Widely implemented + +Human-readable encodings for Nostr entities. + +**Formats**: + +1. **npub**: Public key + - `npub1xyz...` + - Safer to share than hex + +2. **nsec**: Private key (SENSITIVE!) + - `nsec1xyz...` + - Never share publicly + +3. **note**: Event ID + - `note1xyz...` + - Links to specific events + +4. **nprofile**: Profile with hints + - Includes pubkey + relay URLs + - Better discovery + +5. **nevent**: Event with hints + - Includes event ID + relay URLs + author + - Reliable event fetching + +6. **naddr**: Replaceable event coordinate + - Includes kind + pubkey + d-tag + relays + - For parameterized replaceable events + +**Usage**: +- Use for sharing/displaying identifiers +- Clients should support all formats +- Always use npub/nsec instead of hex when possible + +### NIP-20: Command Results + +**Status**: Core protocol + +Defines `OK` message format from relays. + +**Format**: `["OK", <event_id>, <accepted>, <message>]` + +**Examples**: +```json +["OK", "abc123...", true, ""] +["OK", "def456...", false, "invalid: signature verification failed"] +["OK", "ghi789...", false, "pow: difficulty too low"] +["OK", "jkl012...", false, "rate-limited: slow down"] +``` + +**Common Rejection Prefixes**: +- `duplicate:` - Event already received +- `pow:` - Insufficient proof of work +- `blocked:` - Pubkey or content blocked +- `rate-limited:` - Too many requests +- `invalid:` - Event validation failed +- `error:` - Server error + +### NIP-21: nostr: URI Scheme + +**Status**: Standard linking + +Defines `nostr:` URI scheme for deep linking. + +**Format**: +- `nostr:npub1...` +- `nostr:note1...` +- `nostr:nevent1...` +- `nostr:nprofile1...` +- `nostr:naddr1...` + +**Usage**: +- Clickable links in web/mobile +- Cross-app navigation +- QR codes + +### NIP-22: Event created_at Limits + +**Status**: Relay policy + +Relays may reject events with timestamps too far in past/future. + +**Recommendations**: +- Reject events created_at > 15 minutes in future +- Reject very old events (relay-specific) +- Prevents timestamp manipulation + +### NIP-23: Long-form Content + +**Status**: Blog/article support + +Event kind `30023` for long-form content (articles, blogs). + +**Format**: +```json +{ + "kind": 30023, + "tags": [ + ["d", "<unique-identifier>"], + ["title", "Article Title"], + ["summary", "Brief description"], + ["published_at", "<unix-timestamp>"], + ["t", "tag1"], ["t", "tag2"], + ["image", "https://..."] + ], + "content": "Markdown content..." +} +``` + +**Characteristics**: +- Parameterized replaceable (by `d` tag) +- Content in Markdown +- Rich metadata +- Can be edited (updates replace) + +### NIP-25: Reactions + +**Status**: Widely implemented + +Event kind `7` for reactions to events (likes, emoji reactions). + +**Format**: +```json +{ + "kind": 7, + "tags": [ + ["e", "<reacted-event-id>"], + ["p", "<event-author-pubkey>"], + ["k", "<reacted-event-kind>"] + ], + "content": "+" // or emoji +} +``` + +**Content Values**: +- `+`: Like/upvote +- `-`: Dislike (discouraged) +- Emoji: 👍, ❤️, 😂, etc. +- Custom reactions + +**Client Display**: +- Count reactions per event +- Group by emoji +- Show who reacted + +### NIP-26: Delegated Event Signing + +**Status**: Advanced delegation + +Allows delegating event signing to another key. + +**Use Cases**: +- Bot accounts posting for user +- Temporary keys for devices +- Service providers posting on behalf + +**Implementation**: +- Delegation token in tags +- Limits by kind, time range +- Original author still verifiable + +### NIP-27: Text Note References + +**Status**: Convenience + +Shortcuts for mentioning entities inline. + +**Format**: +- `nostr:npub1...` → user mention +- `nostr:note1...` → event reference +- `nostr:nevent1...` → event with context + +Clients render as clickable links. + +### NIP-28: Public Chat (Channels) + +**Status**: Channel support + +Event kinds for public chat channels. + +**Event Kinds**: +- `40`: Create channel +- `41`: Set channel metadata +- `42`: Create message +- `43`: Hide message +- `44`: Mute user + +**Channel Creation (kind 40)**: +```json +{ + "kind": 40, + "content": "{\"name\": \"Bitcoin\", \"about\": \"Discussion\", \"picture\": \"url\"}" +} +``` + +**Channel Message (kind 42)**: +```json +{ + "kind": 42, + "tags": [ + ["e", "<channel-id>", "<relay>", "root"] + ], + "content": "Hello channel!" +} +``` + +### NIP-33: Parameterized Replaceable Events + +**Status**: Core feature + +Event kinds 30000-39999 are replaceable by `d` tag. + +**Format**: +```json +{ + "kind": 30000, + "tags": [ + ["d", "<identifier>"] + ] +} +``` + +**Replacement Rule**: +- Same author + kind + d-tag → replace old event +- Different d-tag → separate events +- No d-tag → treated as `d` = "" + +**Coordinate Reference**: +`<kind>:<pubkey>:<d-value>` + +**Use Cases**: +- Product catalogs (each product = d-tag) +- Article revisions (article slug = d-tag) +- Configuration settings (setting name = d-tag) + +### NIP-36: Sensitive Content Warning + +**Status**: Content moderation + +Tags for marking sensitive/NSFW content. + +**Format**: +```json +{ + "tags": [ + ["content-warning", "nudity"], + ["content-warning", "violence"] + ] +} +``` + +Clients can hide/blur until user confirms. + +### NIP-39: External Identities + +**Status**: Identity verification + +Links Nostr identity to external platforms. + +**Format (in kind 0 metadata)**: +```json +{ + "kind": 0, + "content": "{\"identities\": [{\"platform\": \"github\", \"username\": \"alice\", \"proof\": \"url\"}]}" +} +``` + +**Supported Platforms**: +- GitHub +- Twitter +- Mastodon +- Matrix +- Telegram + +### NIP-40: Expiration Timestamp + +**Status**: Ephemeral content + +Tag for auto-expiring events. + +**Format**: +```json +{ + "tags": [ + ["expiration", "<unix-timestamp>"] + ] +} +``` + +Relays should delete event after expiration time. + +### NIP-42: Authentication of Clients to Relays + +**Status**: Access control + +Relays can require client authentication. + +**Flow**: +1. Relay sends: `["AUTH", "<challenge>"]` +2. Client creates kind `22242` event: +```json +{ + "kind": 22242, + "tags": [ + ["relay", "<relay-url>"], + ["challenge", "<challenge-string>"] + ], + "created_at": <now> +} +``` +3. Client sends: `["AUTH", <signed-event>]` +4. Relay verifies signature and challenge + +**Benefits**: +- Spam prevention +- Access control +- Rate limiting per user +- Paid relays + +### NIP-44: Encrypted Payloads (Versioned) + +**Status**: Modern encryption + +Improved encryption replacing NIP-04. + +**Algorithm**: +- ECDH shared secret +- ChaCha20-Poly1305 AEAD +- Version byte for upgradability +- Salt for key derivation + +**Security Improvements**: +- Authenticated encryption +- Better key derivation +- Version support +- Resistance to padding oracle attacks + +**Format**: +``` +<version-byte><encrypted-payload> +``` + +Base64 encode for `content` field. + +### NIP-45: Event Counts + +**Status**: Statistics + +Request for event counts matching filters. + +**Client Request**: +```json +["COUNT", <subscription_id>, <filters>] +``` + +**Relay Response**: +```json +["COUNT", <subscription_id>, {"count": 123, "approximate": false}] +``` + +**Usage**: +- Display follower counts +- Show engagement metrics +- Statistics dashboards + +### NIP-46: Nostr Connect (Remote Signing) + +**Status**: Remote signer protocol + +Protocol for remote key management and signing. + +**Architecture**: +- Signer: Holds private key +- Client: Requests signatures +- Communication via Nostr events + +**Use Cases**: +- Mobile app delegates to desktop signer +- Browser extension as signer +- Hardware wallet integration +- Multi-device key sharing + +### NIP-47: Wallet Connect + +**Status**: Lightning integration + +Protocol for connecting Lightning wallets to Nostr apps. + +**Commands**: +- `pay_invoice` +- `get_balance` +- `get_info` +- `make_invoice` +- `lookup_invoice` + +Enables in-app Lightning payments. + +### NIP-50: Search Capability + +**Status**: Optional + +Full-text search in filter queries. + +**Format**: +```json +{ + "search": "bitcoin nostr" +} +``` + +**Implementation**: +- Relay-specific behavior +- May search content, tags, etc. +- Not standardized ranking + +### NIP-51: Lists + +**Status**: Curation + +Event kinds for various list types. + +**List Kinds**: +- `30000`: Categorized people list +- `30001`: Categorized bookmarks +- `10000`: Mute list +- `10001`: Pin list + +**Format**: +```json +{ + "kind": 30000, + "tags": [ + ["d", "my-list"], + ["p", "<pubkey>", "<relay>", "<petname>"], + ["t", "<category>"] + ] +} +``` + +### NIP-56: Reporting + +**Status**: Moderation + +Event kind `1984` for reporting content. + +**Format**: +```json +{ + "kind": 1984, + "tags": [ + ["e", "<event-id>", "<relay>"], + ["p", "<pubkey>"], + ["report", "spam"] // or "nudity", "profanity", "illegal", "impersonation" + ], + "content": "Additional details" +} +``` + +Used by relays and clients for moderation. + +### NIP-57: Lightning Zaps + +**Status**: Widely implemented + +Protocol for Lightning tips with proof. + +**Flow**: +1. Get user's Lightning address (from metadata) +2. Fetch LNURL data +3. Create zap request (kind `9734`) +4. Pay invoice +5. Relay publishes zap receipt (kind `9735`) + +**Zap Request (kind 9734)**: +```json +{ + "kind": 9734, + "tags": [ + ["p", "<recipient-pubkey>"], + ["amount", "<millisats>"], + ["relays", "relay1", "relay2"], + ["e", "<event-id>"] // if zapping event + ] +} +``` + +**Zap Receipt (kind 9735)**: +Published by LNURL provider, proves payment. + +### NIP-58: Badges + +**Status**: Reputation system + +Award and display badges (achievements, credentials). + +**Event Kinds**: +- `30008`: Badge definition +- `30009`: Profile badges +- `8`: Badge award + +**Badge Definition**: +```json +{ + "kind": 30008, + "tags": [ + ["d", "badge-id"], + ["name", "Badge Name"], + ["description", "What this means"], + ["image", "url"], + ["thumb", "thumbnail-url"] + ] +} +``` + +### NIP-65: Relay List Metadata + +**Status**: Critical for routing + +Event kind `10002` for user's relay preferences. + +**Format**: +```json +{ + "kind": 10002, + "tags": [ + ["r", "wss://relay1.com"], + ["r", "wss://relay2.com", "write"], + ["r", "wss://relay3.com", "read"] + ] +} +``` + +**Usage**: +- Clients discover where to fetch user's events (read) +- Clients know where to send events for user (write) +- Optimizes relay connections +- Reduces bandwidth + +**Best Practice**: +- Always check NIP-65 before querying +- Fall back to NIP-05 relays if no NIP-65 +- Cache relay lists + +### NIP-78: App-Specific Data + +**Status**: Application storage + +Event kind `30078` for arbitrary app data. + +**Format**: +```json +{ + "kind": 30078, + "tags": [ + ["d", "<app-name>:<data-key>"] + ], + "content": "<encrypted-or-public-data>" +} +``` + +**Use Cases**: +- App settings +- Client-specific cache +- User preferences +- Draft posts + +### NIP-84: Highlights + +**Status**: Annotation + +Event kind `9802` for highlighting content. + +**Format**: +```json +{ + "kind": 9802, + "tags": [ + ["e", "<event-id>"], + ["context", "surrounding text..."], + ["a", "<article-coordinate>"] + ], + "content": "highlighted portion" +} +``` + +Like a highlighter pen for web content. + +### NIP-89: Application Handlers + +**Status**: App discovery + +Advertise and discover apps that handle specific event kinds. + +**Format (kind 31989)**: +```json +{ + "kind": 31989, + "tags": [ + ["k", "1"], // handles kind 1 + ["web", "https://app.com/<bech32>"], + ["ios", "app-scheme://<bech32>"], + ["android", "app-package://<bech32>"] + ] +} +``` + +**Kind 31990**: User's preferred handlers + +### NIP-94: File Metadata + +**Status**: File sharing + +Event kind `1063` for file metadata. + +**Format**: +```json +{ + "kind": 1063, + "tags": [ + ["url", "https://..."], + ["m", "image/jpeg"], // MIME type + ["x", "<sha256-hash>"], + ["size", "123456"], + ["dim", "1920x1080"], + ["magnet", "magnet:..."], + ["blurhash", "..."] + ], + "content": "Description" +} +``` + +**Use Cases**: +- Images, videos, audio +- Documents +- Torrents +- IPFS files + +### NIP-96: HTTP File Storage Integration + +**Status**: File hosting + +HTTP API for file uploads/downloads. + +**Endpoints**: +- `GET /.well-known/nostr/nip96.json` - Server info +- `POST /upload` - Upload file +- `DELETE /delete` - Delete file + +**Upload Response**: +Returns kind `1063` event data for the file. + +### NIP-98: HTTP Auth + +**Status**: API authentication + +Use Nostr events for HTTP API auth. + +**Flow**: +1. Create kind `27235` event with: + - `u` tag: API URL + - `method` tag: HTTP method +2. Add `Authorization: Nostr <base64-event>` header +3. Server verifies signature + +**Benefits**: +- No passwords +- Cryptographic authentication +- Works with Nostr keys + +## Summary of Key NIPs by Category + +### Essential (All implementations) +- NIP-01, NIP-02, NIP-10, NIP-19 + +### Social Features +- NIP-25 (reactions), NIP-18 (reposts), NIP-23 (long-form), NIP-28 (channels) + +### Identity & Discovery +- NIP-05 (verification), NIP-39 (external identities), NIP-65 (relay lists) + +### Security & Privacy +- NIP-04 (deprecated encryption), NIP-44 (modern encryption), NIP-42 (auth), NIP-13 (PoW) + +### Lightning Integration +- NIP-47 (wallet connect), NIP-57 (zaps) + +### Content & Moderation +- NIP-56 (reporting), NIP-36 (content warnings), NIP-09 (deletion) + +### Advanced Features +- NIP-33 (parameterized replaceable), NIP-46 (remote signing), NIP-50 (search) +