A comprehensive, high-performance Go implementation of the Nostr protocol providing encoding/decoding, cryptography, WebSocket client, and relay utilities. This library offers a complete toolkit for building Nostr clients and relays with optimized performance through SIMD acceleration and zero-copy operations.
ebitengine/purego to dynamically load libsecp256k1.sominio/sha256-simd) and hex encoding (templexxx/xhex)go get git.mleku.dev/mleku/nostr
package main
import (
"context"
"fmt"
"git.mleku.dev/mleku/nostr/encoders/filter"
"git.mleku.dev/mleku/nostr/encoders/kind"
"git.mleku.dev/mleku/nostr/ws"
)
func main() {
ctx := context.Background()
// Connect to relay
relay, err := ws.RelayConnect(ctx, "wss://relay.damus.io")
if err != nil {
panic(err)
}
defer relay.Close()
// Create filter
f := filter.New()
f.Kinds = kind.NewS(kind.TextNote)
limit := 10
f.Limit = &limit
// Subscribe to events
sub, err := relay.Subscribe(ctx, filter.NewS(f))
if err != nil {
panic(err)
}
// Process events
for event := range sub.Events {
fmt.Printf("Event: %s\n", string(event.Content))
}
}
The ws package provides a complete WebSocket client implementation for connecting to Nostr relays.
package main
import (
"context"
"git.mleku.dev/mleku/nostr/ws"
)
func main() {
ctx := context.Background()
// Connect to relay
relay, err := ws.RelayConnect(ctx, "wss://relay.damus.io")
if err != nil {
panic(err)
}
defer relay.Close()
// Relay is now ready to use
}
package main
import (
"context"
"fmt"
"git.mleku.dev/mleku/nostr/encoders/filter"
"git.mleku.dev/mleku/nostr/encoders/kind"
"git.mleku.dev/mleku/nostr/ws"
)
func main() {
ctx := context.Background()
relay, _ := ws.RelayConnect(ctx, "wss://relay.damus.io")
defer relay.Close()
// Create filters
f := filter.New()
f.Kinds = kind.NewS(kind.TextNote, kind.Reaction)
limit := 100
f.Limit = &limit
// Subscribe with options
sub, err := relay.Subscribe(ctx, filter.NewS(f),
ws.WithLabel("my-subscription"),
)
if err != nil {
panic(err)
}
// Process events as they arrive
for event := range sub.Events {
fmt.Printf("Event %x: %s\n", event.ID, string(event.Content))
}
// Check if EOSE was received
if sub.EndOfStoredEvents.Load() {
fmt.Println("Received all stored events")
}
}
package main
import (
"context"
"fmt"
"time"
"git.mleku.dev/mleku/nostr/encoders/event"
"git.mleku.dev/mleku/nostr/encoders/kind"
"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"
"git.mleku.dev/mleku/nostr/ws"
)
func main() {
ctx := context.Background()
relay, _ := ws.RelayConnect(ctx, "wss://relay.damus.io")
defer relay.Close()
// Create signer
signer, _ := p8k.New()
signer.Generate()
// Create event
ev := event.New()
ev.Kind = kind.TextNote.K
ev.CreatedAt = time.Now().Unix()
ev.Content = []byte("Hello Nostr!")
ev.Sign(signer)
// Publish event
err := relay.Publish(ctx, ev)
if err != nil {
panic(err)
}
fmt.Printf("Published event: %x\n", ev.ID)
}
package main
import (
"context"
"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"
"git.mleku.dev/mleku/nostr/ws"
)
func main() {
ctx := context.Background()
relay, _ := ws.RelayConnect(ctx, "wss://relay.example.com")
defer relay.Close()
// Create signer
signer, _ := p8k.New()
signer.Generate()
// Authenticate with relay
err := relay.Auth(ctx, signer)
if err != nil {
panic(err)
}
}
// WithLabel sets the subscription label
sub, _ := relay.Subscribe(ctx, filters, ws.WithLabel("my-sub"))
// Subscription automatically closes when context is cancelled
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
sub, _ := relay.Subscribe(ctx, filters)
The event package provides the core Nostr event type with JSON, binary, and canonical encodings.
type E struct {
ID []byte // SHA256 hash of canonical encoding
Pubkey []byte // Public key (32 bytes)
CreatedAt int64 // UNIX timestamp
Kind uint16 // Event kind
Tags *tag.S // Tag list
Content []byte // Arbitrary content
Sig []byte // Schnorr signature (64 bytes)
}
type S []*E // Slice of events (sortable by CreatedAt)
type C chan *E // Channel for event streaming
func New() *E
func (ev *E) Free() // Nil all fields for GC
func (ev *E) Clone() *E // Deep copy with independent memory
func (ev *E) EstimateSize() int // Estimate serialized size
func (ev *E) Marshal(dst []byte) []byte // Marshal to JSON
func (ev *E) MarshalJSON() ([]byte, error) // Standard JSON marshaler
func (ev *E) Serialize() []byte // Marshal to new buffer
func (ev *E) Unmarshal(b []byte) ([]byte, error) // Unmarshal from JSON
func (ev *E) UnmarshalJSON(b []byte) error // Standard JSON unmarshaler
func (ev *E) MarshalBinary(w io.Writer) // Write binary format
func (ev *E) MarshalBinaryToBytes(dst []byte) []byte // Binary to bytes
func (ev *E) UnmarshalBinary(r io.Reader) error // Read binary format
func (ev *E) ToCanonical(dst []byte) []byte // Canonical encoding for ID
func (ev *E) GetIDBytes() []byte // Compute event ID
func Hash(in []byte) []byte // SHA256 hash
func (ev *E) Sign(keys signer.I) error // Sign event with key pair
func (ev *E) Verify() (bool, error) // Verify signature
package main
import (
"fmt"
"time"
"git.mleku.dev/mleku/nostr/encoders/event"
"git.mleku.dev/mleku/nostr/encoders/kind"
"git.mleku.dev/mleku/nostr/encoders/tag"
"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"
)
func main() {
// Create a signer
signer, _ := p8k.New()
signer.Generate()
// Create an event
ev := event.New()
ev.Kind = kind.TextNote.K
ev.CreatedAt = time.Now().Unix()
ev.Content = []byte("Hello Nostr!")
// Add tags
ev.Tags = tag.NewS(
tag.NewFromBytesSlice([]byte("t"), []byte("nostr")),
tag.NewFromBytesSlice([]byte("p"), signer.Pub()),
)
// Sign the event
if err := ev.Sign(signer); err != nil {
panic(err)
}
// Verify signature
valid, err := ev.Verify()
if err != nil || !valid {
panic("invalid signature")
}
// Marshal to JSON
jsonBytes := ev.Serialize()
fmt.Printf("Event JSON: %s\n", jsonBytes)
// Unmarshal from JSON
ev2 := event.New()
_, err = ev2.Unmarshal(jsonBytes)
if err != nil {
panic(err)
}
// Clone for async processing
clone := ev.Clone()
go processEvent(clone)
// Free original
ev.Free()
}
func processEvent(ev *event.E) {
defer ev.Free()
// Process event...
}
The filter package implements Nostr subscription filters for querying events.
type F struct {
IDs *schnorr.Bytes // Event IDs (hex-encoded in JSON)
Authors *schnorr.Bytes // Author pubkeys (hex-encoded in JSON)
Kinds *kind.S // Event kinds
Tags *tag.S // Tag filters (#e, #p, etc.)
Since *int64 // UNIX timestamp
Until *int64 // UNIX timestamp
Limit *int // Max results
}
func New() *F
func (f *F) Sort() // Sort fields for deterministic output
func (f *F) Matches(ev *event.E) bool // Check if event matches filter
func (f *F) MatchesIgnoringTimestampConstraints(ev *event.E) bool
func (f *F) EstimateSize() int // Estimate serialized size
func (f *F) Marshal(dst []byte) []byte // Marshal to JSON
func (f *F) Serialize() []byte // Marshal to new buffer
func (f *F) Unmarshal(b []byte) ([]byte, error) // Unmarshal from JSON
package main
import (
"fmt"
"git.mleku.dev/mleku/nostr/crypto/ec/schnorr"
"git.mleku.dev/mleku/nostr/encoders/event"
"git.mleku.dev/mleku/nostr/encoders/filter"
"git.mleku.dev/mleku/nostr/encoders/kind"
"git.mleku.dev/mleku/nostr/encoders/tag"
)
func main() {
// Create a filter
f := filter.New()
// Filter by kinds
f.Kinds = kind.NewS(kind.TextNote, kind.EncryptedDirectMessage)
// Filter by authors
pubkey, _ := schnorr.ParseBytes("...")
f.Authors = schnorr.NewBytes(pubkey)
// Time constraints
since := int64(1234567890)
until := int64(9999999999)
limit := 100
f.Since = &since
f.Until = &until
f.Limit = &limit
// Tag filters (e.g., #p tag)
f.Tags = tag.NewS(
tag.NewFromBytesSlice([]byte("#p"), pubkey),
)
// Sort for deterministic output
f.Sort()
// Marshal to JSON
jsonBytes := f.Serialize()
fmt.Printf("Filter: %s\n", jsonBytes)
// Check if event matches
ev := event.New()
ev.Kind = kind.TextNote.K
if f.Matches(ev) {
fmt.Println("Event matches filter")
}
}
The tag package provides tag encoding and manipulation.
type T struct {
T [][]byte // Tag elements (first is key, rest are values)
}
type S []*T // Slice of tags
func New() *T
func NewWithCap(c int) *T
func NewFromBytesSlice(t ...[]byte) *T
func NewFromAny(t ...any) *T
func NewS(t ...*T) *S
func NewSWithCap(c int) *S
func (t *T) Free() // Nil all fields
func (t *T) Len() int // Number of elements
func (t *T) Less(i, j int) bool // Compare elements
func (t *T) Swap(i, j int) // Swap elements
func (t *T) Contains(s []byte) bool // Check if contains element
func (t *T) Marshal(dst []byte) []byte // Marshal to JSON
func (t *T) MarshalJSON() ([]byte, error) // Standard marshaler
func (t *T) Unmarshal(b []byte) ([]byte, error) // Unmarshal from JSON
func (t *T) UnmarshalJSON(b []byte) error // Standard unmarshaler
func (t *T) Key() []byte // Get first element (key)
func (t *T) Value() []byte // Get second element (value)
func (t *T) Relay() []byte // Get third element (relay)
func (t *T) ValueHex() []byte // Get value as hex
func (t *T) ValueBinary() []byte // Get value as binary
func (t *T) ToSliceOfStrings() []string // Convert to string slice
func (t *T) Equals(other *T) bool // Compare tags
func (s *S) Len() int
func (s *S) Less(i, j int) bool
func (s *S) Swap(i, j int)
func (s *S) Append(t ...*T)
func (s *S) Marshal(dst []byte) []byte
func (s *S) Unmarshal(b []byte) ([]byte, error)
func (s *S) GetFirst(tagName []byte) *T // Get first tag with key
func (s *S) GetAll(tagName []byte) []*T // Get all tags with key
func (s *S) FilterOut(tagName []byte) *S // Remove tags with key
package main
import (
"fmt"
"git.mleku.dev/mleku/nostr/encoders/hex"
"git.mleku.dev/mleku/nostr/encoders/tag"
)
func main() {
// Create individual tags
eTag := tag.NewFromBytesSlice(
[]byte("e"),
hex.Dec("...event-id..."),
[]byte("wss://relay.example.com"),
[]byte("reply"),
)
pTag := tag.NewFromBytesSlice(
[]byte("p"),
hex.Dec("...pubkey..."),
)
tTag := tag.NewFromAny("t", "nostr", "protocol")
// Create tag collection
tags := tag.NewS(eTag, pTag, tTag)
// Access tag elements
fmt.Printf("Key: %s\n", eTag.Key()) // "e"
fmt.Printf("Value: %s\n", eTag.ValueHex()) // event-id as hex (handles binary storage)
fmt.Printf("Relay: %s\n", eTag.Relay()) // relay URL
// Find tags - use ValueHex() for e/p tags (may be binary-encoded internally)
pTags := tags.GetAll([]byte("p"))
for _, pt := range pTags {
fmt.Printf("P tag: %s\n", pt.ValueHex()) // Always returns hex regardless of storage
}
// Filter tags
withoutE := tags.FilterOut([]byte("e"))
// Marshal to JSON
jsonBytes := tags.Marshal(nil)
fmt.Printf("Tags: %s\n", jsonBytes)
}
The kind package provides event kind constants and utilities.
type K struct {
K uint16 // Kind number
}
type S struct {
K []*K // Slice of kinds (sortable)
}
func New(k uint16) *K
func NewS(k ...*K) *S
func NewWithCap(c int) *S
func FromIntSlice(is []int) *S
var (
Metadata = New(0) // NIP-01
TextNote = New(1) // NIP-01
RecommendRelay = New(2) // NIP-01
Contacts = New(3) // NIP-02
EncryptedDirectMessage = New(4) // NIP-04
EventDeletion = New(5) // NIP-09
Repost = New(6) // NIP-18
Reaction = New(7) // NIP-25
BadgeAward = New(8) // NIP-58
ChannelCreation = New(40) // NIP-28
ChannelMetadata = New(41) // NIP-28
ChannelMessage = New(42) // NIP-28
ChannelHideMessage = New(43) // NIP-28
ChannelMuteUser = New(44) // NIP-28
FileMetadata = New(1063) // NIP-94
LiveChatMessage = New(1311) // NIP-53
// Replaceable events (10000-19999)
ProfileBadges = New(30008) // NIP-58
BadgeDefinition = New(30009) // NIP-58
// Ephemeral events (20000-29999)
Auth = New(22242) // NIP-42
// ... and 200+ more kinds
)
func (k *S) Len() int
func (k *S) Less(i, j int) bool
func (k *S) Swap(i, j int)
func (k *S) ToUint16() []uint16
func (k *S) Clone() *S
func (k *S) Contains(s uint16) bool
func (k *S) Equals(t1 *S) bool
func (k *S) Marshal(dst []byte) []byte
func (k *S) Unmarshal(b []byte) ([]byte, error)
func (k *S) IsPrivileged() bool // Check if contains privileged kinds
func (k *K) IsRegular() bool // 1000 <= k < 10000
func (k *K) IsReplaceable() bool // 10000 <= k < 20000 or k in [0,3]
func (k *K) IsEphemeral() bool // 20000 <= k < 30000
func (k *K) IsAddressable() bool // 30000 <= k < 40000
package main
import (
"fmt"
"git.mleku.dev/mleku/nostr/encoders/kind"
)
func main() {
// Use predefined constants
textNote := kind.TextNote
fmt.Printf("Text note kind: %d\n", textNote.K)
// Create custom kind
customKind := kind.New(30023)
// Check kind properties
if customKind.IsAddressable() {
fmt.Println("This is an addressable event")
}
// Create kind filter
kinds := kind.NewS(
kind.TextNote,
kind.Reaction,
kind.Repost,
)
// Check if contains kind
if kinds.Contains(1) {
fmt.Println("Contains text notes")
}
// Convert to uint16 slice
kindNumbers := kinds.ToUint16()
fmt.Printf("Kinds: %v\n", kindNumbers)
}
Envelope packages provide WebSocket message framing for the Nostr protocol.
eventenvelope - EVENT messages (client to relay)reqenvelope - REQ messages (subscription requests)closeenvelope - CLOSE messages (close subscription)eoseenvelope - EOSE messages (end of stored events)okenvelope - OK messages (command results)noticeenvelope - NOTICE messages (human-readable messages)authenvelope - AUTH messages (NIP-42 authentication)closedenvelope - CLOSED messages (subscription closed by relay)countenvelope - COUNT messages (event counting)All envelopes follow a similar pattern:
// Create envelope
env := eventenvelope.NewSubmission()
// Unmarshal from wire format
remainder, err := env.Unmarshal(wireBytes)
// Access data
event := env.E
// Marshal to wire format
wireBytes = env.Marshal(nil)
package main
import (
"fmt"
"git.mleku.dev/mleku/nostr/encoders/envelopes/eventenvelope"
"git.mleku.dev/mleku/nostr/encoders/event"
)
func main() {
// Client submitting event
ev := event.New()
// ... populate event ...
submission := eventenvelope.NewSubmission()
submission.E = ev
wireBytes := submission.Marshal(nil)
// Send wireBytes over WebSocket
// Server receiving event
received := eventenvelope.NewSubmission()
_, err := received.Unmarshal(wireBytes)
if err != nil {
panic(err)
}
// Process received.E
fmt.Printf("Received event: %x\n", received.E.ID)
}
package main
import (
"git.mleku.dev/mleku/nostr/encoders/envelopes/reqenvelope"
"git.mleku.dev/mleku/nostr/encoders/filter"
)
func main() {
// Create subscription request
req := reqenvelope.New()
req.Label = []byte("my-subscription")
// Add filters
f1 := filter.New()
// ... configure filter ...
req.Filters = append(req.Filters, f1)
// Marshal
wireBytes := req.Marshal(nil)
// Send over WebSocket
}
package main
import (
"git.mleku.dev/mleku/nostr/encoders/envelopes/okenvelope"
"git.mleku.dev/mleku/nostr/encoders/reason"
)
func main() {
// Create OK response
ok := okenvelope.New()
ok.ID = eventID
ok.OK = true
ok.Reason = reason.Duplicate.With(": event already exists")
// Marshal
wireBytes := ok.Marshal(nil)
// Send to client
}
The relayinfo package implements NIP-11 relay information document handling.
type T struct {
Name []byte
Description []byte
Pubkey []byte
Contact []byte
Nips []int
Software []byte
Version []byte
Limitation *Limitation
Payments_url []byte
Fees *Fees
Icon []byte
// ... additional fields
}
type Limitation struct {
MaxMessageLength *int
MaxSubscriptions *int
MaxFilters *int
MaxLimit *int
MaxSubidLength *int
MaxEventTags *int
MaxContentLength *int
MinPowDifficulty *int
AuthRequired *bool
PaymentRequired *bool
RestrictedWrites *bool
CreatedAtLowerLimit *int64
CreatedAtUpperLimit *int64
}
func Fetch(ctx context.Context, u []byte) (*T, error) // Fetch from relay
func Load(filePath []byte) (*T, error) // Load from file
func (t *T) Save(filePath []byte) error // Save to file
func (t *T) Marshal(dst []byte) []byte // Marshal to JSON
func (t *T) Unmarshal(b []byte) ([]byte, error) // Unmarshal from JSON
package main
import (
"context"
"fmt"
"git.mleku.dev/mleku/nostr/relayinfo"
)
func main() {
ctx := context.Background()
// Fetch relay information
info, err := relayinfo.Fetch(ctx, []byte("wss://relay.damus.io"))
if err != nil {
panic(err)
}
// Display relay info
fmt.Printf("Relay: %s\n", info.Name)
fmt.Printf("Description: %s\n", info.Description)
fmt.Printf("NIPs supported: %v\n", info.Nips)
fmt.Printf("Software: %s %s\n", info.Software, info.Version)
// Check limitations
if info.Limitation != nil {
if info.Limitation.MaxMessageLength != nil {
fmt.Printf("Max message length: %d\n", *info.Limitation.MaxMessageLength)
}
if info.Limitation.AuthRequired != nil && *info.Limitation.AuthRequired {
fmt.Println("Authentication required")
}
}
// Save to file
err = info.Save([]byte("relay-info.json"))
if err != nil {
panic(err)
}
// Load from file
loaded, err := relayinfo.Load([]byte("relay-info.json"))
if err != nil {
panic(err)
}
}
The httpauth package implements NIP-98 HTTP authentication for REST APIs.
func AddNIP98Header(req *http.Request, u *url.URL, method, payload string,
signer signer.I, expiration int64) error
func CreateAuthEvent(u *url.URL, method, payload string,
signer signer.I, expiration int64) (*event.E, error)
package main
import (
"net/http"
"net/url"
"time"
"git.mleku.dev/mleku/nostr/httpauth"
"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"
)
func main() {
// Create signer
signer, _ := p8k.New()
signer.Generate()
// Create HTTP request
u, _ := url.Parse("https://api.example.com/upload")
req, _ := http.NewRequest("POST", u.String(), nil)
// Add NIP-98 auth header (expires in 1 hour)
expiration := time.Now().Add(1 * time.Hour).Unix()
err := httpauth.AddNIP98Header(req, u, "POST", "", signer, expiration)
if err != nil {
panic(err)
}
// Make request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
}
The signer.I interface abstracts key management and cryptographic operations.
type I interface {
Generate() error // Generate new key pair
InitSec(sec []byte) error // Load secret key
InitPub(pub []byte) error // Load public key
Sec() []byte // Get secret key
Pub() []byte // Get public key (x-only)
Sign(msg []byte) ([]byte, error) // Sign message
Verify(msg, sig []byte) (bool, error) // Verify signature
Zero() // Wipe secret key
ECDH(pub []byte) ([]byte, error) // Derive shared secret
ECDHRaw(pub []byte) ([]byte, error) // Raw ECDH (for NIP-44)
}
The p8k package provides implementations using both libsecp256k1 (via purego) and pure Go fallback.
package main
import (
"fmt"
"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"
)
func main() {
// Create new signer
signer, err := p8k.New()
if err != nil {
panic(err)
}
// Generate random key pair
if err := signer.Generate(); err != nil {
panic(err)
}
fmt.Printf("Public key: %x\n", signer.Pub())
fmt.Printf("Secret key: %x\n", signer.Sec())
// Sign message
msg := []byte("hello")
sig, err := signer.Sign(msg)
if err != nil {
panic(err)
}
// Verify signature
valid, err := signer.Verify(msg, sig)
if err != nil || !valid {
panic("invalid signature")
}
// ECDH for encryption
recipientPub := []byte{/* 32 bytes */}
sharedSecret, err := signer.ECDH(recipientPub)
if err != nil {
panic(err)
}
fmt.Printf("Shared secret: %x\n", sharedSecret)
// Wipe keys when done
defer signer.Zero()
}
package main
import (
"git.mleku.dev/mleku/nostr/encoders/hex"
"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"
)
func main() {
signer, _ := p8k.New()
// Load from hex secret key
secHex := "..."
secBytes := hex.Dec(secHex)
if err := signer.InitSec(secBytes); err != nil {
panic(err)
}
// Public key is automatically derived
fmt.Printf("Loaded pubkey: %x\n", signer.Pub())
}
The schnorr package provides low-level signature operations.
package main
import (
"git.mleku.dev/mleku/nostr/crypto/ec/schnorr"
)
func main() {
// Parse public key
pubBytes, err := schnorr.ParseBytes("hex-pubkey")
if err != nil {
panic(err)
}
// Verify signature
msg := []byte("message hash")
sigBytes := []byte{/* 64 bytes */}
if !schnorr.Verify(pubBytes, msg, sigBytes) {
panic("invalid signature")
}
}
package main
import (
"git.mleku.dev/mleku/nostr/crypto/encryption"
"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"
)
func main() {
// Sender keys
sender, _ := p8k.New()
sender.Generate()
// Recipient keys
recipient, _ := p8k.New()
recipient.Generate()
plaintext := "Secret message"
// NIP-44 encryption (recommended)
ciphertext, err := encryption.Nip44Encrypt(
plaintext,
sender,
recipient.Pub(),
)
if err != nil {
panic(err)
}
// NIP-44 decryption
decrypted, err := encryption.Nip44Decrypt(
ciphertext,
recipient,
sender.Pub(),
)
if err != nil {
panic(err)
}
// NIP-04 encryption (legacy)
ciphertext04, err := encryption.Nip04Encrypt(
plaintext,
sender,
recipient.Pub(),
)
if err != nil {
panic(err)
}
// NIP-04 decryption
decrypted04, err := encryption.Nip04Decrypt(
ciphertext04,
recipient,
sender.Pub(),
)
}
The bech32encoding package provides npub/nsec/note encoding.
package main
import (
"fmt"
"git.mleku.dev/mleku/nostr/encoders/bech32encoding"
"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"
)
func main() {
signer, _ := p8k.New()
signer.Generate()
// Encode public key as npub
npub := bech32encoding.PubkeyToNpub(signer.Pub())
fmt.Printf("npub: %s\n", npub)
// Encode secret key as nsec
nsec := bech32encoding.SecToNsec(signer.Sec())
fmt.Printf("nsec: %s\n", nsec)
// Decode npub
pubBytes, err := bech32encoding.NpubToPubkey(npub)
if err != nil {
panic(err)
}
// Decode nsec
secBytes, err := bech32encoding.NsecToSec(nsec)
if err != nil {
panic(err)
}
// Encode event ID as note
eventID := []byte{/* 32 bytes */}
note := bech32encoding.EventIDToNote(eventID)
fmt.Printf("note: %s\n", note)
// Decode note
decodedID, err := bech32encoding.NoteToEventID(note)
if err != nil {
panic(err)
}
}
The protocol/auth package provides utilities for NIP-42 authentication.
package main
import (
"time"
"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"
"git.mleku.dev/mleku/nostr/protocol/auth"
)
func main() {
signer, _ := p8k.New()
signer.Generate()
// Create auth event
challenge := []byte("challenge-from-relay")
relayURL := []byte("wss://relay.example.com")
authEvent, err := auth.CreateAuthEvent(challenge, relayURL, signer)
if err != nil {
panic(err)
}
// Validate auth event
valid, err := auth.ValidateAuthEvent(authEvent, challenge, relayURL, time.Minute)
if err != nil || !valid {
panic("invalid auth event")
}
}
The bufpool package provides efficient buffer pooling for zero-copy operations.
package main
import (
"git.mleku.dev/mleku/nostr/utils/bufpool"
)
func main() {
// Get buffer from pool
buf := bufpool.Get()
defer bufpool.Put(buf)
// Use buffer
buf = append(buf, []byte("data")...)
// Buffer is returned to pool on Put
}
The normalize package provides URL and message normalization utilities.
package main
import (
"git.mleku.dev/mleku/nostr/utils/normalize"
)
func main() {
// Normalize WebSocket URL
normalized := normalize.URL([]byte("relay.damus.io"))
// Returns: "wss://relay.damus.io"
// HTTP to WebSocket conversion
wsURL := normalize.URL([]byte("https://relay.example.com"))
// Returns: "wss://relay.example.com"
// Port handling
withPort := normalize.URL([]byte("relay.example.com:443"))
// Returns: "wss://relay.example.com"
}
The hex package provides SIMD-accelerated hex encoding.
package main
import (
"git.mleku.dev/mleku/nostr/encoders/hex"
)
func main() {
// Encode to hex
data := []byte{0x01, 0x02, 0x03}
hexStr := hex.Enc(data)
// Append to buffer
buf := make([]byte, 0, 64)
buf = hex.EncAppend(buf, data)
// Decode from hex
decoded := hex.Dec("010203")
}
The text package provides JSON escaping and text processing.
package main
import (
"git.mleku.dev/mleku/nostr/encoders/text"
)
func main() {
// Escape for JSON
raw := []byte(`Hello "world"`)
escaped := text.EscapeJSONString(nil, raw)
// Unescape from JSON
unescaped := text.UnescapeJSONString(nil, escaped)
// Check if needs escaping
if text.NeedsEscape(raw) {
// Handle escaping
}
}
minio/sha256-simd for hardware-accelerated hashingtemplexxx/xhex for SIMD hex encodingbufpool packageMarshal(dst []byte) methods append to existing buffers// Efficient encoding pattern
buf := bufpool.Get()
defer bufpool.Put(buf)
buf = event.Marshal(buf) // Append to buffer
buf = filter.Marshal(buf) // Append more
For maximum performance, use binary encoding instead of JSON:
// Binary encoding (more compact, faster)
var buf bytes.Buffer
ev.MarshalBinary(&buf)
// Or to bytes
binBytes := ev.MarshalBinaryToBytes(nil)
Run tests with:
go test ./...
Run benchmarks:
go test -bench=. -benchmem ./encoders/event
go test -bench=. -benchmem ./encoders/filter
github.com/minio/sha256-simd - SIMD-accelerated SHA256github.com/templexxx/xhex - SIMD hex encodinggithub.com/ebitengine/purego - CGO-free library loadinggithub.com/gorilla/websocket - WebSocket implementationlol.mleku.dev - Logging and error handlinggolang.org/x/crypto - Cryptographic primitivesgolang.org/x/exp - Experimental packages (constraints)lukechampine.com/frand - Fast random number generationgithub.com/puzpuzpuz/xsync/v3 - Concurrent data structureslibsecp256k1.so is embedded in the p8k package for Linuxgit.mleku.dev/mleku/nostr/
├── crypto/ # Cryptographic operations
│ ├── ec/ # Elliptic curve crypto
│ │ ├── schnorr/ # Schnorr signatures
│ │ ├── secp256k1/ # secp256k1 curve
│ │ ├── bech32/ # Bech32 encoding
│ │ ├── musig2/ # MuSig2 multi-sig
│ │ ├── base58/ # Base58 encoding
│ │ ├── ecdsa/ # ECDSA signatures
│ │ ├── taproot/ # Taproot utilities
│ │ └── ...
│ ├── encryption/ # NIP-04/NIP-44
│ ├── keys/ # Key generation and conversion
│ └── p8k/ # Purego secp256k1
├── encoders/ # Nostr protocol encoders
│ ├── event/ # Event encoding
│ ├── filter/ # Filter encoding
│ ├── tag/ # Tag encoding
│ ├── kind/ # Kind constants
│ ├── envelopes/ # WebSocket envelopes
│ ├── hex/ # Hex encoding
│ ├── text/ # Text utilities
│ ├── bech32encoding/ # Bech32 encoding
│ ├── ints/ # Integer encoding
│ ├── reason/ # Reason codes
│ ├── timestamp/ # Timestamp handling
│ └── varint/ # Varint encoding
├── httpauth/ # NIP-98 HTTP auth
├── interfaces/ # Abstract interfaces
│ ├── signer/ # Signer interface
│ │ └── p8k/ # P8K implementation
│ └── codec/ # Codec interfaces
├── protocol/ # Protocol helpers
│ └── auth/ # NIP-42 auth
├── relayinfo/ # NIP-11 relay info
├── utils/ # Utility packages
│ ├── bufpool/ # Buffer pooling
│ ├── normalize/ # URL normalization
│ ├── constraints/ # Type constraints
│ ├── number/ # Number utilities
│ ├── pointers/ # Pointer utilities
│ ├── units/ # Size units
│ └── values/ # Value utilities
└── ws/ # WebSocket client
Marshal methods accept dst []byte to append to existing buffersbufpool.Get/Put for temporary bufferspackage main
import (
"context"
"fmt"
"time"
"git.mleku.dev/mleku/nostr/encoders/event"
"git.mleku.dev/mleku/nostr/encoders/filter"
"git.mleku.dev/mleku/nostr/encoders/kind"
"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"
"git.mleku.dev/mleku/nostr/ws"
)
func main() {
ctx := context.Background()
// Initialize keys
signer, _ := p8k.New()
signer.Generate()
// Connect to relay
relay, err := ws.RelayConnect(ctx, "wss://relay.damus.io")
if err != nil {
panic(err)
}
defer relay.Close()
// Create and publish event
ev := event.New()
ev.Kind = kind.TextNote.K
ev.CreatedAt = time.Now().Unix()
ev.Content = []byte("Hello Nostr from mleku/nostr!")
ev.Sign(signer)
// Publish event
err = relay.Publish(ctx, ev)
if err != nil {
panic(err)
}
fmt.Printf("Published event: %x\n", ev.ID)
// Subscribe to events
f := filter.New()
f.Kinds = kind.NewS(kind.TextNote)
limit := 10
f.Limit = &limit
sub, err := relay.Subscribe(ctx, filter.NewS(f))
if err != nil {
panic(err)
}
// Process events
for event := range sub.Events {
fmt.Printf("Event: %s\n", string(event.Content))
}
}
Contributions are welcome! Please ensure:
go test ./...go fmt ./...[Insert license information]
This library is extracted from the ORLY relay implementation, designed for high-performance Nostr protocol handling.
libsecp256k1 via purego