From 5bfaed2740f891f19b25a6d9231468214c67e594 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Tue, 4 Mar 2025 11:08:31 -0300 Subject: [PATCH] docstrings for many functions. --- connection.go | 6 ++++++ envelopes.go | 13 +++++++++++++ event.go | 9 ++++----- keyer.go | 28 ++++++++++++++++++++++----- keyer/bunker.go | 11 ++++++++++- keyer/encrypted.go | 13 ++++++++++++- keyer/lib.go | 26 +++++++++++++++++++++---- keyer/manual.go | 24 +++++++++++++++++------ keyer/plain.go | 13 ++++++++++++- pointers.go | 18 ++++++++++++++++++ pool.go | 19 +++++++++++++++---- relay.go | 38 ++++++++++++++++++++++++------------- sdk/event_relays.go | 11 ++++++++++- sdk/helpers.go | 23 +++++++++++++++++----- sdk/metadata.go | 16 +++++++++++++++- sdk/specific_event.go | 15 ++++++++++++--- sdk/system.go | 40 +++++++++++++++++++++++++++++++++++++++ sdk/utils.go | 6 ++++-- signature.go | 8 +++++--- signature_libsecp256k1.go | 4 ---- subscription.go | 12 +++++------- utils.go | 6 ++++++ 22 files changed, 293 insertions(+), 66 deletions(-) diff --git a/connection.go b/connection.go index d31ac1c..f6857b2 100644 --- a/connection.go +++ b/connection.go @@ -12,10 +12,12 @@ import ( ws "github.com/coder/websocket" ) +// Connection represents a websocket connection to a Nostr relay. type Connection struct { conn *ws.Conn } +// NewConnection creates a new websocket connection to a Nostr relay. func NewConnection(ctx context.Context, url string, requestHeader http.Header, tlsConfig *tls.Config) (*Connection, error) { c, _, err := ws.Dial(ctx, url, getConnectionOptions(requestHeader, tlsConfig)) if err != nil { @@ -29,6 +31,7 @@ func NewConnection(ctx context.Context, url string, requestHeader http.Header, t }, nil } +// WriteMessage writes arbitrary bytes to the websocket connection. func (c *Connection) WriteMessage(ctx context.Context, data []byte) error { if err := c.conn.Write(ctx, ws.MessageText, data); err != nil { return fmt.Errorf("failed to write message: %w", err) @@ -37,6 +40,7 @@ func (c *Connection) WriteMessage(ctx context.Context, data []byte) error { return nil } +// ReadMessage reads arbitrary bytes from the websocket connection into the provided buffer. func (c *Connection) ReadMessage(ctx context.Context, buf io.Writer) error { _, reader, err := c.conn.Reader(ctx) if err != nil { @@ -48,10 +52,12 @@ func (c *Connection) ReadMessage(ctx context.Context, buf io.Writer) error { return nil } +// Close closes the websocket connection. func (c *Connection) Close() error { return c.conn.Close(ws.StatusNormalClosure, "") } +// Ping sends a ping message to the websocket connection. func (c *Connection) Ping(ctx context.Context) error { ctx, cancel := context.WithTimeoutCause(ctx, time.Millisecond*800, errors.New("ping took too long")) defer cancel() diff --git a/envelopes.go b/envelopes.go index 8067ec3..9c3a45c 100644 --- a/envelopes.go +++ b/envelopes.go @@ -27,6 +27,7 @@ var ( UnknownLabel = errors.New("unknown envelope label") ) +// ParseMessageSIMD parses a message using the experimental simdjson-go library. func ParseMessageSIMD(message []byte, reuse *simdjson.ParsedJson) (Envelope, error) { parsed, err := simdjson.Parse(message, reuse) if err != nil { @@ -75,6 +76,7 @@ func ParseMessageSIMD(message []byte, reuse *simdjson.ParsedJson) (Envelope, err return v, err } +// ParseMessage parses a message into an Envelope. func ParseMessage(message []byte) Envelope { firstComma := bytes.Index(message, []byte{','}) if firstComma == -1 { @@ -115,6 +117,7 @@ func ParseMessage(message []byte) Envelope { return v } +// Envelope is the interface for all nostr message envelopes. type Envelope interface { Label() string UnmarshalJSON([]byte) error @@ -122,6 +125,7 @@ type Envelope interface { String() string } +// EnvelopeSIMD extends Envelope with SIMD unmarshaling capability. type EnvelopeSIMD interface { Envelope UnmarshalSIMD(simdjson.Iter) error @@ -138,6 +142,7 @@ var ( _ Envelope = (*AuthEnvelope)(nil) ) +// EventEnvelope represents an EVENT message. type EventEnvelope struct { SubscriptionID *string Event @@ -191,6 +196,7 @@ func (v EventEnvelope) MarshalJSON() ([]byte, error) { return w.BuildBytes() } +// ReqEnvelope represents a REQ message. type ReqEnvelope struct { SubscriptionID string Filters @@ -268,6 +274,7 @@ func (v ReqEnvelope) MarshalJSON() ([]byte, error) { return w.BuildBytes() } +// CountEnvelope represents a COUNT message. type CountEnvelope struct { SubscriptionID string Filters @@ -392,6 +399,7 @@ func (v CountEnvelope) MarshalJSON() ([]byte, error) { return w.BuildBytes() } +// NoticeEnvelope represents a NOTICE message. type NoticeEnvelope string func (_ NoticeEnvelope) Label() string { return "NOTICE" } @@ -426,6 +434,7 @@ func (v NoticeEnvelope) MarshalJSON() ([]byte, error) { return w.BuildBytes() } +// EOSEEnvelope represents an EOSE (End of Stored Events) message. type EOSEEnvelope string func (_ EOSEEnvelope) Label() string { return "EOSE" } @@ -460,6 +469,7 @@ func (v EOSEEnvelope) MarshalJSON() ([]byte, error) { return w.BuildBytes() } +// CloseEnvelope represents a CLOSE message. type CloseEnvelope string func (_ CloseEnvelope) Label() string { return "CLOSE" } @@ -496,6 +506,7 @@ func (v CloseEnvelope) MarshalJSON() ([]byte, error) { return w.BuildBytes() } +// ClosedEnvelope represents a CLOSED message. type ClosedEnvelope struct { SubscriptionID string Reason string @@ -539,6 +550,7 @@ func (v ClosedEnvelope) MarshalJSON() ([]byte, error) { return w.BuildBytes() } +// OKEnvelope represents an OK message. type OKEnvelope struct { EventID string OK bool @@ -597,6 +609,7 @@ func (v OKEnvelope) MarshalJSON() ([]byte, error) { return w.BuildBytes() } +// AuthEnvelope represents an AUTH message. type AuthEnvelope struct { Challenge *string Event Event diff --git a/event.go b/event.go index 869ce57..e702626 100644 --- a/event.go +++ b/event.go @@ -8,6 +8,7 @@ import ( "github.com/mailru/easyjson" ) +// Event represents a Nostr event. type Event struct { ID string PubKey string @@ -18,19 +19,18 @@ type Event struct { Sig string } -// Event Stringer interface, just returns the raw JSON as a string. func (evt Event) String() string { j, _ := easyjson.Marshal(evt) return string(j) } -// GetID serializes and returns the event ID as a string. +// GetID computes the event ID abd returns it as a hex string. func (evt *Event) GetID() string { h := sha256.Sum256(evt.Serialize()) return hex.EncodeToString(h[:]) } -// CheckID checks if the implied ID matches the given ID +// CheckID checks if the implied ID matches the given ID more efficiently. func (evt *Event) CheckID() bool { ser := evt.Serialize() h := sha256.Sum256(ser) @@ -52,8 +52,7 @@ func (evt *Event) CheckID() bool { return true } -// Serialize outputs a byte array that can be hashed/signed to identify/authenticate. -// JSON encoding as defined in RFC4627. +// Serialize outputs a byte array that can be hashed to produce the canonical event "id". func (evt *Event) Serialize() []byte { // the serialization process is just putting everything into a JSON array // so the order is kept. See NIP-01 diff --git a/keyer.go b/keyer.go index fcc2864..67a6618 100644 --- a/keyer.go +++ b/keyer.go @@ -1,20 +1,38 @@ package nostr -import "context" +import ( + "context" +) +// Keyer is an interface for signing events and performing cryptographic operations. +// It abstracts away the details of key management, allowing for different implementations +// such as in-memory keys, hardware wallets, or remote signing services (bunker). type Keyer interface { + // Signer provides event signing capabilities Signer + + // Cipher provides encryption and decryption capabilities (NIP-44) Cipher } -// A Signer provides basic public key signing methods. +// Signer is an interface for signing events. type Signer interface { - GetPublicKey(context.Context) (string, error) - SignEvent(context.Context, *Event) error + // SignEvent signs the provided event, setting its ID, PubKey, and Sig fields. + // The context can be used for operations that may require user interaction or + // network access, such as with remote signers. + SignEvent(ctx context.Context, evt *Event) error + + // GetPublicKey returns the public key associated with this signer. + GetPublicKey(ctx context.Context) (string, error) } -// A Cipher provides NIP-44 encryption and decryption methods. +// Cipher is an interface for encrypting and decrypting messages with NIP-44 type Cipher interface { + // Encrypt encrypts a plaintext message for a recipient. + // Returns the encrypted message as a base64-encoded string. Encrypt(ctx context.Context, plaintext string, recipientPublicKey string) (base64ciphertext string, err error) + + // Decrypt decrypts a base64-encoded ciphertext from a sender. + // Returns the decrypted plaintext. Decrypt(ctx context.Context, base64ciphertext string, senderPublicKey string) (plaintext string, err error) } diff --git a/keyer/bunker.go b/keyer/bunker.go index 7d240a3..b46dd7e 100644 --- a/keyer/bunker.go +++ b/keyer/bunker.go @@ -9,15 +9,20 @@ import ( "github.com/nbd-wtf/go-nostr/nip46" ) -// BunkerSigner is a signer that asks a bunker using NIP-46 every time it needs to do an operation. +// BunkerSigner is a signer that delegates operations to a remote bunker using NIP-46. +// It communicates with the bunker for all cryptographic operations rather than +// handling the private key locally. type BunkerSigner struct { bunker *nip46.BunkerClient } +// NewBunkerSignerFromBunkerClient creates a new BunkerSigner from an existing BunkerClient. func NewBunkerSignerFromBunkerClient(bc *nip46.BunkerClient) BunkerSigner { return BunkerSigner{bc} } +// GetPublicKey retrieves the public key from the remote bunker. +// It uses a timeout to prevent hanging indefinitely. func (bs BunkerSigner) GetPublicKey(ctx context.Context) (string, error) { ctx, cancel := context.WithTimeoutCause(ctx, time.Second*30, errors.New("get_public_key took too long")) defer cancel() @@ -28,16 +33,20 @@ func (bs BunkerSigner) GetPublicKey(ctx context.Context) (string, error) { return pk, nil } +// SignEvent sends the event to the remote bunker for signing. +// It uses a timeout to prevent hanging indefinitely. func (bs BunkerSigner) SignEvent(ctx context.Context, evt *nostr.Event) error { ctx, cancel := context.WithTimeoutCause(ctx, time.Second*30, errors.New("sign_event took too long")) defer cancel() return bs.bunker.SignEvent(ctx, evt) } +// Encrypt encrypts a plaintext message for a recipient using the remote bunker. func (bs BunkerSigner) Encrypt(ctx context.Context, plaintext string, recipient string) (string, error) { return bs.bunker.NIP44Encrypt(ctx, recipient, plaintext) } +// Decrypt decrypts a base64-encoded ciphertext from a sender using the remote bunker. func (bs BunkerSigner) Decrypt(ctx context.Context, base64ciphertext string, sender string) (plaintext string, err error) { return bs.bunker.NIP44Encrypt(ctx, sender, base64ciphertext) } diff --git a/keyer/encrypted.go b/keyer/encrypted.go index 1e875d1..3520721 100644 --- a/keyer/encrypted.go +++ b/keyer/encrypted.go @@ -9,13 +9,18 @@ import ( "github.com/nbd-wtf/go-nostr/nip49" ) -// EncryptedKeySigner is a signer that must always ask the user for a password before every operation. +// EncryptedKeySigner is a signer that must ask the user for a password before every operation. +// It stores the private key in encrypted form (NIP-49) and uses a callback to request the password +// when needed for operations. type EncryptedKeySigner struct { ncryptsec string pk string callback func(context.Context) string } +// GetPublicKey returns the public key associated with this signer. +// If the public key is not cached, it will decrypt the private key using the password +// callback to derive the public key. func (es *EncryptedKeySigner) GetPublicKey(ctx context.Context) (string, error) { if es.pk != "" { return es.pk, nil @@ -33,6 +38,8 @@ func (es *EncryptedKeySigner) GetPublicKey(ctx context.Context) (string, error) return pk, nil } +// SignEvent signs the provided event by first decrypting the private key +// using the password callback, then signing the event with the decrypted key. func (es *EncryptedKeySigner) SignEvent(ctx context.Context, evt *nostr.Event) error { password := es.callback(ctx) sk, err := nip49.Decrypt(es.ncryptsec, password) @@ -43,6 +50,8 @@ func (es *EncryptedKeySigner) SignEvent(ctx context.Context, evt *nostr.Event) e return evt.Sign(sk) } +// Encrypt encrypts a plaintext message for a recipient using NIP-44. +// It first decrypts the private key using the password callback. func (es EncryptedKeySigner) Encrypt(ctx context.Context, plaintext string, recipient string) (c64 string, err error) { password := es.callback(ctx) sk, err := nip49.Decrypt(es.ncryptsec, password) @@ -56,6 +65,8 @@ func (es EncryptedKeySigner) Encrypt(ctx context.Context, plaintext string, reci return nip44.Encrypt(plaintext, ck) } +// Decrypt decrypts a base64-encoded ciphertext from a sender using NIP-44. +// It first decrypts the private key using the password callback. func (es EncryptedKeySigner) Decrypt(ctx context.Context, base64ciphertext string, sender string) (plaintext string, err error) { password := es.callback(ctx) sk, err := nip49.Decrypt(es.ncryptsec, password) diff --git a/keyer/lib.go b/keyer/lib.go index 0972b30..15ad6f1 100644 --- a/keyer/lib.go +++ b/keyer/lib.go @@ -22,19 +22,37 @@ var ( _ nostr.Keyer = (*ManualSigner)(nil) ) +// SignerOptions contains configuration options for creating a new signer. type SignerOptions struct { + // BunkerClientSecretKey is the secret key used for the bunker client BunkerClientSecretKey string - BunkerSignTimeout time.Duration - BunkerAuthHandler func(string) - // if a PasswordHandler is provided the key will be stored encrypted and this function will be called + // BunkerSignTimeout is the timeout duration for bunker signing operations + BunkerSignTimeout time.Duration + + // BunkerAuthHandler is called when authentication is needed for bunker operations + BunkerAuthHandler func(string) + + // PasswordHandler is called when an operation needs access to the encrypted key. + // If provided, the key will be stored encrypted and this function will be called // every time an operation needs access to the key so the user can be prompted. PasswordHandler func(context.Context) string - // if instead a Password is provided along with a ncryptsec, then the key will be decrypted and stored in plaintext. + // Password is used along with ncryptsec to decrypt the key. + // If provided, the key will be decrypted and stored in plaintext. Password string } +// New creates a new Keyer implementation based on the input string format. +// It supports various input formats: +// - ncryptsec: Creates an EncryptedKeySigner or KeySigner depending on options +// - NIP-46 bunker URL or NIP-05 identifier: Creates a BunkerSigner +// - nsec: Creates a KeySigner +// - hex private key: Creates a KeySigner +// +// The context is used for operations that may require network access. +// The pool is used for relay connections when needed. +// Options are used for additional pieces required for EncryptedKeySigner and BunkerSigner. func New(ctx context.Context, pool *nostr.SimplePool, input string, opts *SignerOptions) (nostr.Keyer, error) { if opts == nil { opts = &SignerOptions{} diff --git a/keyer/manual.go b/keyer/manual.go index e6e1e0d..c23283d 100644 --- a/keyer/manual.go +++ b/keyer/manual.go @@ -6,28 +6,40 @@ import ( "github.com/nbd-wtf/go-nostr" ) -// ManualSigner is a signer that doesn't really do anything, it just calls the functions given to it. -// It can be used when an app for some reason wants to ask the user to manually provide a signed event -// by copy-and-paste, for example. +// ManualSigner is a signer that delegates all operations to user-provided functions. +// It can be used when an app wants to ask the user or some custom server to manually provide a +// signed event or an encrypted or decrypted payload by copy-and-paste, for example, or when the +// app wants to implement custom signing logic. type ManualSigner struct { + // ManualGetPublicKey is called when the public key is needed ManualGetPublicKey func(context.Context) (string, error) - ManualSignEvent func(context.Context, *nostr.Event) error - ManualEncrypt func(ctx context.Context, plaintext string, recipientPublicKey string) (base64ciphertext string, err error) - ManualDecrypt func(ctx context.Context, base64ciphertext string, senderPublicKey string) (plaintext string, err error) + + // ManualSignEvent is called when an event needs to be signed + ManualSignEvent func(context.Context, *nostr.Event) error + + // ManualEncrypt is called when a message needs to be encrypted + ManualEncrypt func(ctx context.Context, plaintext string, recipientPublicKey string) (base64ciphertext string, err error) + + // ManualDecrypt is called when a message needs to be decrypted + ManualDecrypt func(ctx context.Context, base64ciphertext string, senderPublicKey string) (plaintext string, err error) } +// SignEvent delegates event signing to the ManualSignEvent function. func (ms ManualSigner) SignEvent(ctx context.Context, evt *nostr.Event) error { return ms.ManualSignEvent(ctx, evt) } +// GetPublicKey delegates public key retrieval to the ManualGetPublicKey function. func (ms ManualSigner) GetPublicKey(ctx context.Context) (string, error) { return ms.ManualGetPublicKey(ctx) } +// Encrypt delegates encryption to the ManualEncrypt function. func (ms ManualSigner) Encrypt(ctx context.Context, plaintext string, recipient string) (c64 string, err error) { return ms.ManualEncrypt(ctx, plaintext, recipient) } +// Decrypt delegates decryption to the ManualDecrypt function. func (ms ManualSigner) Decrypt(ctx context.Context, base64ciphertext string, sender string) (plaintext string, err error) { return ms.ManualDecrypt(ctx, base64ciphertext, sender) } diff --git a/keyer/plain.go b/keyer/plain.go index ffab64d..987b7af 100644 --- a/keyer/plain.go +++ b/keyer/plain.go @@ -8,7 +8,8 @@ import ( "github.com/puzpuzpuz/xsync/v3" ) -// Keysigner is a signer that holds the private key in memory and can do all the operations instantly and easily. +// KeySigner is a signer that holds the private key in memory and can perform +// all operations instantly and easily. type KeySigner struct { sk string pk string @@ -16,6 +17,8 @@ type KeySigner struct { conversationKeys *xsync.MapOf[string, [32]byte] } +// NewPlainKeySigner creates a new KeySigner from a private key. +// Returns an error if the private key is invalid. func NewPlainKeySigner(sec string) (KeySigner, error) { pk, err := nostr.GetPublicKey(sec) if err != nil { @@ -24,9 +27,15 @@ func NewPlainKeySigner(sec string) (KeySigner, error) { return KeySigner{sec, pk, xsync.NewMapOf[string, [32]byte]()}, nil } +// SignEvent signs the provided event with the signer's private key. +// It sets the event's ID, PubKey, and Sig fields. func (ks KeySigner) SignEvent(ctx context.Context, evt *nostr.Event) error { return evt.Sign(ks.sk) } + +// GetPublicKey returns the public key associated with this signer. func (ks KeySigner) GetPublicKey(ctx context.Context) (string, error) { return ks.pk, nil } +// Encrypt encrypts a plaintext message for a recipient using NIP-44. +// It caches conversation keys for efficiency in repeated operations. func (ks KeySigner) Encrypt(ctx context.Context, plaintext string, recipient string) (string, error) { ck, ok := ks.conversationKeys.Load(recipient) if !ok { @@ -40,6 +49,8 @@ func (ks KeySigner) Encrypt(ctx context.Context, plaintext string, recipient str return nip44.Encrypt(plaintext, ck) } +// Decrypt decrypts a base64-encoded ciphertext from a sender using NIP-44. +// It caches conversation keys for efficiency in repeated operations. func (ks KeySigner) Decrypt(ctx context.Context, base64ciphertext string, sender string) (string, error) { ck, ok := ks.conversationKeys.Load(sender) if !ok { diff --git a/pointers.go b/pointers.go index e2467f3..3458c06 100644 --- a/pointers.go +++ b/pointers.go @@ -6,9 +6,18 @@ import ( "strings" ) +// Pointer is an interface for different types of Nostr pointers. +// +// In this context, a "pointer" is a reference to an event or profile potentially including +// relays and other metadata that might help find it. type Pointer interface { + // AsTagReference returns the pointer as a string as it would be seen in the value of a tag (i.e. the tag's second item). AsTagReference() string + + // AsTag converts the pointer with all the information available to a tag that can be included in events. AsTag() Tag + + // AsFilter converts the pointer to a Filter that can be used to query for it on relays. AsFilter() Filter MatchesEvent(Event) bool } @@ -19,11 +28,13 @@ var ( _ Pointer = (*EntityPointer)(nil) ) +// ProfilePointer represents a pointer to a Nostr profile. type ProfilePointer struct { PublicKey string `json:"pubkey"` Relays []string `json:"relays,omitempty"` } +// ProfilePointerFromTag creates a ProfilePointer from a "p" tag (but it doesn't have to be necessarily a "p" tag, could be something else). func ProfilePointerFromTag(refTag Tag) (ProfilePointer, error) { pk := (refTag)[1] if !IsValidPublicKey(pk) { @@ -41,6 +52,7 @@ func ProfilePointerFromTag(refTag Tag) (ProfilePointer, error) { return pointer, nil } +// MatchesEvent checks if the pointer matches an event. func (ep ProfilePointer) MatchesEvent(_ Event) bool { return false } func (ep ProfilePointer) AsTagReference() string { return ep.PublicKey } func (ep ProfilePointer) AsFilter() Filter { return Filter{Authors: []string{ep.PublicKey}} } @@ -52,6 +64,7 @@ func (ep ProfilePointer) AsTag() Tag { return Tag{"p", ep.PublicKey} } +// EventPointer represents a pointer to a nostr event. type EventPointer struct { ID string `json:"id"` Relays []string `json:"relays,omitempty"` @@ -59,6 +72,7 @@ type EventPointer struct { Kind int `json:"kind,omitempty"` } +// EventPointerFromTag creates an EventPointer from an "e" tag (but it could be other tag name, it isn't checked). func EventPointerFromTag(refTag Tag) (EventPointer, error) { id := (refTag)[1] if !IsValid32ByteHex(id) { @@ -83,6 +97,7 @@ func (ep EventPointer) MatchesEvent(evt Event) bool { return evt.ID == ep.ID } func (ep EventPointer) AsTagReference() string { return ep.ID } func (ep EventPointer) AsFilter() Filter { return Filter{IDs: []string{ep.ID}} } +// AsTag converts the pointer to a Tag. func (ep EventPointer) AsTag() Tag { if len(ep.Relays) > 0 { if ep.Author != "" { @@ -94,6 +109,7 @@ func (ep EventPointer) AsTag() Tag { return Tag{"e", ep.ID} } +// EntityPointer represents a pointer to a nostr entity (addressable event). type EntityPointer struct { PublicKey string `json:"pubkey"` Kind int `json:"kind,omitempty"` @@ -101,6 +117,7 @@ type EntityPointer struct { Relays []string `json:"relays,omitempty"` } +// EntityPointerFromTag creates an EntityPointer from an "a" tag (but it doesn't check if the tag is really "a", it could be anything). func EntityPointerFromTag(refTag Tag) (EntityPointer, error) { spl := strings.SplitN(refTag[1], ":", 3) if len(spl) != 3 { @@ -129,6 +146,7 @@ func EntityPointerFromTag(refTag Tag) (EntityPointer, error) { return pointer, nil } +// MatchesEvent checks if the pointer matches an event. func (ep EntityPointer) MatchesEvent(evt Event) bool { return ep.PublicKey == evt.PubKey && ep.Kind == evt.Kind && diff --git a/pool.go b/pool.go index 925197a..9b8ba97 100644 --- a/pool.go +++ b/pool.go @@ -20,6 +20,7 @@ const ( seenAlreadyDropTick = time.Minute ) +// SimplePool manages connections to multiple relays, ensures they are reopened when necessary and not duplicated. type SimplePool struct { Relays *xsync.MapOf[string, *Relay] Context context.Context @@ -37,11 +38,13 @@ type SimplePool struct { relayOptions []RelayOption } +// DirectedFilter combines a Filter with a specific relay URL. type DirectedFilter struct { Filter Relay string } +// RelayEvent represents an event received from a specific relay. type RelayEvent struct { *Event Relay *Relay @@ -49,10 +52,12 @@ type RelayEvent struct { func (ie RelayEvent) String() string { return fmt.Sprintf("[%s] >> %s", ie.Relay.URL, ie.Event) } +// PoolOption is an interface for options that can be applied to a SimplePool. type PoolOption interface { ApplyPoolOption(*SimplePool) } +// NewSimplePool creates a new SimplePool with the given context and options. func NewSimplePool(ctx context.Context, opts ...PoolOption) *SimplePool { ctx, cancel := context.WithCancelCause(ctx) @@ -140,7 +145,7 @@ func (h WithDuplicateMiddleware) ApplyPoolOption(pool *SimplePool) { pool.duplicateMiddleware = h } -// WithQueryMiddleware is a function that will be called with every combination of relay+pubkey+kind queried +// WithAuthorKindQueryMiddleware is a function that will be called with every combination of relay+pubkey+kind queried // in a .SubMany*() call -- when applicable (i.e. when the query contains a pubkey and a kind). type WithAuthorKindQueryMiddleware func(relay string, pubkey string, kind int) @@ -155,6 +160,8 @@ var ( _ PoolOption = WithRelayOptions(WithRequestHeader(http.Header{})) ) +// EnsureRelay ensures that a relay connection exists and is active. +// If the relay is not connected, it attempts to connect. func (pool *SimplePool) EnsureRelay(url string) (*Relay, error) { nm := NormalizeURL(url) defer namedLock(nm)() @@ -199,12 +206,14 @@ func (pool *SimplePool) EnsureRelay(url string) (*Relay, error) { return relay, nil } +// PublishResult represents the result of publishing an event to a relay. type PublishResult struct { Error error RelayURL string Relay *Relay } +// PublishMany publishes an event to multiple relays and returns a channel of results emitted as they're received. func (pool *SimplePool) PublishMany(ctx context.Context, urls []string, evt Event) chan PublishResult { ch := make(chan PublishResult, len(urls)) @@ -247,7 +256,7 @@ func (pool *SimplePool) FetchMany( return pool.SubManyEose(ctx, urls, Filters{filter}, opts...) } -// Deprecated: use SubscribeMany instead. +// Deprecated: SubMany is deprecated: use SubscribeMany instead. func (pool *SimplePool) SubMany( ctx context.Context, urls []string, @@ -447,7 +456,7 @@ func (pool *SimplePool) subMany( return events } -// Deprecated: use FetchMany instead. +// Deprecated: SubManyEose is deprecated: use FetchMany instead. func (pool *SimplePool) SubManyEose( ctx context.Context, urls []string, @@ -562,7 +571,7 @@ func (pool *SimplePool) subManyEoseNonOverwriteCheckDuplicate( return events } -// CountMany aggregates count results from multiple relays using HyperLogLog +// CountMany aggregates count results from multiple relays using NIP-45 HyperLogLog func (pool *SimplePool) CountMany( ctx context.Context, urls []string, @@ -611,6 +620,7 @@ func (pool *SimplePool) QuerySingle( return nil } +// BatchedSubManyEose performs batched subscriptions to multiple relays with different filters. func (pool *SimplePool) BatchedSubManyEose( ctx context.Context, dfs []DirectedFilter, @@ -648,6 +658,7 @@ func (pool *SimplePool) BatchedSubManyEose( return res } +// Close closes the pool with the given reason. func (pool *SimplePool) Close(reason string) { pool.cancel(fmt.Errorf("pool closed with reason: '%s'", reason)) } diff --git a/relay.go b/relay.go index a527710..48e720e 100644 --- a/relay.go +++ b/relay.go @@ -17,10 +17,9 @@ import ( "github.com/puzpuzpuz/xsync/v3" ) -type Status int - var subscriptionIDCounter atomic.Int64 +// Relay represents a connection to a Nostr relay. type Relay struct { closeMutex sync.Mutex @@ -51,7 +50,7 @@ type writeRequest struct { answer chan error } -// NewRelay returns a new relay. The relay connection will be closed when the context is canceled. +// NewRelay returns a new relay. It takes a context that, when canceled, will close the relay connection. func NewRelay(ctx context.Context, url string, opts ...RelayOption) *Relay { ctx, cancel := context.WithCancelCause(ctx) r := &Relay{ @@ -73,16 +72,18 @@ func NewRelay(ctx context.Context, url string, opts ...RelayOption) *Relay { } // RelayConnect returns a relay object connected to url. -// Once successfully connected, cancelling ctx has no effect. -// To close the connection, call r.Close(). +// +// The given subscription is only used during the connection phase. Once successfully connected, cancelling ctx has no effect. +// +// The ongoing relay connection uses a background context. To close the connection, call r.Close(). +// If you need fine grained long-term connection contexts, use NewRelay() instead. func RelayConnect(ctx context.Context, url string, opts ...RelayOption) (*Relay, error) { r := NewRelay(context.Background(), url, opts...) err := r.Connect(ctx) return r, err } -// When instantiating relay connections, some options may be passed. -// RelayOption is the type of the argument passed for that. +// RelayOption is the type of the argument passed when instantiating relay connections. type RelayOption interface { ApplyRelayOption(*Relay) } @@ -122,6 +123,7 @@ func (r *Relay) String() string { } // Context retrieves the context that is associated with this relay connection. +// It will be closed when the relay is disconnected. func (r *Relay) Context() context.Context { return r.connectionContext } // IsConnected returns true if the connection to this relay seems to be active. @@ -132,14 +134,13 @@ func (r *Relay) IsConnected() bool { return r.connectionContext.Err() == nil } // Once successfully connected, context expiration has no effect: call r.Close // to close the connection. // -// The underlying relay connection will use a background context. If you want to -// pass a custom context to the underlying relay connection, use NewRelay() and -// then Relay.Connect(). +// The given context here is only used during the connection phase. The long-living +// relay connection will be based on the context given to NewRelay(). func (r *Relay) Connect(ctx context.Context) error { return r.ConnectWithTLS(ctx, nil) } -// ConnectWithTLS tries to establish a secured websocket connection to r.URL using customized tls.Config (CA's, etc). +// ConnectWithTLS is like Connect(), but takes a special tls.Config if you need that. func (r *Relay) ConnectWithTLS(ctx context.Context, tlsConfig *tls.Config) error { if r.connectionContext == nil || r.Subscriptions == nil { return fmt.Errorf("relay must be initialized with a call to NewRelay()") @@ -303,7 +304,7 @@ func (r *Relay) ConnectWithTLS(ctx context.Context, tlsConfig *tls.Config) error return nil } -// Write queues a message to be sent to the relay. +// Write queues an arbitrary message to be sent to the relay. func (r *Relay) Write(msg []byte) <-chan error { ch := make(chan error) select { @@ -320,6 +321,9 @@ func (r *Relay) Publish(ctx context.Context, event Event) error { } // Auth sends an "AUTH" command client->relay as in NIP-42 and waits for an OK response. +// +// You don't have to build the AUTH event yourself, this function takes a function to which the +// event that must be signed will be passed, so it's only necessary to sign that. func (r *Relay) Auth(ctx context.Context, sign func(event *Event) error) error { authEvent := Event{ CreatedAt: Now(), @@ -337,7 +341,6 @@ func (r *Relay) Auth(ctx context.Context, sign func(event *Event) error) error { return r.publish(ctx, authEvent.ID, &AuthEnvelope{Event: authEvent}) } -// publish can be used both for EVENT and for AUTH func (r *Relay) publish(ctx context.Context, id string, env Envelope) error { var err error var cancel context.CancelFunc @@ -451,6 +454,9 @@ func (r *Relay) PrepareSubscription(ctx context.Context, filters Filters, opts . return sub } +// QueryEvents subscribes to events matching the given filter and returns a channel of events. +// +// In most cases it's better to use SimplePool instead of this method. func (r *Relay) QueryEvents(ctx context.Context, filter Filter) (chan *Event, error) { sub, err := r.Subscribe(ctx, Filters{filter}) if err != nil { @@ -473,6 +479,10 @@ func (r *Relay) QueryEvents(ctx context.Context, filter Filter) (chan *Event, er return sub.Events, nil } +// QuerySync subscribes to events matching the given filter and returns a slice of events. +// This method blocks until all events are received or the context is canceled. +// +// In most cases it's better to use SimplePool instead of this method. func (r *Relay) QuerySync(ctx context.Context, filter Filter) ([]*Event, error) { if _, ok := ctx.Deadline(); !ok { // if no timeout is set, force it to 7 seconds @@ -494,6 +504,7 @@ func (r *Relay) QuerySync(ctx context.Context, filter Filter) ([]*Event, error) return events, nil } +// Count sends a "COUNT" command to the relay and returns the count of events matching the filters. func (r *Relay) Count( ctx context.Context, filters Filters, @@ -534,6 +545,7 @@ func (r *Relay) countInternal(ctx context.Context, filters Filters, opts ...Subs } } +// Close closes the relay connection. func (r *Relay) Close() error { return r.close(errors.New("Close() called")) } diff --git a/sdk/event_relays.go b/sdk/event_relays.go index c014bf5..aedc687 100644 --- a/sdk/event_relays.go +++ b/sdk/event_relays.go @@ -10,6 +10,8 @@ import ( const eventRelayPrefix = byte('r') +// makeEventRelayKey creates a key for storing event relay information. +// It uses the first 8 bytes of the event ID to create a compact key. func makeEventRelayKey(eventID []byte) []byte { // format: 'r' + first 8 bytes of event ID key := make([]byte, 9) @@ -18,6 +20,8 @@ func makeEventRelayKey(eventID []byte) []byte { return key } +// encodeRelayList serializes a list of relay URLs into a compact binary format. +// Each relay URL is prefixed with its length as a single byte. func encodeRelayList(relays []string) []byte { totalSize := 0 for _, relay := range relays { @@ -43,6 +47,8 @@ func encodeRelayList(relays []string) []byte { return buf } +// decodeRelayList deserializes a binary-encoded list of relay URLs. +// It expects each relay URL to be prefixed with its length as a single byte. func decodeRelayList(data []byte) []string { relays := make([]string, 0, 6) offset := 0 @@ -67,6 +73,8 @@ func decodeRelayList(data []byte) []string { return relays } +// trackEventRelay records that an event was seen on a particular relay. +// If onlyIfItExists is true, it will only update existing records and not create new ones. func (sys *System) trackEventRelay(eventID string, relay string, onlyIfItExists bool) { // decode the event ID hex into bytes idBytes, err := hex.DecodeString(eventID) @@ -101,7 +109,8 @@ func (sys *System) trackEventRelay(eventID string, relay string, onlyIfItExists }) } -// GetEventRelays returns all known relay URLs that have been seen to carry the given event. +// GetEventRelays returns all known relay URLs an event is known to be available on. +// It is based on information kept on KVStore. func (sys *System) GetEventRelays(eventID string) ([]string, error) { // decode the event ID hex into bytes idBytes, err := hex.DecodeString(eventID) diff --git a/sdk/helpers.go b/sdk/helpers.go index 45139d6..91b54ea 100644 --- a/sdk/helpers.go +++ b/sdk/helpers.go @@ -9,6 +9,8 @@ import ( var json = jsoniter.ConfigFastest +// appendUnique adds items to an array only if they don't already exist in the array. +// Returns the modified array. func appendUnique[I comparable](arr []I, item ...I) []I { for _, item := range item { if slices.Contains(arr, item) { @@ -19,10 +21,19 @@ func appendUnique[I comparable](arr []I, item ...I) []I { return arr } +// doThisNotMoreThanOnceAnHour checks if an operation with the given key +// has been performed in the last hour. If not, it returns true and records +// the operation to prevent it from running again within the hour. func doThisNotMoreThanOnceAnHour(key string) (doItNow bool) { + _dtnmtoahLock.Lock() + defer _dtnmtoahLock.Unlock() + if _dtnmtoah == nil { + // this runs only once for the lifetime of this library and + // starts a long-running process of checking for expired items + // and deleting them from this map every 10 minutes. + _dtnmtoah = make(map[string]time.Time) go func() { - _dtnmtoah = make(map[string]time.Time) for { time.Sleep(time.Minute * 10) _dtnmtoahLock.Lock() @@ -37,9 +48,11 @@ func doThisNotMoreThanOnceAnHour(key string) (doItNow bool) { }() } - _dtnmtoahLock.Lock() - defer _dtnmtoahLock.Unlock() + _, hasBeenPerformedInTheLastHour := _dtnmtoah[key] + if hasBeenPerformedInTheLastHour { + return false + } - _, exists := _dtnmtoah[key] - return !exists + _dtnmtoah[key] = time.Now() + return true } diff --git a/sdk/metadata.go b/sdk/metadata.go index f3bcccb..fa3569c 100644 --- a/sdk/metadata.go +++ b/sdk/metadata.go @@ -11,6 +11,8 @@ import ( "github.com/nbd-wtf/go-nostr/sdk/hints" ) +// ProfileMetadata represents user profile information from kind 0 events. +// It contains both the raw event and parsed metadata fields. type ProfileMetadata struct { PubKey string `json:"-"` // must always be set otherwise things will break Event *nostr.Event `json:"-"` // may be empty if a profile metadata event wasn't found @@ -29,21 +31,28 @@ type ProfileMetadata struct { nip05LastAttempt time.Time } +// Npub returns the NIP-19 npub encoding of the profile's public key. func (p ProfileMetadata) Npub() string { v, _ := nip19.EncodePublicKey(p.PubKey) return v } +// NpubShort returns a shortened version of the NIP-19 npub encoding, +// showing only the first 7 and last 5 characters. func (p ProfileMetadata) NpubShort() string { npub := p.Npub() return npub[0:7] + "…" + npub[58:] } +// Nprofile returns the NIP-19 nprofile encoding of the profile, +// including relay hints from the user's outbox. func (p ProfileMetadata) Nprofile(ctx context.Context, sys *System, nrelays int) string { v, _ := nip19.EncodeProfile(p.PubKey, sys.FetchOutboxRelays(ctx, p.PubKey, 2)) return v } +// ShortName returns the best available name for display purposes. +// It tries Name, then DisplayName, and falls back to a shortened npub. func (p ProfileMetadata) ShortName() string { if p.Name != "" { return p.Name @@ -54,6 +63,7 @@ func (p ProfileMetadata) ShortName() string { return p.NpubShort() } +// NIP05Valid checks if the profile's NIP-05 identifier is valid. func (p *ProfileMetadata) NIP05Valid(ctx context.Context) bool { if p.NIP05 == "" { return false @@ -74,7 +84,8 @@ func (p *ProfileMetadata) NIP05Valid(ctx context.Context) bool { } // FetchProfileFromInput takes an nprofile, npub, nip05 or hex pubkey and returns a ProfileMetadata, -// updating the hintsDB in the process with any eventual relay hints +// updating the hintsDB in the process with any eventual relay hints. +// Returns an error if the profile reference couldn't be decoded. func (sys System) FetchProfileFromInput(ctx context.Context, nip19OrNip05Code string) (ProfileMetadata, error) { p := InputToProfile(ctx, nip19OrNip05Code) if p == nil { @@ -93,6 +104,7 @@ func (sys System) FetchProfileFromInput(ctx context.Context, nip19OrNip05Code st // FetchProfileMetadata fetches metadata for a given user from the local cache, or from the local store, // or, failing these, from the target user's defined outbox relays -- then caches the result. +// It always returns a ProfileMetadata, even if no metadata was found (in which case only the PubKey field is set). func (sys *System) FetchProfileMetadata(ctx context.Context, pubkey string) (pm ProfileMetadata) { if v, ok := sys.MetadataCache.Get(pubkey); ok { return v @@ -160,6 +172,8 @@ func (sys *System) tryFetchMetadataFromNetwork(ctx context.Context, pubkey strin return &pm } +// ParseMetadata parses a kind 0 event into a ProfileMetadata struct. +// Returns an error if the event is not kind 0 or if the content is not valid JSON. func ParseMetadata(event *nostr.Event) (meta ProfileMetadata, err error) { if event.Kind != 0 { err = fmt.Errorf("event %s is kind %d, not 0", event.ID, event.Kind) diff --git a/sdk/specific_event.go b/sdk/specific_event.go index 10c6c8f..274ccac 100644 --- a/sdk/specific_event.go +++ b/sdk/specific_event.go @@ -11,12 +11,19 @@ import ( "github.com/nbd-wtf/go-nostr/sdk/hints" ) +// FetchSpecificEventParameters contains options for fetching specific events. type FetchSpecificEventParameters struct { - WithRelays bool + // WithRelays indicates whether to include relay information in the response + // (this causes the request to take longer as it will wait for all relays to respond). + WithRelays bool + + // SkipLocalStore indicates whether to skip checking the local store for the event + // and storing the result in the local store. SkipLocalStore bool } -// FetchSpecificEventFromInput tries to get a specific event from a NIP-19 code using whatever means necessary. +// FetchSpecificEventFromInput tries to get a specific event from a NIP-19 code or event ID. +// It supports nevent, naddr, and note NIP-19 codes, as well as raw event IDs. func (sys *System) FetchSpecificEventFromInput( ctx context.Context, input string, @@ -47,7 +54,9 @@ func (sys *System) FetchSpecificEventFromInput( return sys.FetchSpecificEvent(ctx, pointer, params) } -// FetchSpecificEvent tries to get a specific event from a NIP-19 code using whatever means necessary. +// FetchSpecificEvent tries to get a specific event using a Pointer (EventPointer or EntityPointer). +// It first checks the local store, then queries relays associated with the event or author, +// and finally falls back to general-purpose relays. func (sys *System) FetchSpecificEvent( ctx context.Context, pointer nostr.Pointer, diff --git a/sdk/system.go b/sdk/system.go index 525d741..5f2183c 100644 --- a/sdk/system.go +++ b/sdk/system.go @@ -16,6 +16,16 @@ import ( kvstore_memory "github.com/nbd-wtf/go-nostr/sdk/kvstore/memory" ) +// System represents the core functionality of the SDK, providing access to +// various caches, relays, and dataloaders for efficient Nostr operations. +// +// Usually an application should have a single global instance of this and use +// its internal Pool for all its operations. +// +// Store, KVStore and Hints are databases that should generally be persisted +// for any application that is intended to be executed more than once. By +// default they're set to in-memory stores, but ideally persisteable +// implementations should be given (some alternatives are provided in subpackages). type System struct { KVStore kvstore.KVStore MetadataCache cache.Cache32[ProfileMetadata] @@ -47,22 +57,35 @@ type System struct { addressableLoaders []*dataloader.Loader[string, []*nostr.Event] } +// SystemModifier is a function that modifies a System instance. +// It's used with NewSystem to configure the system during creation. type SystemModifier func(sys *System) +// RelayStream provides a rotating list of relay URLs. +// It's used to distribute requests across multiple relays. type RelayStream struct { URLs []string serial int } +// NewRelayStream creates a new RelayStream with the provided URLs. func NewRelayStream(urls ...string) *RelayStream { return &RelayStream{URLs: urls, serial: rand.Int()} } +// Next returns the next URL in the rotation. func (rs *RelayStream) Next() string { rs.serial++ return rs.URLs[rs.serial%len(rs.URLs)] } +// NewSystem creates a new System with default configuration, +// which can be customized using the provided modifiers. +// +// The list of provided With* modifiers isn't exhaustive and +// most internal fields of System can be modified after the System +// creation -- and in many cases one or another of these will have +// to be modified, so don't be afraid of doing that. func NewSystem(mods ...SystemModifier) *System { sys := &System{ KVStore: kvstore_memory.NewStore(), @@ -123,84 +146,101 @@ func NewSystem(mods ...SystemModifier) *System { return sys } +// Close releases resources held by the System. func (sys *System) Close() { if sys.KVStore != nil { sys.KVStore.Close() } + if sys.Pool != nil { + sys.Pool.Close("sdk.System closed") + } } +// WithHintsDB returns a SystemModifier that sets the HintsDB. func WithHintsDB(hdb hints.HintsDB) SystemModifier { return func(sys *System) { sys.Hints = hdb } } +// WithRelayListRelays returns a SystemModifier that sets the RelayListRelays. func WithRelayListRelays(list []string) SystemModifier { return func(sys *System) { sys.RelayListRelays.URLs = list } } +// WithMetadataRelays returns a SystemModifier that sets the MetadataRelays. func WithMetadataRelays(list []string) SystemModifier { return func(sys *System) { sys.MetadataRelays.URLs = list } } +// WithFollowListRelays returns a SystemModifier that sets the FollowListRelays. func WithFollowListRelays(list []string) SystemModifier { return func(sys *System) { sys.FollowListRelays.URLs = list } } +// WithFallbackRelays returns a SystemModifier that sets the FallbackRelays. func WithFallbackRelays(list []string) SystemModifier { return func(sys *System) { sys.FallbackRelays.URLs = list } } +// WithJustIDRelays returns a SystemModifier that sets the JustIDRelays. func WithJustIDRelays(list []string) SystemModifier { return func(sys *System) { sys.JustIDRelays.URLs = list } } +// WithUserSearchRelays returns a SystemModifier that sets the UserSearchRelays. func WithUserSearchRelays(list []string) SystemModifier { return func(sys *System) { sys.UserSearchRelays.URLs = list } } +// WithNoteSearchRelays returns a SystemModifier that sets the NoteSearchRelays. func WithNoteSearchRelays(list []string) SystemModifier { return func(sys *System) { sys.NoteSearchRelays.URLs = list } } +// WithStore returns a SystemModifier that sets the Store. func WithStore(store eventstore.Store) SystemModifier { return func(sys *System) { sys.Store = store } } +// WithRelayListCache returns a SystemModifier that sets the RelayListCache. func WithRelayListCache(cache cache.Cache32[GenericList[Relay]]) SystemModifier { return func(sys *System) { sys.RelayListCache = cache } } +// WithFollowListCache returns a SystemModifier that sets the FollowListCache. func WithFollowListCache(cache cache.Cache32[GenericList[ProfileRef]]) SystemModifier { return func(sys *System) { sys.FollowListCache = cache } } +// WithMetadataCache returns a SystemModifier that sets the MetadataCache. func WithMetadataCache(cache cache.Cache32[ProfileMetadata]) SystemModifier { return func(sys *System) { sys.MetadataCache = cache } } +// WithKVStore returns a SystemModifier that sets the KVStore. func WithKVStore(store kvstore.KVStore) SystemModifier { return func(sys *System) { if sys.KVStore != nil { diff --git a/sdk/utils.go b/sdk/utils.go index cd932e9..5bb72c2 100644 --- a/sdk/utils.go +++ b/sdk/utils.go @@ -8,7 +8,7 @@ import ( ) var ( - _dtnmtoah map[string]time.Time + _dtnmtoah map[string]time.Time = make(map[string]time.Time) _dtnmtoahLock sync.Mutex ) @@ -21,6 +21,8 @@ func IsVirtualRelay(url string) bool { if strings.HasPrefix(url, "wss://feeds.nostr.band") || strings.HasPrefix(url, "wss://filter.nostr.wine") || + strings.HasPrefix(url, "ws://localhost") || + strings.HasPrefix(url, "ws://127.0.0.1") || strings.HasPrefix(url, "wss://cache") { return true } @@ -29,7 +31,7 @@ func IsVirtualRelay(url string) bool { } // PerQueryLimitInBatch tries to make an educated guess for the batch size given the total filter limit and -// the number of abstract queries we'll be conducting at the same time +// the number of abstract queries we'll be conducting at the same time. func PerQueryLimitInBatch(totalFilterLimit int, numberOfQueries int) int { if numberOfQueries == 1 || totalFilterLimit*numberOfQueries < 50 { return totalFilterLimit diff --git a/signature.go b/signature.go index 8e6b8e4..8164033 100644 --- a/signature.go +++ b/signature.go @@ -11,9 +11,9 @@ import ( "github.com/btcsuite/btcd/btcec/v2/schnorr" ) -// 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. +// CheckSignature checks if the event signature is valid for the given event. +// It won't look at the ID field, instead it will recompute the id from the entire event body. +// If the signature is invalid bool will be false and err will be set. func (evt Event) CheckSignature() (bool, error) { // read and check pubkey pk, err := hex.DecodeString(evt.PubKey) @@ -42,6 +42,8 @@ func (evt Event) CheckSignature() (bool, error) { } // Sign signs an event with a given privateKey. +// It sets the event's ID, PubKey, and Sig fields. +// Returns an error if the private key is invalid or if signing fails. func (evt *Event) Sign(secretKey string) error { s, err := hex.DecodeString(secretKey) if err != nil { diff --git a/signature_libsecp256k1.go b/signature_libsecp256k1.go index b4c59db..ab8955e 100644 --- a/signature_libsecp256k1.go +++ b/signature_libsecp256k1.go @@ -34,9 +34,6 @@ import ( "github.com/btcsuite/btcd/btcec/v2/schnorr" ) -// 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 (evt Event) CheckSignature() (bool, error) { var pk [32]byte _, err := hex.Decode(pk[:], []byte(evt.PubKey)) @@ -61,7 +58,6 @@ func (evt Event) CheckSignature() (bool, error) { return res == 1, nil } -// Sign signs an event with a given privateKey. func (evt *Event) Sign(secretKey string, signOpts ...schnorr.SignOption) error { sk, err := hex.DecodeString(secretKey) if err != nil { diff --git a/subscription.go b/subscription.go index 5d53a33..807de06 100644 --- a/subscription.go +++ b/subscription.go @@ -8,6 +8,7 @@ import ( "sync/atomic" ) +// Subscription represents a subscription to a relay. type Subscription struct { counter int64 id string @@ -46,13 +47,7 @@ type Subscription struct { storedwg sync.WaitGroup } -type EventMessage struct { - Event Event - Relay string -} - -// When instantiating relay connections, some options may be passed. -// SubscriptionOption is the type of the argument passed for that. +// SubscriptionOption is the type of the argument passed when instantiating relay connections. // Some examples are WithLabel. type SubscriptionOption interface { IsSubscriptionOption() @@ -85,6 +80,7 @@ func (sub *Subscription) start() { sub.mu.Unlock() } +// GetID returns the subscription ID. func (sub *Subscription) GetID() string { return sub.id } func (sub *Subscription) dispatchEvent(evt *Event) { @@ -121,6 +117,7 @@ func (sub *Subscription) dispatchEose() { } } +// handleClosed handles the CLOSED message from a relay. func (sub *Subscription) handleClosed(reason string) { go func() { sub.ClosedReason <- reason @@ -135,6 +132,7 @@ func (sub *Subscription) Unsub() { sub.unsub(errors.New("Unsub() called")) } +// unsub is the internal implementation of Unsub. func (sub *Subscription) unsub(err error) { // cancel the context (if it's not canceled already) sub.cancel(err) diff --git a/utils.go b/utils.go index 320a631..4ccf3cc 100644 --- a/utils.go +++ b/utils.go @@ -7,6 +7,7 @@ import ( "strings" ) +// IsValidRelayURL checks if a URL is a valid relay URL (ws:// or wss://). func IsValidRelayURL(u string) bool { parsed, err := url.Parse(u) if err != nil { @@ -18,6 +19,7 @@ func IsValidRelayURL(u string) bool { return true } +// IsValid32ByteHex checks if a string is a valid 32-byte hex string. func IsValid32ByteHex(thing string) bool { if !isLowerHex(thing) { return false @@ -29,6 +31,7 @@ func IsValid32ByteHex(thing string) bool { return err == nil } +// CompareEvent is meant to to be used with slices.Sort func CompareEvent(a, b Event) int { if a.CreatedAt == b.CreatedAt { return strings.Compare(a.ID, b.ID) @@ -36,6 +39,7 @@ func CompareEvent(a, b Event) int { return cmp.Compare(a.CreatedAt, b.CreatedAt) } +// CompareEventReverse is meant to to be used with slices.Sort func CompareEventReverse(b, a Event) int { if a.CreatedAt == b.CreatedAt { return strings.Compare(a.ID, b.ID) @@ -43,6 +47,7 @@ func CompareEventReverse(b, a Event) int { return cmp.Compare(a.CreatedAt, b.CreatedAt) } +// CompareEventPtr is meant to to be used with slices.Sort func CompareEventPtr(a, b *Event) int { if a == nil { if b == nil { @@ -60,6 +65,7 @@ func CompareEventPtr(a, b *Event) int { return cmp.Compare(a.CreatedAt, b.CreatedAt) } +// CompareEventPtrReverse is meant to to be used with slices.Sort func CompareEventPtrReverse(b, a *Event) int { if a == nil { if b == nil {