--- name: nostr-expert description: Nostr protocol implementation patterns in Quartz (AmethystMultiplatform's KMP Nostr library). Use when working with: (1) Nostr events (creating, parsing, signing), (2) Event kinds and tags, (3) NIP implementations (57 NIPs in quartz/), (4) Event builders and TagArrayBuilder DSL, (5) Nostr cryptography (secp256k1, NIP-44 encryption), (6) Relay communication patterns, (7) Bech32 encoding (npub, nsec, note, nevent). Complements nostr-protocol agent (NIP specs) - this skill provides Quartz codebase patterns and implementation details. --- # Nostr Protocol Expert (Quartz Implementation) Practical patterns for working with Nostr in Quartz, AmethystMultiplatform's KMP Nostr library. ## When to Use This Skill - Implementing Nostr event types (TextNote, Reaction, Zap, etc.) - Creating/parsing events with TagArrayBuilder DSL - Working with event kinds and tags - Finding NIP implementations in quartz/ codebase - Nostr cryptography (secp256k1 signing, NIP-44 encryption) - Bech32 encoding/decoding (npub, nsec, note formats) - Event validation and verification **For NIP specifications** → Use `nostr-protocol` agent **For Quartz implementation** → Use this skill ## Quartz Architecture Quartz organizes code by NIP number: ``` quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/ ├── nip01Core/ # Core protocol (Event, Kind, Tags) ├── nip04Dm/ # Legacy DMs (deprecated) ├── nip10Notes/ # Text notes with threading ├── nip17Dm/ # Private DMs (gift wrap) ├── nip19Bech32/ # Bech32 encoding ├── nip44Encryption/ # Modern encryption (ChaCha20) ├── nip57Zaps/ # Lightning zaps ├── ... (57 NIPs total) └── experimental/ # Draft NIPs ``` **Pattern**: `nip##/` directories contain event classes, tags, and utilities for that NIP. **Find implementations**: Use `scripts/nip-lookup.sh ` or see `references/nip-catalog.md`. ## Event Anatomy ### Core Structure ```kotlin @Immutable open class Event( val id: HexKey, // SHA-256 hash of serialized event val pubKey: HexKey, // Author's public key (32 bytes hex) val createdAt: Long, // Unix timestamp val kind: Kind, // Event kind (Int typealias) val tags: TagArray, // Array of tag arrays val content: String, // Event content val sig: HexKey, // Schnorr signature (64 bytes hex) ) : IEvent ``` **Key insight**: `Event` is the base class. Specific event types (TextNoteEvent, ReactionEvent) extend it and add parsing/helper methods. ### Kind Classification ```kotlin typealias Kind = Int fun Kind.isEphemeral() = this in 20000..29999 // Not stored fun Kind.isReplaceable() = this == 0 || this == 3 || this in 10000..19999 fun Kind.isAddressable() = this in 30000..39999 // Replaceable + has d-tag fun Kind.isRegular() = this in 1000..9999 // Stored, not replaced ``` **Pattern**: Kind determines event lifecycle and replaceability. ## Creating Events ### EventTemplate Pattern ```kotlin fun eventTemplate( kind: Kind, content: String, tags: TagArray = emptyArray() ): EventTemplate ``` **Usage**: ```kotlin val template = eventTemplate( kind = 1, // Text note content = "Hello Nostr!", tags = tagArray { add(arrayOf("subject", "Greeting")) } ) // Sign with a signer val signedEvent = signer.sign(template) ``` **Why templates?** Separates event data from signing. Templates can be signed by different signers (local keys, remote signers, hardware wallets). ### TagArrayBuilder DSL ```kotlin fun tagArray( initializer: TagArrayBuilder.() -> Unit ): TagArray ``` **Methods**: - `add(tag)` - Append tag - `addFirst(tag)` - Prepend tag (for ordering) - `addUnique(tag)` - Replace all tags with this name - `remove(tagName)` - Remove by name - `addAll(tags)` - Bulk add **Example**: ```kotlin val tags = tagArray { add(arrayOf("e", replyToEventId, "", "reply")) add(arrayOf("p", authorPubkey)) addUnique(arrayOf("subject", "Re: Hello")) add(arrayOf("content-warning", "spoilers")) } ``` **Pattern**: Fluent DSL for building tag arrays with validation and deduplication. ## Common Event Types ### TextNoteEvent (kind 1) ```kotlin class TextNoteEvent : BaseThreadedEvent ``` **Creating**: ```kotlin val note = eventTemplate( kind = 1, content = "Hello world!", tags = tagArray { add(arrayOf("subject", "First post")) } ) ``` **Parsing**: ```kotlin val event: TextNoteEvent = ... val subject = event.subject() // Extension from nip14Subject val mentions = event.mentions() // List of p-tags val quotedEvents = event.quotes() // List of q-tags ``` ### ReactionEvent (kind 7) ```kotlin fun createReaction( targetEvent: Event, emoji: String = "+" ): EventTemplate { return eventTemplate( kind = 7, content = emoji, tags = tagArray { add(arrayOf("e", targetEvent.id)) add(arrayOf("p", targetEvent.pubKey)) } ) } ``` ### MetadataEvent (kind 0) ```kotlin data class UserMetadata( val name: String?, val displayName: String?, val picture: String?, val banner: String?, val about: String?, // ... more fields ) fun createMetadata(metadata: UserMetadata): EventTemplate { return eventTemplate( kind = 0, content = metadata.toJson() // Serialize to JSON ) } ``` ### Addressable Events (kinds 30000-40000) ```kotlin fun createArticle( slug: String, title: String, content: String ): EventTemplate { return eventTemplate( kind = 30023, content = content, tags = tagArray { addUnique(arrayOf("d", slug)) // Unique identifier add(arrayOf("title", title)) add(arrayOf("published_at", "${TimeUtils.now()}")) } ) } ``` **Key**: `d-tag` makes it addressable. Events with same kind + pubkey + d-tag replace each other. ## Tag Patterns Tags are `Array` with pattern `[name, value, ...optionalParams]`. ### Core Tags **e-tag** (event reference): ```kotlin add(arrayOf("e", eventId, relayHint, marker)) // marker: "reply", "root", "mention" ``` **p-tag** (pubkey reference): ```kotlin add(arrayOf("p", pubkey, relayHint)) ``` **a-tag** (addressable event): ```kotlin add(arrayOf("a", "$kind:$pubkey:$dtag", relayHint)) ``` **d-tag** (identifier for addressable events): ```kotlin addUnique(arrayOf("d", "unique-slug")) ``` ### Tag Extensions ```kotlin // Find tags event.tags.tagValue("subject") // First subject tag value event.tags.allTags("p") // All p-tags event.tags.tagValues("e") // All e-tag values // Parse structured tags event.tags.mapNotNull(ETag::parse) // Parse as ETag objects ``` For comprehensive tag patterns, see `references/tag-patterns.md`. ## Threading (NIP-10) ```kotlin fun createReply( original: TextNoteEvent, content: String ): EventTemplate { return eventTemplate( kind = 1, content = content, tags = tagArray { // Reply marker add(arrayOf("e", original.id, "", "reply")) // Root marker (original's root, or original itself) original.rootEvent()?.let { add(arrayOf("e", it.id, "", "root")) } ?: add(arrayOf("e", original.id, "", "root")) // Tag author add(arrayOf("p", original.pubKey)) // Tag all mentioned users original.mentions().forEach { add(arrayOf("p", it)) } } ) } ``` **Pattern**: `reply` and `root` markers establish thread hierarchy. ## Cryptography ### Signing (secp256k1) ```kotlin interface ISigner { suspend fun sign(template: EventTemplate): Event } // Local key signing class LocalSigner(private val privateKey: ByteArray) : ISigner { override suspend fun sign(template: EventTemplate): Event { val id = template.generateId() val sig = Secp256k1.sign(id, privateKey) return Event(id, pubKey, createdAt, kind, tags, content, sig) } } ``` **Pattern**: Signers abstract key management. Can be local, remote (NIP-46), or hardware. ### Encryption (NIP-44) ```kotlin // Modern encryption (ChaCha20-Poly1305) object Nip44v2 { fun encrypt(plaintext: String, privateKey: ByteArray, pubKey: HexKey): String fun decrypt(ciphertext: String, privateKey: ByteArray, pubKey: HexKey): String } // Usage val encrypted = Nip44v2.encrypt( plaintext = "Secret message", privateKey = myPrivateKey, pubKey = recipientPubKey ) val decrypted = Nip44v2.decrypt( ciphertext = encrypted, privateKey = myPrivateKey, pubKey = senderPubKey ) ``` **Pattern**: Elliptic curve Diffie-Hellman + ChaCha20-Poly1305 AEAD. ### NIP-04 (Deprecated) ```kotlin // Legacy encryption (NIP-04, deprecated for NIP-44) object Nip04 { fun encrypt(msg: String, privateKey: ByteArray, pubKey: HexKey): String fun decrypt(msg: String, privateKey: ByteArray, pubKey: HexKey): String } ``` **Note**: Use NIP-44 (Nip44v2) for new implementations. NIP-04 has security issues. ## Bech32 Encoding (NIP-19) ```kotlin object Nip19 { // Encode fun npubEncode(pubkey: HexKey): String // npub1... fun nsecEncode(privateKey: ByteArray): String // nsec1... fun noteEncode(eventId: HexKey): String // note1... fun neventEncode(eventId: HexKey, relays: List = emptyList()): String fun nprofileEncode(pubkey: HexKey, relays: List = emptyList()): String fun naddrEncode(kind: Int, pubkey: HexKey, dTag: String, relays: List = emptyList()): String // Decode fun decode(bech32: String): Nip19Result } sealed class Nip19Result { data class NPub(val hex: HexKey) : Nip19Result() data class NSec(val hex: HexKey) : Nip19Result() data class Note(val hex: HexKey) : Nip19Result() data class NEvent(val hex: HexKey, val relays: List) : Nip19Result() data class NProfile(val hex: HexKey, val relays: List) : Nip19Result() data class NAddr(val kind: Int, val pubkey: HexKey, val dTag: String, val relays: List) : Nip19Result() } ``` **Usage**: ```kotlin // Encode val npub = Nip19.npubEncode(pubkeyHex) // Output: "npub1..." // Decode when (val result = Nip19.decode(npub)) { is Nip19Result.NPub -> println("Pubkey: ${result.hex}") is Nip19Result.NEvent -> println("Event: ${result.hex}, relays: ${result.relays}") else -> println("Other type") } ``` ## Event Validation ```kotlin fun Event.verify(): Boolean { // 1. Verify ID matches content hash val computedId = generateId() if (id != computedId) return false // 2. Verify signature return Secp256k1.verify(id, sig, pubKey) } fun Event.generateId(): HexKey { val serialized = serializeForId() // JSON array format return sha256(serialized) } ``` **Pattern**: Always verify events from untrusted sources (relays). ## Common Workflows ### Publishing an Event ```kotlin suspend fun publishNote(content: String, signer: ISigner, relays: List) { // 1. Create template val template = eventTemplate(kind = 1, content = content) // 2. Sign val event = signer.sign(template) // 3. Verify (optional but recommended) require(event.verify()) { "Signature verification failed" } // 4. Publish to relays relays.forEach { relay -> relayClient.send(relay, event) } } ``` ### Querying Events ```kotlin // Subscription filter data class Filter( val ids: List? = null, val authors: List? = null, val kinds: List? = null, val since: Long? = null, val until: Long? = null, val limit: Int? = null, val tags: Map>? = null // e.g., {"#e": [eventId], "#p": [pubkey]} ) // Usage val filter = Filter( authors = listOf(userPubkey), kinds = listOf(1), // Text notes only limit = 50 ) relayClient.subscribe(relay, filter) { event -> // Handle incoming events } ``` ### Creating a Zap (NIP-57) ```kotlin fun createZapRequest( targetEvent: Event, amountSats: Long, comment: String = "" ): EventTemplate { return eventTemplate( kind = 9734, // Zap request content = comment, tags = tagArray { add(arrayOf("e", targetEvent.id)) add(arrayOf("p", targetEvent.pubKey)) add(arrayOf("amount", "${amountSats * 1000}")) // millisats add(arrayOf("relays", "wss://relay1.com", "wss://relay2.com")) } ) } ``` ### Gift-Wrapped DMs (NIP-17) ```kotlin fun createGiftWrappedDM( recipientPubkey: HexKey, message: String, signer: ISigner ): Event { // 1. Create sealed gossip (kind 14) val sealedGossip = createSealedGossip(message, recipientPubkey, signer) // 2. Wrap in gift wrap (kind 1059) return createGiftWrap(sealedGossip, recipientPubkey, signer) } ``` **Pattern**: Double encryption + random ephemeral keys for metadata protection. ## Finding NIPs Use the bundled script: ```bash # Find by NIP number scripts/nip-lookup.sh 44 # Search by term scripts/nip-lookup.sh encryption scripts/nip-lookup.sh "gift wrap" ``` Or see `references/nip-catalog.md` for complete catalog. ## Bundled Resources - **references/nip-catalog.md** - All 57 NIPs with package locations and key files - **references/event-hierarchy.md** - Event class hierarchy, kind classifications, common types - **references/tag-patterns.md** - Tag structure, TagArrayBuilder DSL, common tag types, parsing patterns - **scripts/nip-lookup.sh** - Find NIP implementations by number or search term ## Quick Reference | Task | Pattern | Location | |------|---------|----------| | Create event | `eventTemplate(kind, content, tags)` | nip01Core/signers/ | | Build tags | `tagArray { add(...) }` | nip01Core/core/ | | Sign event | `signer.sign(template)` | nip01Core/signers/ | | Verify signature | `event.verify()` | nip01Core/core/ | | Encrypt (NIP-44) | `Nip44v2.encrypt(...)` | nip44Encryption/ | | Bech32 encode | `Nip19.npubEncode(...)` | nip19Bech32/ | | Find NIP | `scripts/nip-lookup.sh ` | - | ## Common Event Kinds | Kind | Type | NIP | Package | |------|------|-----|---------| | 0 | Metadata | 01 | nip01Core/ | | 1 | Text note | 01, 10 | nip10Notes/ | | 3 | Contact list | 02 | nip02FollowList/ | | 5 | Deletion | 09 | nip09Deletions/ | | 7 | Reaction | 25 | nip25Reactions/ | | 1059 | Gift wrap | 59 | nip59Giftwrap/ | | 9734 | Zap request | 57 | nip57Zaps/ | | 9735 | Zap receipt | 57 | nip57Zaps/ | | 10002 | Relay list | 65 | nip65RelayList/ | | 30023 | Long-form content | 23 | nip23LongContent/ | ## Related Skills - **nostr-protocol** - NIP specifications and protocol details - **kotlin-expert** - Kotlin patterns (@Immutable, sealed classes, DSLs) - **kotlin-coroutines** - Async patterns for relay communication - **kotlin-multiplatform** - KMP patterns, expect/actual in Quartz