### Run Example Script Source: https://github.com/nbd-wtf/go-nostr/blob/master/README.md Execute the example script provided within the project. ```bash go run example/example.go ``` -------------------------------- ### MultiStore Relay Query Example Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Demonstrates querying multiple relays simultaneously using MultiStore and retrieving events sorted by timestamp. ```go package main import ( "context" "fmt" "time" "github.com/nbd-wtf/go-nostr" ) func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() r1, _ := nostr.RelayConnect(ctx, "wss://relay.damus.io") r2, _ := nostr.RelayConnect(ctx, "wss://nos.lol") multi := nostr.MultiStore{r1, r2} events, err := multi.QuerySync(ctx, nostr.Filter{ Kinds: []int{nostr.KindTextNote}, Limit: 5, }) if err != nil { fmt.Println("error (partial):", err) } for _, ev := range events { fmt.Println(ev.ID[:8], ev.CreatedAt) } } ``` -------------------------------- ### Install go-nostr Source: https://github.com/nbd-wtf/go-nostr/blob/master/README.md Use this command to add the go-nostr library to your Go project. ```bash go get github.com/nbd-wtf/go-nostr ``` -------------------------------- ### Enable libsecp256k1 Compilation Source: https://github.com/nbd-wtf/go-nostr/blob/master/README.md To compile with libsecp256k1 for improved performance, use the `-tags=libsecp256k1` flag during compilation. This requires libsecp256k1 to be installed as a shared library on the host system and CGO to be supported. ```bash # Example compilation command: go build -tags=libsecp256k1 your_program.go ``` -------------------------------- ### Test for Wasm Source: https://github.com/nbd-wtf/go-nostr/blob/master/README.md To run tests for WebAssembly compilation, first install wasmbrowsertest, then execute the tests using the specified GOOS and GOARCH environment variables. ```bash GOOS=js GOARCH=wasm go test -short ./... ``` -------------------------------- ### Get Event Expiration Timestamp Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Extracts the expiration timestamp from an event's tags using NIP-40. Returns -1 if the tag is absent or invalid. ```go package main import ( "fmt" "time" "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip40" ) func main() { expiry := nostr.Timestamp(time.Now().Add(24 * time.Hour).Unix()) ev := nostr.Event{ Kind: nostr.KindTextNote, Content: "This note expires in 24 hours", Tags: nostr.Tags{ {"expiration", fmt.Sprintf("%d", expiry)}, }, } ts := nip40.GetExpiration(ev.Tags) fmt.Println("expires at:", ts) fmt.Println("is expired:", ts < nostr.Now()) // false // Event without expiration tag ev2 := nostr.Event{Kind: nostr.KindTextNote} fmt.Println("no expiry:", nip40.GetExpiration(ev2.Tags)) // -1 } ``` -------------------------------- ### Create and Use a PlainKeySigner Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Demonstrates creating an in-memory KeySigner for signing events and NIP-44 encryption/decryption. Caches conversation keys for performance. ```go package main import ( "context" "fmt" "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/keyer" ) func main() { sk := nostr.GeneratePrivateKey() ks, err := keyer.NewPlainKeySigner(sk) if err != nil { panic(err) } ctx := context.Background() pk, _ := ks.GetPublicKey(ctx) fmt.Println("public key:", pk[:16], "...") // Sign an event ev := &nostr.Event{ CreatedAt: nostr.Now(), Kind: nostr.KindTextNote, Content: "signed via KeySigner", } if err := ks.SignEvent(ctx, ev); err != nil { panic(err) } fmt.Println("signed event ID:", ev.ID[:16], "...") // Encrypt/decrypt using NIP-44 recipientSk := nostr.GeneratePrivateKey() recipientPk, _ := nostr.GetPublicKey(recipientSk) ciphertext, err := ks.Encrypt(ctx, "hello!", recipientPk) if err != nil { panic(err) } recipientKs, _ := keyer.NewPlainKeySigner(recipientSk) plaintext, _ := recipientKs.Decrypt(ctx, ciphertext, pk) fmt.Println("decrypted:", plaintext) // hello! } ``` -------------------------------- ### Relay Connection and Event Handling Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Shows how to connect to a Nostr relay, query stored events using a filter, and publish a new event. ```go package main import ( "context" "fmt" "time" "github.com/nbd-wtf/go-nostr" ) func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() relay, err := nostr.RelayConnect(ctx, "wss://relay.damus.io") if err != nil { panic(err) } defer relay.Close() // Query stored events synchronously filter := nostr.Filter{ Kinds: []int{nostr.KindTextNote}, Limit: 5, } events, err := relay.QuerySync(ctx, filter) if err != nil { panic(err) } for _, ev := range events { fmt.Printf("[%s] %s\n", ev.PubKey[:8], ev.Content) } // Publish an event sk := nostr.GeneratePrivateKey() pk, _ := nostr.GetPublicKey(sk) ev := nostr.Event{ PubKey: pk, CreatedAt: nostr.Now(), Kind: nostr.KindTextNote, Content: "Hello from go-nostr!", } ev.Sign(sk) if err := relay.Publish(ctx, ev); err != nil { fmt.Println("publish error:", err) } else { fmt.Println("published:", ev.ID) } } ``` -------------------------------- ### Nostr Subscriptions and Live Events Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Demonstrates how to subscribe to events from a relay, handle stored events (EOSE), and listen for new live events. ```go package main import ( "context" "fmt" "time" "github.com/nbd-wtf/go-nostr" ) func main() { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() relay, _ := nostr.RelayConnect(ctx, "wss://relay.nostr.band") defer relay.Close() since := nostr.Now() - 3600 // last hour sub, err := relay.Subscribe(ctx, nostr.Filters{{ Kinds: []int{nostr.KindTextNote}, Since: &since, Limit: 10, }}) if err != nil { panic(err) } // Wait for all stored events before listening for new ones <-sub.EndOfStoredEvents fmt.Println("stored events received, now listening for live events...") for { select { case ev, ok := <-sub.Events: if !ok { return } fmt.Printf("event %s: %s\n", ev.ID[:8], ev.Content) case <-ctx.Done(): sub.Unsub() return } } } ``` -------------------------------- ### Benchmark with libsecp256k1 Source: https://github.com/nbd-wtf/go-nostr/blob/master/README.md Shows benchmark results comparing performance with and without libsecp256k1 for signing and checking operations. ```text goos: linux goarch: amd64 cpu: Intel(R) Core(TM) i5-2400 CPU @ 3.10GHz BenchmarkWithoutLibsecp256k1/sign-4 2794 434114 ns/op BenchmarkWithoutLibsecp256k1/check-4 4352 297416 ns/op BenchmarkWithLibsecp256k1/sign-4 12559 94607 ns/op BenchmarkWithLibsecp256k1/check-4 13761 84595 ns/op PASS ``` -------------------------------- ### Create, Sign, and Verify Nostr Events in Go Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Demonstrates the creation, signing, and verification of a Nostr event using the Go Nostr library. Ensure event integrity and authenticity before publishing. ```go package main import ( "fmt" "github.com/nbd-wtf/go-nostr" ) func main() { sk := nostr.GeneratePrivateKey() pk, _ := nostr.GetPublicKey(sk) ev := nostr.Event{ PubKey: pk, CreatedAt: nostr.Now(), Kind: nostr.KindTextNote, Tags: nostr.Tags{{"t", "nostr"}}, Content: "Hello Nostr!", } // Sign sets ID, PubKey, and Sig if err := ev.Sign(sk); err != nil { panic(err) } fmt.Println("ID:", ev.ID) fmt.Println("Sig:", ev.Sig[:16], "...") // Verify ok, err := ev.CheckSignature() fmt.Println("signature valid:", ok, err) // Check ID integrity fmt.Println("ID valid:", ev.CheckID()) // ID: 3d4a... // signature valid: true // ID valid: true } ``` -------------------------------- ### Working with Nostr Tags in Go Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Illustrates how to create and query tags within a Nostr event using the Go Nostr library. Useful for categorizing and referencing other events or public keys. ```go package main import ( "fmt" "github.com/nbd-wtf/go-nostr" ) func main() { ev := nostr.Event{ Kind: nostr.KindTextNote, Content: "reply", Tags: nostr.Tags{ {"e", "abc123eventid", "wss://relay.example.com", "reply"}, {"p", "deadbeefpubkey"}, {"t", "nostr"}, }, } eTag := ev.Tags.Find("e") fmt.Println("e tag:", eTag) // e tag: [e abc123eventid wss://relay.example.com reply] pTag := ev.Tags.Find("p") fmt.Println("p tag:", pTag) // p tag: [p deadbeefpubkey] hasTopic := ev.Tags.ContainsAny("t", []string{"bitcoin", "nostr"}) fmt.Println("has topic:", hasTopic) // has topic: true } ``` -------------------------------- ### Configure Logging Source: https://github.com/nbd-wtf/go-nostr/blob/master/README.md To enable debug logs, compile or run your program with the `-tags debug` flag. To disable info logs completely, replace `nostr.InfoLogger` with `log.New(io.Discard, "", 0)`. ```go nostr.InfoLogger = log.New(io.Discard, "", 0) ``` -------------------------------- ### Perform Proof of Work (NIP-13) Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Implements event proof-of-work by mining a nonce tag to achieve a target number of leading zero bits in the event ID. DoWork mines in parallel across all CPU cores. Check verifies the difficulty. ```go package main import ( "context" "fmt" "time" "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip13" ) func main() { sk := nostr.GeneratePrivateKey() pk, _ := nostr.GetPublicKey(sk) ev := nostr.Event{ PubKey: pk, CreatedAt: nostr.Now(), Kind: nostr.KindTextNote, Content: "proof of work note", Tags: nostr.Tags{}, } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Mine for 16 bits of difficulty nonceTag, err := nip13.DoWork(ctx, ev, 16) if err != nil { panic(err) } ev.Tags = append(ev.Tags, nonceTag) ev.Sign(sk) difficulty := nip13.Difficulty(ev.ID) fmt.Printf("achieved %d bits of difficulty\n", difficulty) fmt.Println("nonce tag:", nonceTag) // Check difficulty satisfies target if err := nip13.Check(ev.ID, 16); err != nil { fmt.Println("insufficient difficulty:", err) } else { fmt.Println("proof of work valid!") } } ``` -------------------------------- ### Encrypt and Decrypt Private Key with Password (NIP-49) Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Encrypts a private key using scrypt + XChaCha20-Poly1305 and encodes it as an `ncryptsec1` bech32 string. Decrypts the key using the password. Ensure the correct password is used for decryption, as an incorrect one will return an error. ```go package main import ( "fmt" "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip49" ) func main() { sk := nostr.GeneratePrivateKey() password := "correct-horse-battery-staple" // Encrypt with logN=16 (standard security) ncryptsec, err := nip49.Encrypt(sk, password, 16, nip49.NotKnownToHaveBeenHandledInsecurely) if err != nil { panic(err) } fmt.Println("ncryptsec:", ncryptsec[:20], "...") // Decrypt recovered, err := nip49.Decrypt(ncryptsec, password) if err != nil { panic(err) } fmt.Println("keys match:", sk == recovered) // true // Wrong password returns error _, err = nip49.Decrypt(ncryptsec, "wrong-password") fmt.Println("wrong password error:", err != nil) // true } ``` -------------------------------- ### Publish Event to Two Relays Source: https://github.com/nbd-wtf/go-nostr/blob/master/README.md Generates a private key, creates a text note event, signs it, and publishes it to two specified relays. Handles potential connection or publishing errors for each relay. ```go sk := nostr.GeneratePrivateKey() pub, _ := nostr.GetPublicKey(sk) ev := nostr.Event{ PubKey: pub, CreatedAt: nostr.Now(), Kind: nostr.KindTextNote, Tags: nil, Content: "Hello World!", } // calling Sign sets the event ID field and the event Sig field ev.Sign(sk) // publish the event to two relays ctx := context.Background() for _, url := range []string{"wss://relay.stoner.com", "wss://nostr-pub.wellorder.net"} { relay, err := nostr.RelayConnect(ctx, url) if err != nil { fmt.Println(err) continue } if err := relay.Publish(ctx, ev); err != nil { fmt.Println(err) continue } fmt.Printf("published to %s\n", url) } ``` -------------------------------- ### Nostr Filters and Event Matching Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Explains how to define Nostr filters and use them to match events, including timestamp constraints and theoretical limits. ```go package main import ( "fmt" "github.com/nbd-wtf/go-nostr" ) func main() { since := nostr.Timestamp(1700000000) until := nostr.Now() f := nostr.Filter{ Kinds: []int{nostr.KindTextNote, nostr.KindReaction}, Authors: []string{"deadbeef...pubkey"}, Tags: nostr.TagMap{"t": {"nostr", "bitcoin"}}, Since: &since, Until: &until, Limit: 100, } ev := &nostr.Event{ ID: "abc123", PubKey: "deadbeef...pubkey", CreatedAt: nostr.Now(), Kind: nostr.KindTextNote, Tags: nostr.Tags{{"t", "nostr"}}, } fmt.Println("matches:", f.Matches(ev)) // true limit := nostr.GetTheoreticalLimit(f) fmt.Println("theoretical limit:", limit) // -1 (no fixed bound for regular kind + authors) // Replaceable kind: 1 author × 1 kind = 1 possible event replaceableFilter := nostr.Filter{ Kinds: []int{nostr.KindProfileMetadata}, Authors: []string{"deadbeef...pubkey"}, } fmt.Println("theoretical limit (replaceable):", nostr.GetTheoreticalLimit(replaceableFilter)) // 1 } ``` -------------------------------- ### Implement Relay Authentication (NIP-42) Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Use `CreateUnsignedAuthEvent` to build a kind-22242 auth event on the client side. Use `ValidateAuthEvent` on the relay side to verify the received auth event. ```go package main import ( "fmt" "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip42" ) func main() { sk := nostr.GeneratePrivateKey() pk, _ := nostr.GetPublicKey(sk) relayURL := "wss://relay.example.com" challenge := "some-random-challenge-string" // Client side: build and sign auth event authEvent := nip42.CreateUnsignedAuthEvent(challenge, pk, relayURL) if err := authEvent.Sign(sk); err != nil { panic(err) } fmt.Println("auth event kind:", authEvent.Kind) // 22242 // Relay side: validate authedPubkey, ok := nip42.ValidateAuthEvent(&authEvent, challenge, relayURL) fmt.Println("auth ok:", ok) fmt.Println("authed pubkey:", authedPubkey[:16], "...") } ``` -------------------------------- ### Fetch Relay Information Document (NIP-11) Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Fetches a relay's application/nostr+json metadata document. Use Fetch to retrieve information like name, description, supported NIPs, and limitations. ```go package main import ( "context" "fmt" "time" "github.com/nbd-wtf/go-nostr/nip11" ) func main() { ctx, cancel := context.WithTimeout(context.Background(), 7*time.Second) defer cancel() info, err := nip11.Fetch(ctx, "wss://relay.damus.io") if err != nil { fmt.Println("error:", err) return } fmt.Println("name:", info.Name) fmt.Println("description:", info.Description) fmt.Println("pubkey:", info.PubKey) fmt.Println("software:", info.Software) fmt.Println("supported NIPs:", info.SupportedNIPs) if info.Limitation != nil { fmt.Println("max message length:", info.Limitation.MaxMessageLength) fmt.Println("auth required:", info.Limitation.AuthRequired) } } ``` -------------------------------- ### Derive Nostr Keys from Mnemonic (NIP-06) Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Implements BIP-39/BIP-32 hierarchical deterministic key derivation for Nostr using the derivation path m/44'/1237'/0'/0/0. Use GenerateSeedWords to create a new mnemonic and PrivateKeyFromSeed to derive the private key. ```go package main import ( "fmt" "github.com/nbd-wtf/go-nostr/nip06" "github.com/nbd-wtf/go-nostr" ) func main() { // Generate a new 24-word mnemonic words, err := nip06.GenerateSeedWords() if err != nil { panic(err) } fmt.Println("mnemonic:", words) // Validate mnemonic fmt.Println("valid:", nip06.ValidateWords(words)) // Derive private key seed := nip06.SeedFromWords(words) sk, err := nip06.PrivateKeyFromSeed(seed) if err != nil { panic(err) } pk, _ := nostr.GetPublicKey(sk) fmt.Println("derived sk:", sk[:16], "...") fmt.Println("derived pk:", pk[:16], "...") } ``` -------------------------------- ### NIP-19 Bech32 Encoding and Decoding Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Shows how to encode private keys, public keys, notes, profiles, events, and entities into bech32 format and decode a profile pointer. ```go package main import ( "fmt" "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip19" ) func main() { sk := nostr.GeneratePrivateKey() pk, _ := nostr.GetPublicKey(sk) // Encode nsec, _ := nip19.EncodePrivateKey(sk) npub, _ := nip19.EncodePublicKey(pk) note, _ := nip19.EncodeNote("3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d") nprofile, _ := nip19.EncodeProfile(pk, []string{"wss://relay.damus.io", "wss://nos.lol"}) nevent, _ := nip19.EncodeEvent( "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", []string{"wss://relay.damus.io"}, pk, ) naddr, _ := nip19.EncodeEntity(pk, nostr.KindArticle, "my-article-slug", []string{"wss://relay.damus.io"}) fmt.Println(nsec[:10], "...") fmt.Println(npub[:10], "...") fmt.Println(note[:10], "...") fmt.Println(nprofile[:10], "...") fmt.Println(nevent[:10], "...") fmt.Println(naddr[:10], "...") // Decode prefix, val, err := nip19.Decode(nprofile) if err != nil { panic(err) } fmt.Println("prefix:", prefix) // nprofile ptr := val.(nostr.ProfilePointer) fmt.Println("pubkey:", ptr.PublicKey[:8]) fmt.Println("relays:", ptr.Relays) } ``` -------------------------------- ### Connect to a Bunker Remote Signer Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Shows how to validate a `bunker://` URI and connect to a remote signer service using NIP-46. Handles potential connection errors. ```go package main import ( "context" "fmt" "time" "github.com/nbd-wtf/go-nostr/nip46" ) func main() { // Validate a bunker URL bunkerURL := "bunker://b3fab...pubkey?relay=wss%3A%2F%2Frelay.nsecbunker.com&secret=abc123" fmt.Println("valid bunker URL:", nip46.IsValidBunkerURL(bunkerURL)) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Connect to a bunker signer, err := nip46.NewSignerFromBunkerURI(ctx, bunkerURL) if err != nil { fmt.Println("bunker connect error (expected in example):", err) return } pk, err := signer.GetPublicKey(ctx) if err != nil { fmt.Println("get public key error:", err) return } fmt.Println("remote signer public key:", pk) } ``` -------------------------------- ### Nostr Event Kinds and Predicates Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Demonstrates the usage of named constants for Nostr event kinds and predicate functions to classify them. ```go package main import ( "fmt" "github.com/nbd-wtf/go-nostr" ) func main() { fmt.Println(nostr.KindTextNote) // 1 fmt.Println(nostr.KindEncryptedDirectMessage) // 4 fmt.Println(nostr.KindReaction) // 7 fmt.Println(nostr.KindArticle) // 30023 fmt.Println(nostr.IsRegularKind(1)) // true fmt.Println(nostr.IsReplaceableKind(0)) // true (profile metadata) fmt.Println(nostr.IsEphemeralKind(20001)) // true fmt.Println(nostr.IsAddressableKind(30023)) // true } ``` -------------------------------- ### Event Creation, Signing, and Verification Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Core structures and methods for creating, signing, and verifying Nostr events. ```APIDOC ## Event Creation, Signing, and Verification `Event` is the core Nostr data structure. `GetID` computes the canonical SHA-256 event ID; `CheckID` verifies the stored ID efficiently; `Sign` signs the event with a raw private key hex string and populates `ID`, `PubKey`, and `Sig`; `CheckSignature` verifies the Schnorr signature. ### Types - **Event** struct - **ID**: string - **PubKey**: string - **CreatedAt**: int64 - **Kind**: int - **Tags**: Tags - **Content**: string - **Sig**: string ### Methods - **(ev *Event) Sign**(privateKey: string) error - Signs the event with the provided private key and populates ID, PubKey, and Sig. - **(ev *Event) CheckSignature**() (bool, error) - Verifies the Schnorr signature of the event. - **(ev *Event) CheckID**() bool - Verifies the integrity of the event's ID. ### Example Usage ```go package main import ( "fmt" "github.com/nbd-wtf/go-nostr" ) func main() { sk := nostr.GeneratePrivateKey() pk, _ := nostr.GetPublicKey(sk) ev := nostr.Event{ PubKey: pk, CreatedAt: nostr.Now(), Kind: nostr.KindTextNote, Tags: nostr.Tags{{"t", "nostr"}}, Content: "Hello Nostr!", } if err := ev.Sign(sk); err != nil { panic(err) } fmt.Println("ID:", ev.ID) fmt.Println("Sig:", ev.Sig[:16], "...") ok, err := ev.CheckSignature() fmt.Println("signature valid:", ok, err) fmt.Println("ID valid:", ev.CheckID()) } ``` ``` -------------------------------- ### Generate Keys and Encode with Go Nostr Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Generates a private key, derives the public key, and encodes them using NIP-19. Use this for key management in Nostr applications. ```go package main import ( "fmt" "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip19" ) func main() { sk := nostr.GeneratePrivateKey() pk, err := nostr.GetPublicKey(sk) if err != nil { panic(err) } nsec, _ := nip19.EncodePrivateKey(sk) npub, _ := nip19.EncodePublicKey(pk) fmt.Println("private key (hex):", sk) fmt.Println("public key (hex):", pk) fmt.Println("nsec:", nsec) fmt.Println("npub:", npub) fmt.Println("valid pubkey:", nostr.IsValidPublicKey(pk)) // private key (hex): 3501...a7c2 // public key (hex): b3fa...11d0 // nsec: nsec1... // npub: npub1... // valid pubkey: true } ``` -------------------------------- ### Subscribe to a Single Relay Source: https://github.com/nbd-wtf/go-nostr/blob/master/README.md Connects to a specified relay and subscribes to text notes authored by a given public key, limiting the results to one event. Ensure the context is managed to avoid goroutine bloat. ```go ctx := context.Background() relay, err := nostr.RelayConnect(ctx, "wss://relay.stoner.com") if err != nil { panic(err) } npub := "npub1422a7ws4yul24p0pf7cacn7cghqkutdnm35z075vy68ggqpqjcyswn8ekc" var filters nostr.Filters if _, v, err := nip19.Decode(npub); err == nil { pub := v.(string) filters = []nostr.Filter{{ Kinds: []int{nostr.KindTextNote}, Authors: []string{pub}, Limit: 1, }} } else { panic(err) } ctx, cancel := context.WithTimeout(ctx, 3*time.Second) defuncel() // Use defer cancel() for proper context management sub, err := relay.Subscribe(ctx, filters) if err != nil { panic(err) } for ev := range sub.Events { // handle returned event. // channel will stay open until the ctx is cancelled (in this case, context timeout) fmt.Println(ev.ID) } ``` -------------------------------- ### NIP-44 Versioned Encrypted Messaging Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Shows how to encrypt and decrypt messages using the modern NIP-44 standard, which employs XChaCha20 and HMAC-SHA256. ```go package main import ( "fmt" "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip44" ) func main() { senderSk := nostr.GeneratePrivateKey() recipientSk := nostr.GeneratePrivateKey() recipientPk, _ := nostr.GetPublicKey(recipientSk) senderPk, _ := nostr.GetPublicKey(senderSk) // Derive conversation key ck, err := nip44.GenerateConversationKey(recipientPk, senderSk) if err != nil { panic(err) } // Encrypt ciphertext, err := nip44.Encrypt("Hello NIP-44!", ck) if err != nil { panic(err) } fmt.Println("ciphertext (b64):", ciphertext[:30], "...") // Decrypt from recipient's side ck2, _ := nip44.GenerateConversationKey(senderPk, recipientSk) plaintext, err := nip44.Decrypt(ciphertext, ck2) if err != nil { panic(err) } fmt.Println("plaintext:", plaintext) // Hello NIP-44! } ``` -------------------------------- ### NIP-04 Legacy Direct Message Encryption/Decryption Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Demonstrates encrypting and decrypting direct messages using the NIP-04 standard with AES-256-CBC. ```go package main import ( "fmt" "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip04" ) func main() { senderSk := nostr.GeneratePrivateKey() recipientSk := nostr.GeneratePrivateKey() recipientPk, _ := nostr.GetPublicKey(recipientSk) senderPk, _ := nostr.GetPublicKey(senderSk) // Sender encrypts sharedKey, err := nip04.ComputeSharedSecret(recipientPk, senderSk) if err != nil { panic(err) } ciphertext, err := nip04.Encrypt("Hello via NIP-04!", sharedKey) if err != nil { panic(err) } fmt.Println("ciphertext:", ciphertext[:20], "...") // Recipient decrypts sharedKey2, _ := nip04.ComputeSharedSecret(senderPk, recipientSk) plaintext, err := nip04.Decrypt(ciphertext, sharedKey2) if err != nil { panic(err) } fmt.Println("plaintext:", plaintext) // Hello via NIP-04! } ``` -------------------------------- ### Implement Gift Wrap for Sealed Private Messages (NIP-59) Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Implements sealed private messages by double-encrypting a "rumor" event. The sender gift-wraps the rumor using their key and a one-time random key addressed to the recipient. The recipient then unwraps the message using their private key and the sender's public key. ```go package main import ( "context" "fmt" "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip44" "github.com/nbd-wtf/go-nostr/nip59" "github.com/nbd-wtf/go-nostr/keyer" ) func main() { senderSk := nostr.GeneratePrivateKey() senderKs, _ := keyer.NewPlainKeySigner(senderSk) recipientSk := nostr.GeneratePrivateKey() recipientPk, _ := nostr.GetPublicKey(recipientSk) // The actual message (a "rumor" – unsigned event) rumor := nostr.Event{ Kind: nostr.KindDirectMessage, Content: "Top secret message", Tags: nostr.Tags{{"p", recipientPk}}, } ctx := context.Background() senderPk, _ := senderKs.GetPublicKey(ctx) rumor.PubKey = senderPk // Wrap the rumor gw, err := nip59.GiftWrap( rumor, recipientPk, func(plaintext string) (string, error) { return senderKs.Encrypt(ctx, plaintext, recipientPk) }, func(ev *nostr.Event) error { return senderKs.SignEvent(ctx, ev) }, nil, ) if err != nil { panic(err) } fmt.Println("gift wrap kind:", gw.Kind) // 1059 // Recipient unwraps recipientKs, _ := keyer.NewPlainKeySigner(recipientSk) unwrapped, err := nip59.GiftUnwrap(gw, func(senderPk, ciphertext string) (string, error) { ck, _ := nip44.GenerateConversationKey(senderPk, recipientSk) return nip44.Decrypt(ciphertext, ck) }) if err != nil { panic(err) } _ = recipientKs fmt.Println("unwrapped content:", unwrapped.Content) // Top secret message } ``` -------------------------------- ### Generate Nostr Keys Source: https://github.com/nbd-wtf/go-nostr/blob/master/README.md Generates a private key, public key, and their respective NIP-19 encoded strings (nsec and npub). ```go package main import ( "fmt" "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip19" ) func main() { sk := nostr.GeneratePrivateKey() pk, _ := nostr.GetPublicKey(sk) nsec, _ := nip19.EncodePrivateKey(sk) npub, _ := nip19.EncodePublicKey(pk) fmt.Println("sk:", sk) fmt.Println("pk:", pk) fmt.Println(nsec) fmt.Println(npub) } ``` -------------------------------- ### Resolve Nostr Internet Identifiers (NIP-05) Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Resolves user@domain.com style identifiers to Nostr public keys and relay hints by fetching /.well-known/nostr.json. Use IsValidIdentifier to check format and QueryIdentifier to perform network resolution. ```go package main import ( "context" "fmt" "time" "github.com/nbd-wtf/go-nostr/nip05" ) func main() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // Validate format fmt.Println(nip05.IsValidIdentifier("jack@cash.app")) // true fmt.Println(nip05.IsValidIdentifier("not-an-identifier")) // false // Resolve to public key + relay hints ptr, err := nip05.QueryIdentifier(ctx, "jack@cash.app") if err != nil { fmt.Println("error:", err) return } fmt.Println("pubkey:", ptr.PublicKey) fmt.Println("relays:", ptr.Relays) // Just parse without network request name, domain, err := nip05.ParseIdentifier("alice@example.com") fmt.Println(name, domain, err) // alice example.com } ``` -------------------------------- ### Key Generation Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Functions for generating and validating secp256k1 private and public keys. ```APIDOC ## Key Generation `GeneratePrivateKey` produces a cryptographically random secp256k1 private key as a lowercase hex string; `GetPublicKey` derives the corresponding Schnorr public key; `IsValidPublicKey` validates a hex public key against the curve. ### Functions - **GeneratePrivateKey**(): string - Generates a new private key. - **GetPublicKey**(privateKey: string): string - Derives the public key from a private key. - **IsValidPublicKey**(publicKey: string): boolean - Validates if a given string is a valid public key. ### Example Usage ```go package main import ( "fmt" "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip19" ) func main() { sk := nostr.GeneratePrivateKey() pk, err := nostr.GetPublicKey(sk) if err != nil { panic(err) } nsec, _ := nip19.EncodePrivateKey(sk) npub, _ := nip19.EncodePublicKey(pk) fmt.Println("private key (hex):", sk) fmt.Println("public key (hex):", pk) fmt.Println("nsec:", nsec) fmt.Println("npub:", npub) fmt.Println("valid pubkey:", nostr.IsValidPublicKey(pk)) } ``` ``` -------------------------------- ### Tags Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Utilities for working with tags within Nostr events. ```APIDOC ## Tags `Tags` is a slice of `Tag` (a `[]string`). Common helpers: `Tags.Find(key)` returns the first tag with that key; `Tags.FindWithValue(key, val)` returns the tag only if a specific value matches; `Tags.ContainsAny(key, values)` checks membership. ### Types - **Tag**: []string - **Tags**: []Tag ### Methods - **(t Tags) Find**(key: string): Tag | nil - Finds the first tag with the specified key. - **(t Tags) FindWithValue**(key: string, value: string): Tag | nil - Finds the first tag with the specified key and value. - **(t Tags) ContainsAny**(key: string, values: []string): bool - Checks if any of the provided values exist for the given tag key. ### Example Usage ```go package main import ( "fmt" "github.com/nbd-wtf/go-nostr" ) func main() { ev := nostr.Event{ Kind: nostr.KindTextNote, Content: "reply", Tags: nostr.Tags{ {"e", "abc123eventid", "wss://relay.example.com", "reply"}, {"p", "deadbeefpubkey"}, {"t", "nostr"}, }, } eTag := ev.Tags.Find("e") fmt.Println("e tag:", eTag) pTag := ev.Tags.Find("p") fmt.Println("p tag:", pTag) hasTopic := ev.Tags.ContainsAny("t", []string{"bitcoin", "nostr"}) fmt.Println("has topic:", hasTopic) } ``` ``` -------------------------------- ### Parse and Serialize Nostr Protocol Messages Source: https://context7.com/nbd-wtf/go-nostr/llms.txt Demonstrates parsing incoming JSON Nostr messages into typed envelopes and serializing outgoing REQ envelopes. ```go package main import ( "fmt" "github.com/nbd-wtf/go-nostr" ) func main() { // Parse an EVENT message eventMsg := `["EVENT","sub1",{"id":"abc","pubkey":"def","created_at":1700000000,"kind":1,"tags":[],"content":"hello","sig":"xyz"}]` env := nostr.ParseMessage(eventMsg) if env != nil { fmt.Println("label:", env.Label()) // EVENT if evEnv, ok := env.(*nostr.EventEnvelope); ok { fmt.Println("sub id:", *evEnv.SubscriptionID) // sub1 fmt.Println("content:", evEnv.Content) // hello } } // Parse an OK message okMsg := `["OK","abc123eventid",true,""]` okEnv := nostr.ParseMessage(okMsg).(*nostr.OKEnvelope) fmt.Println("OK:", okEnv.OK, "eventID:", okEnv.EventID[:8]) // Serialize a REQ envelope req := nostr.ReqEnvelope{ SubscriptionID: "my-sub", Filters: nostr.Filters{{Kinds: []int{1}, Limit: 10}}, } b, _ := req.MarshalJSON() fmt.Println("REQ:", string(b)) // ["REQ","my-sub",{"kinds":[1],"limit":10}] } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.