nostr

package module
v0.16.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 11, 2023 License: MIT Imports: 25 Imported by: 379

README

go-nostr

A set of useful things for Nostr Protocol implementations.

GoDoc
test every commit

Install go-nostr:

go get github.com/nbd-wtf/go-nostr
Generating a key
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)
}
Subscribing to a single relay
relay, err := nostr.RelayConnect(context.Background(), "wss://nostr.zebedee.cloud")
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{1},
		Authors: []string{pub},
		Limit:   1,
	}}
} else {
	panic(err)
}

ctx, cancel := context.WithCancel(context.Background())
sub := relay.Subscribe(ctx, filters)

go func() {
	<-sub.EndOfStoredEvents
	// handle end of stored events (EOSE, see NIP-15)
}()

for ev := range sub.Events {
	// handle returned event.
	// channel will stay open until the ctx is cancelled (in this case, by calling cancel())
	
	fmt.Println(ev.ID)
}
Publishing to two relays
sk := nostr.GeneratePrivateKey()
pub, _ := nostr.GetPublicKey(sk)

ev := nostr.Event{
	PubKey:    pub,
	CreatedAt: time.Now(),
	Kind:      1,
	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
for _, url := range []string{"wss://nostr.zebedee.cloud", "wss://nostr-pub.wellorder.net"} {
	relay, e := nostr.RelayConnect(context.Background(), url)
	if e != nil {
		fmt.Println(e)
		continue
	}
	fmt.Println("published to ", url, relay.Publish(context.Background(), ev))
}
Authenticating with NIP-42

For this section, the user needs access to a relay implementing NIP-42. E.g., https://github.com/fiatjaf/relayer with a relay implementing the relayer.Auther interface.

func main() {
	url := "ws://localhost:7447"

	// Once the connection is initiated the server will send "AUTH" with the challenge string.
	relay, err := nostr.RelayConnect(context.Background(), url)
	if err != nil {
		panic(err)
	}

	// Initialize test user.
	sk := nostr.GeneratePrivateKey()
	pub, _ := nostr.GetPublicKey(sk)
	npub, _ := nip19.EncodePublicKey(pub)

	// Relay.Challenges channel will receive the "AUTH" command.
	challenge := <-relay.Challenges

	// Create the auth event to send back.
	// The user will be authenticated as pub.
	event := nip42.CreateUnsignedAuthEvent(challenge, pub, url)
	event.Sign(sk)

	// Set-up context with 3 second time out.
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	// Send the event by calling relay.Auth.
	// Returned status is either success, fail, or sent (if no reply given in the 3 second timeout).
	auth_status := relay.Auth(ctx, event)

	fmt.Printf("authenticated as %s: %s\n", npub, auth_status)
}
Example script
go run example/example.go

Documentation

Index

Examples

Constants

View Source
const (
	KindSetMetadata            int = 0
	KindTextNote               int = 1
	KindRecommendServer        int = 2
	KindContactList            int = 3
	KindEncryptedDirectMessage int = 4
	KindDeletion               int = 5
	KindBoost                  int = 6
	KindReaction               int = 7
	KindChannelCreation        int = 40
	KindChannelMetadata        int = 41
	KindChannelMessage         int = 42
	KindChannelHideMessage     int = 43
	KindChannelMuteUser        int = 44
	KindZapRequest             int = 9734
	KindZap                    int = 9735
)

Variables

View Source
var (
	// call SetOutput on InfoLogger to enable info logging
	InfoLogger = log.New(ioutil.Discard, "[go-nostr][info] ", log.LstdFlags)

	// call SetOutput on DebugLogger to enable debug logging
	DebugLogger = log.New(ioutil.Discard, "[go-nostr][debug] ", log.LstdFlags)
)

Functions

func FilterEqual

func FilterEqual(a Filter, b Filter) bool

func GeneratePrivateKey

func GeneratePrivateKey() string

func GetPublicKey

func GetPublicKey(sk string) (string, error)

func IsValidPublicKeyHex added in v0.14.0

func IsValidPublicKeyHex(pk string) bool

func NormalizeURL

func NormalizeURL(u string) string

NormalizeURL normalizes the url and replaces http://, https:// schemes by ws://, wss://.

Example
fmt.Println(NormalizeURL(""))
fmt.Println(NormalizeURL("wss://x.com/y"))
fmt.Println(NormalizeURL("wss://x.com/y/"))
fmt.Println(NormalizeURL("http://x.com/y"))
fmt.Println(NormalizeURL(NormalizeURL("http://x.com/y")))
fmt.Println(NormalizeURL("wss://x.com"))
fmt.Println(NormalizeURL("wss://x.com/"))
fmt.Println(NormalizeURL(NormalizeURL(NormalizeURL("wss://x.com/"))))
fmt.Println(NormalizeURL("x.com"))
fmt.Println(NormalizeURL("x.com/"))
fmt.Println(NormalizeURL("x.com////"))
fmt.Println(NormalizeURL("x.com/?x=23"))
Output:


wss://x.com/y
wss://x.com/y
ws://x.com/y
ws://x.com/y
wss://x.com
wss://x.com
wss://x.com
wss://x.com
wss://x.com
wss://x.com
wss://x.com?x=23

Types

type Connection

type Connection struct {
	// contains filtered or unexported fields
}

func NewConnection

func NewConnection(socket *websocket.Conn) *Connection

func (*Connection) Close

func (c *Connection) Close() error

func (*Connection) WriteJSON

func (c *Connection) WriteJSON(v interface{}) error

func (*Connection) WriteMessage

func (c *Connection) WriteMessage(messageType int, data []byte) error

type EntityPointer added in v0.14.0

type EntityPointer struct {
	PublicKey  string
	Kind       int
	Identifier string
	Relays     []string
}

type Event

type Event struct {
	ID        string
	PubKey    string
	CreatedAt time.Time
	Kind      int
	Tags      Tags
	Content   string
	Sig       string
	// contains filtered or unexported fields
}

func (Event) CheckSignature

func (evt Event) CheckSignature() (bool, error)

CheckSignature checks if the signature is valid for the id (which is a hash of the serialized event content). returns an error if the signature itself is invalid.

func (Event) GetExtra added in v0.8.2

func (evt Event) GetExtra(key string) any

GetExtra tries to get a value under the given key that may be present in the event object but is hidden in the basic type since it is out of the spec.

func (Event) GetExtraBoolean added in v0.8.2

func (evt Event) GetExtraBoolean(key string) bool

GetExtraBoolean is like Event.GetExtra, but only works if the value is a boolean, otherwise returns the zero-value.

func (Event) GetExtraNumber added in v0.8.2

func (evt Event) GetExtraNumber(key string) float64

GetExtraNumber is like Event.GetExtra, but only works if the value is a float64, otherwise returns the zero-value.

func (Event) GetExtraString added in v0.8.2

func (evt Event) GetExtraString(key string) string

GetExtraString is like Event.GetExtra, but only works if the value is a string, otherwise returns the zero-value.

func (*Event) GetID

func (evt *Event) GetID() string

GetID serializes and returns the event ID as a string

func (Event) MarshalJSON

func (evt Event) MarshalJSON() ([]byte, error)

MarshalJSON() returns the JSON byte encoding of the event, as in NIP-01.

func (*Event) Serialize

func (evt *Event) Serialize() []byte

Serialize outputs a byte array that can be hashed/signed to identify/authenticate. JSON encoding as defined in RFC4627.

func (*Event) SetExtra added in v0.8.2

func (evt *Event) SetExtra(key string, value any)

SetExtra sets an out-of-the-spec value under the given key into the event object.

func (*Event) Sign

func (evt *Event) Sign(privateKey string) error

Sign signs an event with a given privateKey

func (*Event) UnmarshalJSON

func (evt *Event) UnmarshalJSON(payload []byte) error

type EventMessage

type EventMessage struct {
	Event Event
	Relay string
}

type EventPointer added in v0.13.0

type EventPointer struct {
	ID     string
	Relays []string
	Author string
}

type Filter

type Filter struct {
	IDs     []string
	Kinds   []int
	Authors []string
	Tags    TagMap
	Since   *time.Time
	Until   *time.Time
	Limit   int
	Search  string
}

func (Filter) MarshalJSON

func (f Filter) MarshalJSON() ([]byte, error)

func (Filter) Matches

func (ef Filter) Matches(event *Event) bool

func (Filter) String added in v0.8.2

func (ef Filter) String() string

func (*Filter) UnmarshalJSON

func (f *Filter) UnmarshalJSON(payload []byte) error

type Filters

type Filters []Filter

func (Filters) Match

func (eff Filters) Match(event *Event) bool

func (Filters) String added in v0.8.2

func (eff Filters) String() string

type ProfileMetadata added in v0.9.0

type ProfileMetadata struct {
	Name    string `json:"name,omitempty"`
	About   string `json:"about,omitempty"`
	Picture string `json:"picture,omitempty"`
	NIP05   string `json:"nip05,omitempty"`
}

func ParseMetadata added in v0.9.0

func ParseMetadata(event Event) (*ProfileMetadata, error)

type ProfilePointer added in v0.13.0

type ProfilePointer struct {
	PublicKey string
	Relays    []string
}

type Relay

type Relay struct {
	URL           string
	RequestHeader http.Header // e.g. for origin header

	Connection *Connection

	Challenges        chan string // NIP-42 Challenges
	Notices           chan string
	ConnectionError   error
	ConnectionContext context.Context // will be canceled when the connection closes

	// custom things that aren't often used
	//
	AssumeValid bool // this will skip verifying signatures for events received from this relay
	// contains filtered or unexported fields
}

func RelayConnect added in v0.8.1

func RelayConnect(ctx context.Context, url string) (*Relay, error)

RelayConnect returns a relay object connected to url. Once successfully connected, cancelling ctx has no effect. To close the connection, call r.Close().

func (*Relay) Auth added in v0.12.0

func (r *Relay) Auth(ctx context.Context, event Event) (Status, error)

Auth sends an "AUTH" command client -> relay as in NIP-42. Status can be: success, failed, or sent (no response from relay before ctx times out).

func (*Relay) Close

func (r *Relay) Close() error

func (*Relay) Connect

func (r *Relay) Connect(ctx context.Context) error

Connect tries to establish a websocket connection to r.URL. If the context expires before the connection is complete, an error is returned. Once successfully connected, context expiration has no effect: call r.Close to close the connection.

func (*Relay) PrepareSubscription added in v0.8.2

func (r *Relay) PrepareSubscription(ctx context.Context) *Subscription

func (*Relay) Publish

func (r *Relay) Publish(ctx context.Context, event Event) (Status, error)

Publish sends an "EVENT" command to the relay r as in NIP-01. Status can be: success, failed, or sent (no response from relay before ctx times out).

func (*Relay) QuerySync added in v0.9.0

func (r *Relay) QuerySync(ctx context.Context, filter Filter) ([]*Event, error)

func (*Relay) String added in v0.8.2

func (r *Relay) String() string

func (*Relay) Subscribe

func (r *Relay) Subscribe(ctx context.Context, filters Filters) (*Subscription, error)

Subscribe sends a "REQ" command to the relay r as in NIP-01. Events are returned through the channel sub.Events. The subscription is closed when context ctx is cancelled ("CLOSE" in NIP-01).

type Status

type Status int
const (
	PublishStatusSent      Status = 0
	PublishStatusFailed    Status = -1
	PublishStatusSucceeded Status = 1
)

func (Status) String

func (s Status) String() string

type Subscription

type Subscription struct {
	Relay             *Relay
	Filters           Filters
	Events            chan *Event
	EndOfStoredEvents chan struct{}
	Context           context.Context
	// contains filtered or unexported fields
}

func (*Subscription) Fire added in v0.8.2

func (sub *Subscription) Fire() error

Fire sends the "REQ" command to the relay.

func (*Subscription) GetID added in v0.14.0

func (sub *Subscription) GetID() string

GetID return the Nostr subscription ID as given to the relay, it will be a sequential number, stringified.

func (*Subscription) SetLabel added in v0.14.0

func (sub *Subscription) SetLabel(label string)

SetLabel puts a label on the subscription that is prepended to the id that is sent to relays,

it's only useful for debugging and sanity purposes.

func (*Subscription) Sub

func (sub *Subscription) Sub(ctx context.Context, filters Filters)

Sub sets sub.Filters and then calls sub.Fire(ctx).

func (*Subscription) Unsub

func (sub *Subscription) Unsub()

Unsub closes the subscription, sending "CLOSE" to relay as in NIP-01. Unsub() also closes the channel sub.Events.

type Tag

type Tag []string

func (Tag) Key added in v0.8.2

func (tag Tag) Key() string

func (Tag) Relay added in v0.8.2

func (tag Tag) Relay() string

func (Tag) StartsWith

func (tag Tag) StartsWith(prefix []string) bool

StartsWith checks if a tag contains a prefix. for example,

["p", "abcdef...", "wss://relay.com"]

would match against

["p", "abcdef..."]

or even

["p", "abcdef...", "wss://"]

func (Tag) Value added in v0.8.2

func (tag Tag) Value() string

type TagMap

type TagMap map[string][]string

type Tags

type Tags []Tag

func (Tags) AppendUnique

func (tags Tags) AppendUnique(tag Tag) Tags

AppendUnique appends a tag if it doesn't exist yet, otherwise does nothing. the uniqueness comparison is done based only on the first 2 elements of the tag.

func (Tags) ContainsAny

func (tags Tags) ContainsAny(tagName string, values []string) bool

func (Tags) FilterOut

func (tags Tags) FilterOut(tagPrefix []string) Tags

FilterOut removes all tags that match the prefix, see Tag.StartsWith

func (Tags) GetAll

func (tags Tags) GetAll(tagPrefix []string) Tags

GetAll gets all the tags that match the prefix, see Tag.StartsWith

func (Tags) GetFirst

func (tags Tags) GetFirst(tagPrefix []string) *Tag

GetFirst gets the first tag in tags that matches the prefix, see Tag.StartsWith

func (Tags) GetLast

func (tags Tags) GetLast(tagPrefix []string) *Tag

GetLast gets the last tag in tags that matches the prefix, see Tag.StartsWith

func (*Tags) Scan

func (t *Tags) Scan(src interface{}) error

Directories

Path Synopsis
Package nip13 implements NIP-13 See https://github.com/nostr-protocol/nips/blob/master/13.md for details.
Package nip13 implements NIP-13 See https://github.com/nostr-protocol/nips/blob/master/13.md for details.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL