mirror of
https://github.com/nbd-wtf/go-nostr.git
synced 2025-06-24 15:52:25 +02:00
upgrade interfaces, add local filtering for subscriptions and some other things I forgot. also a README with examples.
This commit is contained in:
parent
7dfc354eec
commit
0a60285e30
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
go-nostr
|
75
README.md
Normal file
75
README.md
Normal file
@ -0,0 +1,75 @@
|
||||
go-nostr
|
||||
========
|
||||
|
||||
A set of useful things for [Nostr Protocol](https://github.com/fiatjaf/nostr) implementations.
|
||||
|
||||
<a href="https://godoc.org/github.com/fiatjaf/go-nostr"><img src="https://img.shields.io/badge/api-reference-blue.svg?style=flat-square" alt="GoDoc"></a>
|
||||
|
||||
|
||||
### Subscribing to a set of relays
|
||||
|
||||
```go
|
||||
pool := relaypool.New()
|
||||
|
||||
pool.Add("wss://relay.nostr.com/", &relaypool.Policy{
|
||||
SimplePolicy: relaypool.SimplePolicy{Read: true, Write: true},
|
||||
})
|
||||
pool.Add("wss://nostrrelay.example.com/", &relaypool.Policy{
|
||||
SimplePolicy: relaypool.SimplePolicy{Read: true, Write: true},
|
||||
})
|
||||
|
||||
for notice := range pool.Notices {
|
||||
log.Printf("%s has sent a notice: '%s'\n", notice.Relay, notice.Message)
|
||||
}
|
||||
```
|
||||
|
||||
### Listening for events
|
||||
|
||||
```go
|
||||
kind1 := event.KindTextNote
|
||||
sub := pool.Sub(filter.EventFilters{
|
||||
{
|
||||
Authors: []string{"0ded86bf80c76847320b16f22b7451c08169434837a51ad5fe3b178af6c35f5d"},
|
||||
Kind: &kind1, // or 1
|
||||
},
|
||||
})
|
||||
|
||||
go func() {
|
||||
for event := range sub.UniqueEvents {
|
||||
log.Print(event)
|
||||
}
|
||||
}()
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
sub.Unsub()
|
||||
```
|
||||
|
||||
### Publishing an event
|
||||
|
||||
```go
|
||||
secretKey := "3f06a81e0a0c2ad34ee9df2a30d87a810da9e3c3881f780755ace5e5e64d30a7"
|
||||
|
||||
pool.SecretKey = &secretKey
|
||||
|
||||
event, statuses, _ := pool.PublishEvent(&event.Event{
|
||||
CreatedAt: uint32(time.Now().Unix()),
|
||||
Kind: 1, // or event.KindTextNote
|
||||
Tags: make(event.Tags, 0),
|
||||
Content: "hello",
|
||||
})
|
||||
|
||||
log.Print(event.PubKey)
|
||||
log.Print(event.ID)
|
||||
log.Print(event.Sig)
|
||||
|
||||
for status := range statuses {
|
||||
switch status.Status {
|
||||
case relaypool.PublishStatusSent:
|
||||
fmt.Printf("Sent event %s to '%s'.\n", event.ID, status.Relay)
|
||||
case relaypool.PublishStatusFailed:
|
||||
fmt.Printf("Failed to send event %s to '%s'.\n", event.ID, status.Relay)
|
||||
case relaypool.PublishStatusSucceeded:
|
||||
fmt.Printf("Event seen %s on '%s'.\n", event.ID, status.Relay)
|
||||
}
|
||||
}
|
||||
```
|
@ -8,7 +8,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/fiatjaf/bip340"
|
||||
)
|
||||
@ -95,12 +94,9 @@ func (evt *Event) Serialize() []byte {
|
||||
// returns an error if the signature itself is invalid.
|
||||
func (evt Event) CheckSignature() (bool, error) {
|
||||
// read and check pubkey
|
||||
pubkeyb, err := hex.DecodeString(evt.PubKey)
|
||||
pubkey, err := bip340.ParsePublicKey(evt.PubKey)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(pubkeyb) != 32 {
|
||||
return false, fmt.Errorf("pubkey must be 32 bytes, not %d", len(pubkeyb))
|
||||
return false, fmt.Errorf("Event has invalid pubkey '%s': %w", evt.PubKey, err)
|
||||
}
|
||||
|
||||
// check tags
|
||||
@ -116,32 +112,28 @@ func (evt Event) CheckSignature() (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
sig, err := hex.DecodeString(evt.Sig)
|
||||
s, err := hex.DecodeString(evt.Sig)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("signature is invalid hex: %w", err)
|
||||
}
|
||||
if len(sig) != 64 {
|
||||
return false, fmt.Errorf("signature must be 64 bytes, not %d", len(sig))
|
||||
if len(s) != 64 {
|
||||
return false, fmt.Errorf("signature must be 64 bytes, not %d", len(s))
|
||||
}
|
||||
|
||||
var p [32]byte
|
||||
copy(p[:], pubkeyb)
|
||||
var sig [64]byte
|
||||
copy(sig[:], s)
|
||||
|
||||
var s [64]byte
|
||||
copy(s[:], sig)
|
||||
|
||||
h := sha256.Sum256(evt.Serialize())
|
||||
|
||||
return bip340.Verify(p, h, s)
|
||||
hash := sha256.Sum256(evt.Serialize())
|
||||
return bip340.Verify(pubkey, hash, sig)
|
||||
}
|
||||
|
||||
// Sign signs an event with a given privateKey
|
||||
func (evt *Event) Sign(privateKey string) error {
|
||||
h := sha256.Sum256(evt.Serialize())
|
||||
s, _ := new(big.Int).SetString(privateKey, 16)
|
||||
|
||||
if s == nil {
|
||||
return errors.New("invalid private key " + privateKey)
|
||||
s, err := bip340.ParsePrivateKey(privateKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Sign called with invalid private key '%s': %w", privateKey, err)
|
||||
}
|
||||
|
||||
aux := make([]byte, 32)
|
||||
|
@ -2,6 +2,8 @@ package filter
|
||||
|
||||
import "github.com/fiatjaf/go-nostr/event"
|
||||
|
||||
type EventFilters []EventFilter
|
||||
|
||||
type EventFilter struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Kind *int `json:"kind,omitempty"`
|
||||
@ -11,6 +13,16 @@ type EventFilter struct {
|
||||
Since uint32 `json:"since,omitempty"`
|
||||
}
|
||||
|
||||
func (eff EventFilters) Match(event *event.Event) bool {
|
||||
for _, filter := range eff {
|
||||
if filter.Matches(event) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (ef EventFilter) Matches(event *event.Event) bool {
|
||||
if event == nil {
|
||||
return false
|
||||
|
2
go.mod
2
go.mod
@ -3,6 +3,6 @@ module github.com/fiatjaf/go-nostr
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/fiatjaf/bip340 v1.0.0
|
||||
github.com/fiatjaf/bip340 v1.1.0
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
)
|
||||
|
4
go.sum
4
go.sum
@ -14,8 +14,8 @@ github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46f
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fiatjaf/bip340 v1.0.0 h1:mpwbm+0KC9BXB/7/pnac4e4N1TiuppyEVXxtVAXj75k=
|
||||
github.com/fiatjaf/bip340 v1.0.0/go.mod h1:MxAz+5FQUTW4OT2gnCBC6Our486wmqf72ykZIrh7+is=
|
||||
github.com/fiatjaf/bip340 v1.1.0 h1:W+CnUU3RyqgMKS2S9t/r2l3L4D+sSkRtU4la7MlVBR8=
|
||||
github.com/fiatjaf/bip340 v1.1.0/go.mod h1:MxAz+5FQUTW4OT2gnCBC6Our486wmqf72ykZIrh7+is=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/fiatjaf/bip340"
|
||||
"github.com/fiatjaf/go-nostr/event"
|
||||
"github.com/fiatjaf/go-nostr/filter"
|
||||
nostrutils "github.com/fiatjaf/go-nostr/utils"
|
||||
@ -121,11 +122,18 @@ func (r *RelayPool) Add(url string, policy *Policy) error {
|
||||
if subscription, ok := r.subscriptions[channel]; ok {
|
||||
var event event.Event
|
||||
json.Unmarshal(jsonMessage[2], &event)
|
||||
|
||||
// check signature of all received events, ignore invalid
|
||||
ok, _ := event.CheckSignature()
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// check if the event matches the desired filter, ignore otherwise
|
||||
if !subscription.filters.Match(&event) {
|
||||
continue
|
||||
}
|
||||
|
||||
subscription.Events <- EventMessage{
|
||||
Relay: nm,
|
||||
Event: event,
|
||||
@ -153,7 +161,7 @@ func (r *RelayPool) Remove(url string) {
|
||||
delete(r.websockets, nm)
|
||||
}
|
||||
|
||||
func (r *RelayPool) Sub(filter filter.EventFilter) *Subscription {
|
||||
func (r *RelayPool) Sub(filters filter.EventFilters) *Subscription {
|
||||
random := make([]byte, 7)
|
||||
rand.Read(random)
|
||||
|
||||
@ -170,17 +178,25 @@ func (r *RelayPool) Sub(filter filter.EventFilter) *Subscription {
|
||||
subscription.UniqueEvents = make(chan event.Event)
|
||||
r.subscriptions[subscription.channel] = &subscription
|
||||
|
||||
subscription.Sub(&filter)
|
||||
subscription.Sub(filters)
|
||||
return &subscription
|
||||
}
|
||||
|
||||
func (r *RelayPool) PublishEvent(evt *event.Event) (*event.Event, chan PublishStatus, error) {
|
||||
status := make(chan PublishStatus, 1)
|
||||
|
||||
if r.SecretKey == nil && evt.Sig == "" {
|
||||
if r.SecretKey == nil && (evt.PubKey == "" || evt.Sig == "") {
|
||||
return nil, status, errors.New("PublishEvent needs either a signed event to publish or to have been configured with a .SecretKey.")
|
||||
}
|
||||
|
||||
if evt.PubKey == "" {
|
||||
secretKeyN, err := bip340.ParsePrivateKey(*r.SecretKey)
|
||||
if err != nil {
|
||||
return nil, status, fmt.Errorf("The pool's global SecretKey is invalid: %w", err)
|
||||
}
|
||||
evt.PubKey = fmt.Sprintf("%x", bip340.GetPublicKey(secretKeyN))
|
||||
}
|
||||
|
||||
if evt.Sig == "" {
|
||||
err := evt.Sign(*r.SecretKey)
|
||||
if err != nil {
|
||||
@ -197,7 +213,7 @@ func (r *RelayPool) PublishEvent(evt *event.Event) (*event.Event, chan PublishSt
|
||||
}
|
||||
status <- PublishStatus{relay, PublishStatusSent}
|
||||
|
||||
subscription := r.Sub(filter.EventFilter{ID: evt.ID})
|
||||
subscription := r.Sub(filter.EventFilters{{ID: evt.ID}})
|
||||
for {
|
||||
select {
|
||||
case event := <-subscription.UniqueEvents:
|
||||
|
@ -10,8 +10,8 @@ type Subscription struct {
|
||||
channel string
|
||||
relays map[string]*websocket.Conn
|
||||
|
||||
filter *filter.EventFilter
|
||||
Events chan EventMessage
|
||||
filters filter.EventFilters
|
||||
Events chan EventMessage
|
||||
|
||||
started bool
|
||||
UniqueEvents chan event.Event
|
||||
@ -38,17 +38,17 @@ func (subscription Subscription) Unsub() {
|
||||
}
|
||||
}
|
||||
|
||||
func (subscription Subscription) Sub(filter *filter.EventFilter) {
|
||||
if filter != nil {
|
||||
subscription.filter = filter
|
||||
}
|
||||
|
||||
func (subscription Subscription) Sub(filters filter.EventFilters) {
|
||||
for _, ws := range subscription.relays {
|
||||
ws.WriteJSON([]interface{}{
|
||||
message := []interface{}{
|
||||
"REQ",
|
||||
subscription.channel,
|
||||
subscription.filter,
|
||||
})
|
||||
}
|
||||
for _, filter := range subscription.filters {
|
||||
message = append(message, filter)
|
||||
}
|
||||
|
||||
ws.WriteJSON(message)
|
||||
}
|
||||
|
||||
if !subscription.started {
|
||||
@ -79,9 +79,14 @@ func (subscription Subscription) removeRelay(relay string) {
|
||||
|
||||
func (subscription Subscription) addRelay(relay string, ws *websocket.Conn) {
|
||||
subscription.relays[relay] = ws
|
||||
ws.WriteJSON([]interface{}{
|
||||
|
||||
message := []interface{}{
|
||||
"REQ",
|
||||
subscription.channel,
|
||||
subscription.filter,
|
||||
})
|
||||
}
|
||||
for _, filter := range subscription.filters {
|
||||
message = append(message, filter)
|
||||
}
|
||||
|
||||
ws.WriteJSON(message)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user