mirror of
https://github.com/asmogo/nws.git
synced 2025-01-18 10:01:33 +00:00
commit
187ce54d12
125
netstr/conn.go
125
netstr/conn.go
@ -6,7 +6,13 @@ import (
|
|||||||
"encoding/base32"
|
"encoding/base32"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/asmogo/nws/protocol"
|
"github.com/asmogo/nws/protocol"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@ -14,10 +20,6 @@ import (
|
|||||||
"github.com/nbd-wtf/go-nostr/nip04"
|
"github.com/nbd-wtf/go-nostr/nip04"
|
||||||
"github.com/nbd-wtf/go-nostr/nip19"
|
"github.com/nbd-wtf/go-nostr/nip19"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
"log/slog"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NostrConnection implements the net.Conn interface.
|
// NostrConnection implements the net.Conn interface.
|
||||||
@ -50,13 +52,13 @@ type NostrConnection struct {
|
|||||||
// It is used to write incoming events which will be read and processed by the Read method.
|
// It is used to write incoming events which will be read and processed by the Read method.
|
||||||
subscriptionChan chan nostr.IncomingEvent
|
subscriptionChan chan nostr.IncomingEvent
|
||||||
|
|
||||||
// readIds represents the list of event IDs that have been read by the NostrConnection object.
|
// readIDs represents the list of event IDs that have been read by the NostrConnection object.
|
||||||
readIds []string
|
readIDs []string
|
||||||
|
|
||||||
// writeIds is a field of type []string in the NostrConnection struct.
|
// writeIDs is a field of type []string in the NostrConnection struct.
|
||||||
// It stores the IDs of the events that have been written to the connection.
|
// It stores the IDs of the events that have been written to the connection.
|
||||||
// This field is used to check if an event has already been written and avoid duplicate writes.
|
// This field is used to check if an event has already been written and avoid duplicate writes.
|
||||||
writeIds []string
|
writeIDs []string
|
||||||
|
|
||||||
// sentBytes is a field that stores the bytes of data that have been sent by the connection.
|
// sentBytes is a field that stores the bytes of data that have been sent by the connection.
|
||||||
sentBytes [][]byte
|
sentBytes [][]byte
|
||||||
@ -67,6 +69,8 @@ type NostrConnection struct {
|
|||||||
targetPublicKey string
|
targetPublicKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errContextCanceled = errors.New("context canceled")
|
||||||
|
|
||||||
// WriteNostrEvent writes the incoming event to the subscription channel of the NostrConnection.
|
// WriteNostrEvent writes the incoming event to the subscription channel of the NostrConnection.
|
||||||
// The subscription channel is used by the Read method to read events and handle them.
|
// The subscription channel is used by the Read method to read events and handle them.
|
||||||
// Parameters:
|
// Parameters:
|
||||||
@ -78,7 +82,6 @@ func (nc *NostrConnection) WriteNostrEvent(event nostr.IncomingEvent) {
|
|||||||
// NewConnection creates a new NostrConnection object with the provided context and options.
|
// NewConnection creates a new NostrConnection object with the provided context and options.
|
||||||
// It initializes the config with default values, processes the options to customize the config,
|
// It initializes the config with default values, processes the options to customize the config,
|
||||||
// and creates a new NostrConnection object using the config.
|
// and creates a new NostrConnection object using the config.
|
||||||
// The NostrConnection object includes the privateKey, dst, pool, ctx, cancel, sub, subscriptionChan, readIds, and sentBytes fields.
|
|
||||||
// If an uuid is provided in the options, it is assigned to the NostrConnection object.
|
// If an uuid is provided in the options, it is assigned to the NostrConnection object.
|
||||||
// The NostrConnection object is then returned.
|
// The NostrConnection object is then returned.
|
||||||
func NewConnection(ctx context.Context, opts ...NostrConnOption) *NostrConnection {
|
func NewConnection(ctx context.Context, opts ...NostrConnOption) *NostrConnection {
|
||||||
@ -88,7 +91,7 @@ func NewConnection(ctx context.Context, opts ...NostrConnOption) *NostrConnectio
|
|||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: c,
|
cancel: c,
|
||||||
subscriptionChan: make(chan nostr.IncomingEvent),
|
subscriptionChan: make(chan nostr.IncomingEvent),
|
||||||
readIds: make([]string, 0),
|
readIDs: make([]string, 0),
|
||||||
sentBytes: make([][]byte, 0),
|
sentBytes: make([][]byte, 0),
|
||||||
}
|
}
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
@ -104,8 +107,8 @@ func NewConnection(ctx context.Context, opts ...NostrConnOption) *NostrConnectio
|
|||||||
//
|
//
|
||||||
// The number of bytes read is returned as n and any error encountered is returned as err.
|
// The number of bytes read is returned as n and any error encountered is returned as err.
|
||||||
// The content of the decrypted message is then copied to the provided byte slice b.
|
// The content of the decrypted message is then copied to the provided byte slice b.
|
||||||
func (nc *NostrConnection) Read(b []byte) (n int, err error) {
|
func (nc *NostrConnection) Read(b []byte) (int, error) {
|
||||||
return nc.handleNostrRead(b, n)
|
return nc.handleNostrRead(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleNostrRead reads the incoming events from the subscription channel and processes them.
|
// handleNostrRead reads the incoming events from the subscription channel and processes them.
|
||||||
@ -113,7 +116,7 @@ func (nc *NostrConnection) Read(b []byte) (n int, err error) {
|
|||||||
// unmarshals the decoded message and copies the content into the provided byte slice.
|
// unmarshals the decoded message and copies the content into the provided byte slice.
|
||||||
// It returns the number of bytes copied and any error encountered.
|
// It returns the number of bytes copied and any error encountered.
|
||||||
// If the context is canceled, it returns an error with "context canceled" message.
|
// If the context is canceled, it returns an error with "context canceled" message.
|
||||||
func (nc *NostrConnection) handleNostrRead(b []byte, n int) (int, error) {
|
func (nc *NostrConnection) handleNostrRead(buffer []byte) (int, error) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case event := <-nc.subscriptionChan:
|
case event := <-nc.subscriptionChan:
|
||||||
@ -121,27 +124,30 @@ func (nc *NostrConnection) handleNostrRead(b []byte, n int) (int, error) {
|
|||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
// check if we have already read this event
|
// check if we have already read this event
|
||||||
if lo.Contains(nc.readIds, event.ID) {
|
if lo.Contains(nc.readIDs, event.ID) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
nc.readIds = append(nc.readIds, event.ID)
|
nc.readIDs = append(nc.readIDs, event.ID)
|
||||||
sharedKey, err := nip04.ComputeSharedSecret(event.PubKey, nc.privateKey)
|
sharedKey, err := nip04.ComputeSharedSecret(event.PubKey, nc.privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, fmt.Errorf("could not compute shared key: %w", err)
|
||||||
}
|
}
|
||||||
decodedMessage, err := nip04.Decrypt(event.Content, sharedKey)
|
decodedMessage, err := nip04.Decrypt(event.Content, sharedKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, fmt.Errorf("could not decrypt message: %w", err)
|
||||||
}
|
}
|
||||||
message, err := protocol.UnmarshalJSON([]byte(decodedMessage))
|
message, err := protocol.UnmarshalJSON([]byte(decodedMessage))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, fmt.Errorf("could not unmarshal message: %w", err)
|
||||||
}
|
}
|
||||||
slog.Debug("reading", slog.String("event", event.ID), slog.String("content", base64.StdEncoding.EncodeToString(message.Data)))
|
slog.Debug("reading",
|
||||||
n = copy(b, message.Data)
|
slog.String("event", event.ID),
|
||||||
|
slog.String("content", base64.StdEncoding.EncodeToString(message.Data)),
|
||||||
|
)
|
||||||
|
n := copy(buffer, message.Data)
|
||||||
return n, nil
|
return n, nil
|
||||||
case <-nc.ctx.Done():
|
case <-nc.ctx.Done():
|
||||||
return 0, fmt.Errorf("context canceled")
|
return 0, errContextCanceled
|
||||||
default:
|
default:
|
||||||
time.Sleep(time.Millisecond * 100)
|
time.Sleep(time.Millisecond * 100)
|
||||||
}
|
}
|
||||||
@ -155,43 +161,58 @@ func (nc *NostrConnection) Write(b []byte) (int, error) {
|
|||||||
return nc.handleNostrWrite(b)
|
return nc.handleNostrWrite(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleNostrWrite handles the writing of a Nostr event.
|
// Go lang
|
||||||
// It checks if the event has already been sent, parses the destination,
|
func (nc *NostrConnection) handleNostrWrite(buffer []byte) (int, error) {
|
||||||
// creates a message signer, creates message options, signs the event,
|
|
||||||
// publishes the event to relays, and appends the sent bytes to the connection's sentBytes array.
|
|
||||||
// The method returns the number of bytes written and any error that occurred.
|
|
||||||
func (nc *NostrConnection) handleNostrWrite(b []byte) (int, error) {
|
|
||||||
// check if we have already sent this event
|
|
||||||
publicKey, relays, err := nc.parseDestination()
|
publicKey, relays, err := nc.parseDestination()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, fmt.Errorf("could not parse destination: %w", err)
|
||||||
}
|
}
|
||||||
signer, err := protocol.NewEventSigner(nc.privateKey)
|
signer, err := protocol.NewEventSigner(nc.privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, fmt.Errorf("could not create event signer: %w", err)
|
||||||
}
|
}
|
||||||
// create message options
|
signedEvent, err := nc.createSignedEvent(signer, buffer, publicKey, relays)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("could not create signed event: %w", err)
|
||||||
|
}
|
||||||
|
err = nc.publishEventToRelays(signedEvent, relays)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("could not publish event to relays: %w", err)
|
||||||
|
}
|
||||||
|
nc.appendSentBytes(buffer)
|
||||||
|
slog.Debug("writing",
|
||||||
|
slog.String("event", signedEvent.ID),
|
||||||
|
slog.String("content", base64.StdEncoding.EncodeToString(buffer)),
|
||||||
|
)
|
||||||
|
return len(buffer), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NostrConnection) createSignedEvent(
|
||||||
|
signer *protocol.EventSigner,
|
||||||
|
b []byte,
|
||||||
|
publicKey string,
|
||||||
|
relays []string,
|
||||||
|
) (nostr.Event, error) {
|
||||||
opts := []protocol.MessageOption{
|
opts := []protocol.MessageOption{
|
||||||
protocol.WithUUID(nc.uuid),
|
protocol.WithUUID(nc.uuid),
|
||||||
protocol.WithType(protocol.MessageTypeSocks5),
|
protocol.WithType(protocol.MessageTypeSocks5),
|
||||||
protocol.WithDestination(nc.dst),
|
protocol.WithDestination(nc.dst),
|
||||||
protocol.WithData(b),
|
protocol.WithData(b),
|
||||||
}
|
}
|
||||||
ev, err := signer.CreateSignedEvent(
|
signedEvent, err := signer.CreateSignedEvent(
|
||||||
publicKey,
|
publicKey,
|
||||||
protocol.KindEphemeralEvent,
|
protocol.KindEphemeralEvent,
|
||||||
nostr.Tags{nostr.Tag{"p", publicKey}},
|
nostr.Tags{nostr.Tag{"p", publicKey}},
|
||||||
opts...,
|
opts...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return signedEvent, fmt.Errorf("could not create signed event: %w", err)
|
||||||
}
|
}
|
||||||
if lo.Contains(nc.writeIds, ev.ID) {
|
if lo.Contains(nc.writeIDs, signedEvent.ID) {
|
||||||
slog.Info("event already sent", slog.String("event", ev.ID))
|
slog.Info("event already sent", slog.String("event", signedEvent.ID))
|
||||||
return 0, nil
|
return signedEvent, nil
|
||||||
}
|
}
|
||||||
nc.writeIds = append(nc.writeIds, ev.ID)
|
nc.writeIDs = append(nc.writeIDs, signedEvent.ID)
|
||||||
|
|
||||||
if nc.sub {
|
if nc.sub {
|
||||||
nc.sub = false
|
nc.sub = false
|
||||||
now := nostr.Now()
|
now := nostr.Now()
|
||||||
@ -202,27 +223,33 @@ func (nc *NostrConnection) handleNostrWrite(b []byte) (int, error) {
|
|||||||
Authors: []string{publicKey},
|
Authors: []string{publicKey},
|
||||||
Since: &now,
|
Since: &now,
|
||||||
Tags: nostr.TagMap{
|
Tags: nostr.TagMap{
|
||||||
"p": []string{ev.PubKey},
|
"p": []string{signedEvent.PubKey},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
nc.subscriptionChan = incomingEventChannel
|
nc.subscriptionChan = incomingEventChannel
|
||||||
}
|
}
|
||||||
|
return signedEvent, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NostrConnection) publishEventToRelays(ev nostr.Event, relays []string) error {
|
||||||
for _, responseRelay := range relays {
|
for _, responseRelay := range relays {
|
||||||
var relay *nostr.Relay
|
var relay *nostr.Relay
|
||||||
relay, err = nc.pool.EnsureRelay(responseRelay)
|
relay, err := nc.pool.EnsureRelay(responseRelay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return fmt.Errorf("could not ensure relay: %w", err)
|
||||||
}
|
}
|
||||||
err = relay.Publish(nc.ctx, ev)
|
err = relay.Publish(nc.ctx, ev)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return fmt.Errorf("could not publish event to relay: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NostrConnection) appendSentBytes(b []byte) {
|
||||||
nc.sentBytes = append(nc.sentBytes, b)
|
nc.sentBytes = append(nc.sentBytes, b)
|
||||||
slog.Debug("writing", slog.String("event", ev.ID), slog.String("content", base64.StdEncoding.EncodeToString(b)))
|
|
||||||
return len(b), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseDestination takes a destination string and returns a public key and relays.
|
// parseDestination takes a destination string and returns a public key and relays.
|
||||||
@ -237,7 +264,7 @@ func (nc *NostrConnection) parseDestination() (string, []string, error) {
|
|||||||
prefix, pubKey, err := nip19.Decode(nc.dst)
|
prefix, pubKey, err := nip19.Decode(nc.dst)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, fmt.Errorf("could not decode destination: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var relays []string
|
var relays []string
|
||||||
@ -278,7 +305,7 @@ func (nc *NostrConnection) parseDestinationDomain() (string, []string, error) {
|
|||||||
}*/
|
}*/
|
||||||
return nc.targetPublicKey, nc.defaultRelays, nil
|
return nc.targetPublicKey, nc.defaultRelays, nil
|
||||||
}
|
}
|
||||||
var subdomains []string
|
subdomains := make([]string, 0)
|
||||||
split := strings.Split(url.SubName, ".")
|
split := strings.Split(url.SubName, ".")
|
||||||
for _, subdomain := range split {
|
for _, subdomain := range split {
|
||||||
decodedSubDomain, err := base32.HexEncoding.WithPadding(base32.NoPadding).DecodeString(strings.ToUpper(subdomain))
|
decodedSubDomain, err := base32.HexEncoding.WithPadding(base32.NoPadding).DecodeString(strings.ToUpper(subdomain))
|
||||||
@ -315,15 +342,15 @@ func (nc *NostrConnection) RemoteAddr() net.Addr {
|
|||||||
return &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0}
|
return &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nc *NostrConnection) SetDeadline(t time.Time) error {
|
func (nc *NostrConnection) SetDeadline(_ time.Time) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nc *NostrConnection) SetReadDeadline(t time.Time) error {
|
func (nc *NostrConnection) SetReadDeadline(_ time.Time) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nc *NostrConnection) SetWriteDeadline(t time.Time) error {
|
func (nc *NostrConnection) SetWriteDeadline(_ time.Time) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package protocol
|
package protocol
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
@ -32,7 +33,7 @@ func NewEventSigner(privateKey string) (*EventSigner, error) {
|
|||||||
myPublicKey, err := nostr.GetPublicKey(privateKey)
|
myPublicKey, err := nostr.GetPublicKey(privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("could not generate pubkey")
|
slog.Error("could not generate pubkey")
|
||||||
return nil, err
|
return nil, fmt.Errorf("could not generate public key: %w", err)
|
||||||
}
|
}
|
||||||
signer := &EventSigner{
|
signer := &EventSigner{
|
||||||
privateKey: privateKey,
|
privateKey: privateKey,
|
||||||
@ -42,7 +43,7 @@ func NewEventSigner(privateKey string) (*EventSigner, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateEvent creates a new Event with the provided tags. The Public Key and the
|
// CreateEvent creates a new Event with the provided tags. The Public Key and the
|
||||||
// current timestamp are set automatically. The Kind is set to KindEphemeralEvent.
|
// current timestamp are set automatically. The Kind is set to KindEphemeralEvent
|
||||||
func (s *EventSigner) CreateEvent(kind int, tags nostr.Tags) nostr.Event {
|
func (s *EventSigner) CreateEvent(kind int, tags nostr.Tags) nostr.Event {
|
||||||
return nostr.Event{
|
return nostr.Event{
|
||||||
PubKey: s.PublicKey,
|
PubKey: s.PublicKey,
|
||||||
@ -59,26 +60,34 @@ func (s *EventSigner) CreateEvent(kind int, tags nostr.Tags) nostr.Event {
|
|||||||
// The method then calls CreateEvent to create a new unsigned event with the provided tags.
|
// The method then calls CreateEvent to create a new unsigned event with the provided tags.
|
||||||
// The encrypted message is set as the content of the event.
|
// The encrypted message is set as the content of the event.
|
||||||
// Finally, the event is signed with the private key of the EventSigner, setting the event ID and event Sig fields.
|
// Finally, the event is signed with the private key of the EventSigner, setting the event ID and event Sig fields.
|
||||||
// The signed event is returned along with any error that occurs.
|
// The signed event is returned along with any error that occurs
|
||||||
func (s *EventSigner) CreateSignedEvent(targetPublicKey string, kind int, tags nostr.Tags, opts ...MessageOption) (nostr.Event, error) {
|
func (s *EventSigner) CreateSignedEvent(
|
||||||
|
targetPublicKey string,
|
||||||
|
kind int,
|
||||||
|
tags nostr.Tags,
|
||||||
|
opts ...MessageOption,
|
||||||
|
) (nostr.Event, error) {
|
||||||
sharedKey, err := nip04.ComputeSharedSecret(targetPublicKey, s.privateKey)
|
sharedKey, err := nip04.ComputeSharedSecret(targetPublicKey, s.privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nostr.Event{}, err
|
return nostr.Event{}, fmt.Errorf("could not compute shared key: %w", err)
|
||||||
}
|
}
|
||||||
message := NewMessage(
|
message := NewMessage(
|
||||||
opts...,
|
opts...,
|
||||||
)
|
)
|
||||||
messageJson, err := MarshalJSON(message)
|
messageJSON, err := MarshalJSON(message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nostr.Event{}, err
|
return nostr.Event{}, fmt.Errorf("could not marshal message: %w", err)
|
||||||
}
|
}
|
||||||
encryptedMessage, err := nip04.Encrypt(string(messageJson), sharedKey)
|
encryptedMessage, err := nip04.Encrypt(string(messageJSON), sharedKey)
|
||||||
ev := s.CreateEvent(kind, tags)
|
if err != nil {
|
||||||
ev.Content = encryptedMessage
|
return nostr.Event{}, fmt.Errorf("could not encrypt message: %w", err)
|
||||||
|
}
|
||||||
|
event := s.CreateEvent(kind, tags)
|
||||||
|
event.Content = encryptedMessage
|
||||||
// calling Sign sets the event ID field and the event Sig field
|
// calling Sign sets the event ID field and the event Sig field
|
||||||
err = ev.Sign(s.privateKey)
|
err = event.Sign(s.privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nostr.Event{}, err
|
return nostr.Event{}, fmt.Errorf("could not sign event: %w", err)
|
||||||
}
|
}
|
||||||
return ev, nil
|
return event, nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user