Nostr Codec

A high-performance, pure Go implementation of Nostr protocol encoding/decoding, cryptography, and utilities. This library provides a complete toolkit for building Nostr clients and relays with optimized performance through SIMD acceleration and zero-copy operations.

Features

  • Pure Go with Purego - No CGO dependencies, uses ebitengine/purego to dynamically load libsecp256k1.so
  • SIMD Optimization - Accelerated SHA256 (minio/sha256-simd) and hex encoding (templexxx/xhex)
  • Zero-Copy Operations - Efficient buffer pooling and reuse
  • Complete NIP Support - Events, filters, tags, envelopes, and cryptographic operations
  • Multiple Encodings - JSON, binary, and canonical encodings for events
  • Comprehensive Crypto - Schnorr signatures, ECDH, NIP-04/NIP-44 encryption

Installation

go get git.mleku.dev/mleku/nostr-codec

Table of Contents


Event Package

The event package provides the core Nostr event type with JSON, binary, and canonical encodings.

Types

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

Constructor

func New() *E

Methods

Lifecycle

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

Encoding/Decoding

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

Binary Encoding

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

Canonical Encoding

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

Signatures

func (ev *E) Sign(keys signer.I) error           // Sign event with key pair
func (ev *E) Verify() (bool, error)              // Verify signature

Usage Example

package main

import (
    "fmt"
    "time"

    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/event"
    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/kind"
    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/tag"
    "git.mleku.dev/mleku/nostr-codec/pkg/interfaces/signer/p8k"
)

func main() {
    // Create a signer
    keys := p8k.MustNew()
    keys.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"), keys.Pub()),
    )

    // Sign the event
    if err := ev.Sign(keys); 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...
}

Filter Package

The filter package implements Nostr subscription filters for querying events.

Type

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
}

Constructor

func New() *F

Methods

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

Usage Example

package main

import (
    "fmt"

    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/filter"
    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/kind"
    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/event"
    "git.mleku.dev/mleku/nostr-codec/pkg/crypto/ec/schnorr"
)

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")
    }
}

Tag Package

The tag package provides tag encoding and manipulation.

Types

type T struct {
    T [][]byte  // Tag elements (first is key, rest are values)
}

type S []*T  // Slice of tags

Constructors

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

Tag Methods

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

Tag Slice Methods

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

Usage Example

package main

import (
    "fmt"

    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/tag"
    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/hex"
)

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: %x\n", eTag.Value())   // event-id bytes
    fmt.Printf("Relay: %s\n", eTag.Relay())   // relay URL

    // Find tags
    pTags := tags.GetAll([]byte("p"))
    for _, pt := range pTags {
        fmt.Printf("P tag: %x\n", pt.Value())
    }

    // Filter tags
    withoutE := tags.FilterOut([]byte("e"))

    // Marshal to JSON
    jsonBytes := tags.Marshal(nil)
    fmt.Printf("Tags: %s\n", jsonBytes)
}

Kind Package

The kind package provides event kind constants and utilities.

Type

type K struct {
    K uint16  // Kind number
}

type S struct {
    K []*K  // Slice of kinds (sortable)
}

Constructors

func New(k uint16) *K
func NewS(k ...*K) *S
func NewWithCap(c int) *S
func FromIntSlice(is []int) *S

Constants

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 many more
)

Methods

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

Usage Example

package main

import (
    "fmt"

    "git.mleku.dev/mleku/nostr-codec/pkg/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

Envelope packages provide WebSocket message framing for the Nostr protocol.

Available Envelopes

  • 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)

Common Pattern

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)

Event Envelope Example

package main

import (
    "fmt"

    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/envelopes/eventenvelope"
    "git.mleku.dev/mleku/nostr-codec/pkg/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)
}

REQ Envelope Example

package main

import (
    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/envelopes/reqenvelope"
    "git.mleku.dev/mleku/nostr-codec/pkg/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
}

OK Envelope Example

package main

import (
    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/envelopes/okenvelope"
    "git.mleku.dev/mleku/nostr-codec/pkg/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
}

Cryptography

Signer Interface

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)
}

P8K Implementation

The p8k package provides a purego-based implementation using libsecp256k1.so.

package main

import (
    "fmt"

    "git.mleku.dev/mleku/nostr-codec/pkg/interfaces/signer/p8k"
)

func main() {
    // Create new signer
    keys := p8k.MustNew()

    // Generate random key pair
    if err := keys.Generate(); err != nil {
        panic(err)
    }

    fmt.Printf("Public key: %x\n", keys.Pub())
    fmt.Printf("Secret key: %x\n", keys.Sec())

    // Sign message
    msg := []byte("hello")
    sig, err := keys.Sign(msg)
    if err != nil {
        panic(err)
    }

    // Verify signature
    valid, err := keys.Verify(msg, sig)
    if err != nil || !valid {
        panic("invalid signature")
    }

    // ECDH for encryption
    recipientPub := []byte{/* 32 bytes */}
    sharedSecret, err := keys.ECDH(recipientPub)
    if err != nil {
        panic(err)
    }

    fmt.Printf("Shared secret: %x\n", sharedSecret)

    // Wipe keys when done
    defer keys.Zero()
}

Loading Existing Keys

package main

import (
    "git.mleku.dev/mleku/nostr-codec/pkg/interfaces/signer/p8k"
    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/hex"
)

func main() {
    keys := p8k.MustNew()

    // Load from hex secret key
    secHex := "..."
    secBytes := hex.Dec(secHex)

    if err := keys.InitSec(secBytes); err != nil {
        panic(err)
    }

    // Public key is automatically derived
    fmt.Printf("Loaded pubkey: %x\n", keys.Pub())
}

Schnorr Signatures

The schnorr package provides low-level signature operations.

package main

import (
    "git.mleku.dev/mleku/nostr-codec/pkg/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")
    }
}

Encryption (NIP-04 and NIP-44)

package main

import (
    "git.mleku.dev/mleku/nostr-codec/pkg/crypto/encryption"
    "git.mleku.dev/mleku/nostr-codec/pkg/interfaces/signer/p8k"
)

func main() {
    // Sender keys
    sender := p8k.MustNew()
    sender.Generate()

    // Recipient keys
    recipient := p8k.MustNew()
    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(),
    )
}

Bech32 Encoding

The bech32encoding package provides npub/nsec/note encoding.

package main

import (
    "fmt"

    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/bech32encoding"
    "git.mleku.dev/mleku/nostr-codec/pkg/interfaces/signer/p8k"
)

func main() {
    keys := p8k.MustNew()
    keys.Generate()

    // Encode public key as npub
    npub := bech32encoding.PubkeyToNpub(keys.Pub())
    fmt.Printf("npub: %s\n", npub)

    // Encode secret key as nsec
    nsec := bech32encoding.SecToNsec(keys.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)
    }
}

Utilities

Buffer Pool

The bufpool package provides efficient buffer pooling for zero-copy operations.

package main

import (
    "git.mleku.dev/mleku/nostr-codec/pkg/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
}

Atomic Operations

The atomic package provides extended atomic operations beyond the standard library.

package main

import (
    "git.mleku.dev/mleku/nostr-codec/pkg/utils/atomic"
)

func main() {
    var counter atomic.Int64

    counter.Store(0)
    counter.Add(1)
    value := counter.Load()

    counter.CompareAndSwap(1, 2)
}

Hex Encoding

The hex package provides SIMD-accelerated hex encoding.

package main

import (
    "git.mleku.dev/mleku/nostr-codec/pkg/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")
}

Text Utilities

The text package provides JSON escaping and text processing.

package main

import (
    "git.mleku.dev/mleku/nostr-codec/pkg/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
    }
}

Performance Optimizations

SIMD Acceleration

  • SHA256: Uses minio/sha256-simd for hardware-accelerated hashing
  • Hex Encoding: Uses templexxx/xhex for SIMD hex encoding

Zero-Copy Operations

  • Buffer pooling via bufpool package
  • Reusable byte slices in all encoders
  • Marshal(dst []byte) methods append to existing buffers

Memory Management

// Efficient encoding pattern
buf := bufpool.Get()
defer bufpool.Put(buf)

buf = event.Marshal(buf)  // Append to buffer
buf = filter.Marshal(buf) // Append more

Binary Encoding

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)

Testing

Run tests with:

go test ./...

Run benchmarks:

go test -bench=. -benchmem ./pkg/encoders/event
go test -bench=. -benchmem ./pkg/encoders/filter

Dependencies

Required

  • github.com/minio/sha256-simd - SIMD-accelerated SHA256
  • github.com/templexxx/xhex - SIMD hex encoding
  • github.com/ebitengine/purego - CGO-free library loading
  • lol.mleku.dev - Logging and error handling
  • golang.org/x/crypto - Cryptographic primitives
  • golang.org/x/exp - Experimental packages (constraints)

System Requirements

  • libsecp256k1.so must be available in LD_LIBRARY_PATH or the same directory as the binary
  • Go 1.25.3 or later

Architecture

Package Structure

pkg/
 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
 crypto/            # Cryptographic operations
    ec/            # Elliptic curve crypto
       schnorr/  # Schnorr signatures
       secp256k1/# secp256k1 curve
       bech32/   # Bech32 encoding
    encryption/    # NIP-04/NIP-44
    p8k/          # Purego secp256k1
 interfaces/        # Abstract interfaces
    signer/       # Signer interface
    codec/        # Codec interfaces
 utils/             # Utility packages
    bufpool/      # Buffer pooling
    atomic/       # Atomic operations
    constraints/  # Type constraints
 protocol/          # Protocol helpers
     auth/         # NIP-42 auth

Design Principles

  1. Zero-Copy: All Marshal methods accept dst []byte to append to existing buffers
  2. Buffer Pooling: Use bufpool.Get/Put for temporary buffers
  3. Pure Go: No CGO, uses purego for C library interop
  4. Performance: SIMD acceleration where available
  5. Correctness: Extensive test coverage

Examples

Complete Relay Handler Example

package main

import (
    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/envelopes/eventenvelope"
    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/envelopes/okenvelope"
    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/reason"
    "git.mleku.dev/mleku/nostr-codec/pkg/utils/bufpool"
)

func handleEventMessage(msg []byte) []byte {
    // Parse event envelope
    env := eventenvelope.NewSubmission()
    _, err := env.Unmarshal(msg)
    if err != nil {
        return errorResponse(nil, reason.Invalid.With(": parse error"))
    }
    defer env.E.Free()

    // Verify signature
    valid, err := env.E.Verify()
    if err != nil || !valid {
        return errorResponse(env.E.ID, reason.Invalid.With(": invalid signature"))
    }

    // Store event (pseudo-code)
    if err := database.SaveEvent(env.E); err != nil {
        return errorResponse(env.E.ID, reason.Error.With(": database error"))
    }

    // Return OK
    return successResponse(env.E.ID)
}

func successResponse(eventID []byte) []byte {
    ok := okenvelope.New()
    ok.ID = eventID
    ok.OK = true
    ok.Reason = []byte("")
    return ok.Marshal(nil)
}

func errorResponse(eventID []byte, reason []byte) []byte {
    ok := okenvelope.New()
    ok.ID = eventID
    ok.OK = false
    ok.Reason = reason
    return ok.Marshal(nil)
}

Complete Client Example

package main

import (
    "fmt"
    "time"

    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/event"
    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/kind"
    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/envelopes/eventenvelope"
    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/envelopes/reqenvelope"
    "git.mleku.dev/mleku/nostr-codec/pkg/encoders/filter"
    "git.mleku.dev/mleku/nostr-codec/pkg/interfaces/signer/p8k"
)

func main() {
    // Initialize keys
    keys := p8k.MustNew()
    keys.Generate()

    // Create and publish event
    ev := event.New()
    ev.Kind = kind.TextNote.K
    ev.CreatedAt = time.Now().Unix()
    ev.Content = []byte("Hello Nostr!")
    ev.Sign(keys)

    // Wrap in envelope
    envEvent := eventenvelope.NewSubmission()
    envEvent.E = ev
    wireBytes := envEvent.Marshal(nil)

    // Send to relay
    // ws.Send(wireBytes)

    // Subscribe to events
    req := reqenvelope.New()
    req.Label = []byte("my-feed")

    f := filter.New()
    f.Kinds = kind.NewS(kind.TextNote)
    limit := 100
    f.Limit = &limit

    req.Filters = append(req.Filters, f)
    subscribeBytes := req.Marshal(nil)

    // Send subscription
    // ws.Send(subscribeBytes)

    fmt.Printf("Published event: %x\n", ev.ID)
}

Contributing

Contributions are welcome! Please ensure:

  1. All tests pass: go test ./...
  2. Code is formatted: go fmt ./...
  3. Benchmarks show no regressions
  4. Documentation is updated

License

[Insert license information]


Credits

This library is extracted from the ORLY relay implementation, designed for high-performance Nostr protocol handling.

Key Technologies

  • Cryptography: Uses Bitcoin Core's libsecp256k1 via purego
  • SIMD: MinIO's SHA256 and templexxx's hex encoder
  • Logging: lol.mleku.dev logging framework

See Also

Description
a simple, highly optimized set of encoders for nostr events, filters, and associated helpers. includes AVX SIMD accelerated hex encoding, matching functions for filters, and API messages (envelopes), bech32 encoded entities, as well as a tested and concurrrent safe websocket client library with subscription handling that follows nostr convention, and the cryptographic operations required, signing, verification, ecdh and x-only pubkey derivation
Readme 14 MiB
Languages
Go 97.8%
Python 1.4%
Shell 0.4%
Makefile 0.4%