a bunch of [32]byte conversions. still more needed.

This commit is contained in:
fiatjaf
2025-04-14 17:31:23 -03:00
parent 40535e6b19
commit b4268d649c
132 changed files with 857 additions and 879 deletions

View File

@@ -1,6 +1,6 @@
[![Run Tests](https://github.com/nbd-wtf/go-nostr/actions/workflows/test.yml/badge.svg)](https://github.com/nbd-wtf/go-nostr/actions/workflows/test.yml) [![Run Tests](https://fiatjaf.com/nostrlib/actions/workflows/test.yml/badge.svg)](https://fiatjaf.com/nostrlib/actions/workflows/test.yml)
[![Go Reference](https://pkg.go.dev/badge/github.com/nbd-wtf/go-nostr.svg)](https://pkg.go.dev/github.com/nbd-wtf/go-nostr) [![Go Reference](https://pkg.go.dev/badge/fiatjaf.com/nostrlib.svg)](https://pkg.go.dev/fiatjaf.com/nostrlib)
[![Go Report Card](https://goreportcard.com/badge/github.com/nbd-wtf/go-nostr)](https://goreportcard.com/report/github.com/nbd-wtf/go-nostr) [![Go Report Card](https://goreportcard.com/badge/fiatjaf.com/nostrlib)](https://goreportcard.com/report/fiatjaf.com/nostrlib)
<a href="https://nbd.wtf"><img align="right" height="196" src="https://user-images.githubusercontent.com/1653275/194609043-0add674b-dd40-41ed-986c-ab4a2e053092.png" /></a> <a href="https://nbd.wtf"><img align="right" height="196" src="https://user-images.githubusercontent.com/1653275/194609043-0add674b-dd40-41ed-986c-ab4a2e053092.png" /></a>
@@ -10,7 +10,7 @@ go-nostr
A set of useful things for [Nostr](https://github.com/nostr-protocol/nostr)-related software. A set of useful things for [Nostr](https://github.com/nostr-protocol/nostr)-related software.
```bash ```bash
go get github.com/nbd-wtf/go-nostr go get fiatjaf.com/nostrlib
``` ```
### Generating a key ### Generating a key
@@ -21,8 +21,8 @@ package main
import ( import (
"fmt" "fmt"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/nip19" "fiatjaf.com/nostrlib/nip19"
) )
func main() { func main() {

View File

@@ -13,7 +13,7 @@ import (
var defaultConnectionOptions = &ws.DialOptions{ var defaultConnectionOptions = &ws.DialOptions{
CompressionMode: ws.CompressionContextTakeover, CompressionMode: ws.CompressionContextTakeover,
HTTPHeader: http.Header{ HTTPHeader: http.Header{
textproto.CanonicalMIMEHeaderKey("User-Agent"): {"github.com/nbd-wtf/go-nostr"}, textproto.CanonicalMIMEHeaderKey("User-Agent"): {"fiatjaf.com/nostrlib"},
}, },
} }

View File

@@ -14,7 +14,7 @@ func TestCount(t *testing.T) {
defer rl.Close() defer rl.Close()
count, _, err := rl.Count(context.Background(), Filters{ count, _, err := rl.Count(context.Background(), Filters{
{Kinds: []int{KindFollowList}, Tags: TagMap{"p": []string{"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"}}}, {Kinds: []uint16{KindFollowList}, Tags: TagMap{"p": []string{"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"}}},
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.Greater(t, count, int64(0)) assert.Greater(t, count, int64(0))

View File

@@ -124,7 +124,7 @@ func (v EventEnvelope) MarshalJSON() ([]byte, error) {
// ReqEnvelope represents a REQ message. // ReqEnvelope represents a REQ message.
type ReqEnvelope struct { type ReqEnvelope struct {
SubscriptionID string SubscriptionID string
Filters Filter
} }
func (_ ReqEnvelope) Label() string { return "REQ" } func (_ ReqEnvelope) Label() string { return "REQ" }
@@ -136,13 +136,8 @@ func (v *ReqEnvelope) FromJSON(data string) error {
return fmt.Errorf("failed to decode REQ envelope: missing filters") return fmt.Errorf("failed to decode REQ envelope: missing filters")
} }
v.SubscriptionID = string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str))) v.SubscriptionID = string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str)))
v.Filters = make(Filters, len(arr)-2) if err := easyjson.Unmarshal(unsafe.Slice(unsafe.StringData(arr[2].Raw), len(arr[2].Raw)), &v.Filter); err != nil {
f := 0 return fmt.Errorf("on filter: %w", err)
for i := 2; i < len(arr); i++ {
if err := easyjson.Unmarshal(unsafe.Slice(unsafe.StringData(arr[i].Raw), len(arr[i].Raw)), &v.Filters[f]); err != nil {
return fmt.Errorf("%w -- on filter %d", err, f)
}
f++
} }
return nil return nil
@@ -152,11 +147,8 @@ func (v ReqEnvelope) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{NoEscapeHTML: true} w := jwriter.Writer{NoEscapeHTML: true}
w.RawString(`["REQ","`) w.RawString(`["REQ","`)
w.RawString(v.SubscriptionID) w.RawString(v.SubscriptionID)
w.RawString(`"`) w.RawString(`",`)
for _, filter := range v.Filters { v.Filter.MarshalEasyJSON(&w)
w.RawString(`,`)
filter.MarshalEasyJSON(&w)
}
w.RawString(`]`) w.RawString(`]`)
return w.BuildBytes() return w.BuildBytes()
} }
@@ -198,15 +190,9 @@ func (v *CountEnvelope) FromJSON(data string) error {
return nil return nil
} }
f := 0 item := unsafe.Slice(unsafe.StringData(arr[2].Raw), len(arr[2].Raw))
for i := 2; i < len(arr); i++ {
item := unsafe.Slice(unsafe.StringData(arr[i].Raw), len(arr[i].Raw))
if err := easyjson.Unmarshal(item, &v.Filter); err != nil { if err := easyjson.Unmarshal(item, &v.Filter); err != nil {
return fmt.Errorf("%w -- on filter %d", err, f) return fmt.Errorf("on filter: %w", err)
}
f++
} }
return nil return nil
@@ -216,7 +202,7 @@ func (v CountEnvelope) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{NoEscapeHTML: true} w := jwriter.Writer{NoEscapeHTML: true}
w.RawString(`["COUNT","`) w.RawString(`["COUNT","`)
w.RawString(v.SubscriptionID) w.RawString(v.SubscriptionID)
w.RawString(`",`) w.RawString(`"`)
if v.Count != nil { if v.Count != nil {
w.RawString(`{"count":`) w.RawString(`{"count":`)
w.RawString(strconv.FormatInt(*v.Count, 10)) w.RawString(strconv.FormatInt(*v.Count, 10))
@@ -357,7 +343,7 @@ func (v ClosedEnvelope) MarshalJSON() ([]byte, error) {
// OKEnvelope represents an OK message. // OKEnvelope represents an OK message.
type OKEnvelope struct { type OKEnvelope struct {
EventID string EventID ID
OK bool OK bool
Reason string Reason string
} }
@@ -374,7 +360,9 @@ func (v *OKEnvelope) FromJSON(data string) error {
if len(arr) < 4 { if len(arr) < 4 {
return fmt.Errorf("failed to decode OK envelope: missing fields") return fmt.Errorf("failed to decode OK envelope: missing fields")
} }
v.EventID = string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str))) if _, err := hex.Decode(v.EventID[:], []byte(arr[1].Str)); err != nil {
return err
}
v.OK = arr[2].Raw == "true" v.OK = arr[2].Raw == "true"
v.Reason = string(unsafe.Slice(unsafe.StringData(arr[3].Str), len(arr[3].Str))) v.Reason = string(unsafe.Slice(unsafe.StringData(arr[3].Str), len(arr[3].Str)))
@@ -384,7 +372,7 @@ func (v *OKEnvelope) FromJSON(data string) error {
func (v OKEnvelope) MarshalJSON() ([]byte, error) { func (v OKEnvelope) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{NoEscapeHTML: true} w := jwriter.Writer{NoEscapeHTML: true}
w.RawString(`["OK","`) w.RawString(`["OK","`)
w.RawString(v.EventID) w.RawString(hex.EncodeToString(v.EventID[:]))
w.RawString(`",`) w.RawString(`",`)
ok := "false" ok := "false"
if v.OK { if v.OK {

View File

@@ -165,7 +165,6 @@ func (sv *sonicVisitor) OnArrayEnd() error {
sv.mainEnvelope = sv.event sv.mainEnvelope = sv.event
case inReq: case inReq:
sv.mainEnvelope = sv.req sv.mainEnvelope = sv.req
sv.smp.doneWithFilterSlice(sv.req.Filters)
case inOk: case inOk:
sv.mainEnvelope = sv.ok sv.mainEnvelope = sv.ok
case inEose: case inEose:
@@ -184,13 +183,13 @@ func (sv *sonicVisitor) OnArrayEnd() error {
// filter object properties // filter object properties
case inIds: case inIds:
sv.whereWeAre = inFilterObject sv.whereWeAre = inFilterObject
sv.smp.doneWithStringSlice(sv.currentFilter.IDs) sv.smp.doneWithIDSlice(sv.currentFilter.IDs)
case inAuthors: case inAuthors:
sv.whereWeAre = inFilterObject sv.whereWeAre = inFilterObject
sv.smp.doneWithStringSlice(sv.currentFilter.Authors) sv.smp.doneWithPubKeySlice(sv.currentFilter.Authors)
case inKinds: case inKinds:
sv.whereWeAre = inFilterObject sv.whereWeAre = inFilterObject
sv.smp.doneWithIntSlice(sv.currentFilter.Kinds) sv.smp.doneWithUint16Slice(sv.currentFilter.Kinds)
case inAFilterTag: case inAFilterTag:
sv.currentFilter.Tags[sv.currentFilterTagName] = sv.currentFilterTagList sv.currentFilter.Tags[sv.currentFilterTagName] = sv.currentFilterTagList
sv.whereWeAre = inFilterObject sv.whereWeAre = inFilterObject
@@ -268,13 +267,13 @@ func (sv *sonicVisitor) OnObjectKey(key string) error {
sv.whereWeAre = inUntil sv.whereWeAre = inUntil
case "ids": case "ids":
sv.whereWeAre = inIds sv.whereWeAre = inIds
sv.currentFilter.IDs = sv.smp.reusableStringArray sv.currentFilter.IDs = sv.smp.reusableIDArray
case "authors": case "authors":
sv.whereWeAre = inAuthors sv.whereWeAre = inAuthors
sv.currentFilter.Authors = sv.smp.reusableStringArray sv.currentFilter.Authors = sv.smp.reusablePubKeyArray
case "kinds": case "kinds":
sv.whereWeAre = inKinds sv.whereWeAre = inKinds
sv.currentFilter.Kinds = sv.smp.reusableIntArray sv.currentFilter.Kinds = sv.smp.reusableUint16Array
case "search": case "search":
sv.whereWeAre = inSearch sv.whereWeAre = inSearch
case "count", "hll": case "count", "hll":
@@ -316,7 +315,7 @@ func (sv *sonicVisitor) OnObjectEnd() error {
sv.currentEvent = nil sv.currentEvent = nil
case inFilterObject: case inFilterObject:
if sv.req != nil { if sv.req != nil {
sv.req.Filters = append(sv.req.Filters, *sv.currentFilter) sv.req.Filter = *sv.currentFilter
sv.whereWeAre = inReq sv.whereWeAre = inReq
} else { } else {
sv.count.Filter = *sv.currentFilter sv.count.Filter = *sv.currentFilter
@@ -335,6 +334,7 @@ func (sv *sonicVisitor) OnObjectEnd() error {
func (sv *sonicVisitor) OnString(v string) error { func (sv *sonicVisitor) OnString(v string) error {
// fmt.Println("***", "OnString", v, "==", sv.whereWeAre) // fmt.Println("***", "OnString", v, "==", sv.whereWeAre)
var err error
switch sv.whereWeAre { switch sv.whereWeAre {
case inEnvelope: case inEnvelope:
switch v { switch v {
@@ -342,7 +342,7 @@ func (sv *sonicVisitor) OnString(v string) error {
sv.event = &EventEnvelope{} sv.event = &EventEnvelope{}
sv.whereWeAre = inEvent sv.whereWeAre = inEvent
case "REQ": case "REQ":
sv.req = &ReqEnvelope{Filters: sv.smp.reusableFilterArray} sv.req = &ReqEnvelope{}
sv.whereWeAre = inReq sv.whereWeAre = inReq
case "OK": case "OK":
sv.ok = &OKEnvelope{} sv.ok = &OKEnvelope{}
@@ -372,8 +372,11 @@ func (sv *sonicVisitor) OnString(v string) error {
case inReq: case inReq:
sv.req.SubscriptionID = v sv.req.SubscriptionID = v
case inOk: case inOk:
if sv.ok.EventID == "" { if sv.ok.EventID == [32]byte{} {
sv.ok.EventID = v sv.ok.EventID, err = IDFromHex(v)
if err != nil {
return err
}
} else { } else {
sv.ok.Reason = v sv.ok.Reason = v
} }
@@ -396,9 +399,17 @@ func (sv *sonicVisitor) OnString(v string) error {
// filter object properties // filter object properties
case inIds: case inIds:
sv.currentFilter.IDs = append(sv.currentFilter.IDs, v) id, err := IDFromHex(v)
if err != nil {
return err
}
sv.currentFilter.IDs = append(sv.currentFilter.IDs, id)
case inAuthors: case inAuthors:
sv.currentFilter.Authors = append(sv.currentFilter.Authors, v) pk, err := PubKeyFromHex(v)
if err != nil {
return err
}
sv.currentFilter.Authors = append(sv.currentFilter.Authors, pk)
case inSearch: case inSearch:
sv.currentFilter.Search = v sv.currentFilter.Search = v
sv.whereWeAre = inFilterObject sv.whereWeAre = inFilterObject
@@ -484,36 +495,27 @@ func (_ sonicVisitor) OnFloat64(v float64, n stdlibjson.Number) error {
} }
type sonicMessageParser struct { type sonicMessageParser struct {
reusableFilterArray []Filter
reusableTagArray []Tag reusableTagArray []Tag
reusableIDArray []ID
reusablePubKeyArray []PubKey
reusableStringArray []string reusableStringArray []string
reusableIntArray []int reusableUint16Array []uint16
} }
// NewMessageParser returns a sonicMessageParser object that is intended to be reused many times. // NewMessageParser returns a sonicMessageParser object that is intended to be reused many times.
// It is not goroutine-safe. // It is not goroutine-safe.
func NewMessageParser() sonicMessageParser { func NewMessageParser() sonicMessageParser {
return sonicMessageParser{ return sonicMessageParser{
reusableFilterArray: make([]Filter, 0, 1000),
reusableTagArray: make([]Tag, 0, 10000), reusableTagArray: make([]Tag, 0, 10000),
reusableStringArray: make([]string, 0, 10000), reusableStringArray: make([]string, 0, 10000),
reusableIntArray: make([]int, 0, 10000), reusableIDArray: make([]ID, 0, 10000),
reusablePubKeyArray: make([]PubKey, 0, 10000),
reusableUint16Array: make([]uint16, 0, 10000),
} }
} }
var NewSonicMessageParser = NewMessageParser var NewSonicMessageParser = NewMessageParser
func (smp *sonicMessageParser) doneWithFilterSlice(slice []Filter) {
if unsafe.SliceData(smp.reusableFilterArray) == unsafe.SliceData(slice) {
smp.reusableFilterArray = slice[len(slice):]
}
if cap(smp.reusableFilterArray) < 7 {
// create a new one
smp.reusableFilterArray = make([]Filter, 0, 1000)
}
}
func (smp *sonicMessageParser) doneWithTagSlice(slice []Tag) { func (smp *sonicMessageParser) doneWithTagSlice(slice []Tag) {
if unsafe.SliceData(smp.reusableTagArray) == unsafe.SliceData(slice) { if unsafe.SliceData(smp.reusableTagArray) == unsafe.SliceData(slice) {
smp.reusableTagArray = slice[len(slice):] smp.reusableTagArray = slice[len(slice):]
@@ -525,7 +527,7 @@ func (smp *sonicMessageParser) doneWithTagSlice(slice []Tag) {
} }
} }
func (smp *sonicMessageParser) doneWithStringSlice(slice []string) { func (smp *sonicMessageParser) doneWithIDSlice(slice []string) {
if unsafe.SliceData(smp.reusableStringArray) == unsafe.SliceData(slice) { if unsafe.SliceData(smp.reusableStringArray) == unsafe.SliceData(slice) {
smp.reusableStringArray = slice[len(slice):] smp.reusableStringArray = slice[len(slice):]
} }
@@ -536,14 +538,14 @@ func (smp *sonicMessageParser) doneWithStringSlice(slice []string) {
} }
} }
func (smp *sonicMessageParser) doneWithIntSlice(slice []int) { func (smp *sonicMessageParser) doneWithUint16Slice(slice []uint16) {
if unsafe.SliceData(smp.reusableIntArray) == unsafe.SliceData(slice) { if unsafe.SliceData(smp.reusableUint16Array) == unsafe.SliceData(slice) {
smp.reusableIntArray = slice[len(slice):] smp.reusableUint16Array = slice[len(slice):]
} }
if cap(smp.reusableIntArray) < 8 { if cap(smp.reusableUint16Array) < 8 {
// create a new one // create a new one
smp.reusableIntArray = make([]int, 0, 10000) smp.reusableUint16Array = make([]uint16, 0, 10000)
} }
} }

View File

@@ -99,7 +99,7 @@ func TestParseMessage(t *testing.T) {
{ {
Name: "REQ envelope", Name: "REQ envelope",
Message: `["REQ","million", {"kinds": [1]}, {"kinds": [30023 ], "#d": ["buteko", "batuke"]}]`, Message: `["REQ","million", {"kinds": [1]}, {"kinds": [30023 ], "#d": ["buteko", "batuke"]}]`,
ExpectedEnvelope: &ReqEnvelope{SubscriptionID: "million", Filters: Filters{{Kinds: []int{1}}, {Kinds: []int{30023}, Tags: TagMap{"d": []string{"buteko", "batuke"}}}}}, ExpectedEnvelope: &ReqEnvelope{SubscriptionID: "million", Filters: Filters{{Kinds: []uint16{1}}, {Kinds: []uint16{30023}, Tags: TagMap{"d": []string{"buteko", "batuke"}}}}},
}, },
{ {
Name: "CLOSE envelope", Name: "CLOSE envelope",

View File

@@ -13,7 +13,7 @@ func TestEOSEMadness(t *testing.T) {
defer rl.Close() defer rl.Close()
sub, err := rl.Subscribe(context.Background(), Filters{ sub, err := rl.Subscribe(context.Background(), Filters{
{Kinds: []int{KindTextNote}, Limit: 2}, {Kinds: []uint16{KindTextNote}, Limit: 2},
}) })
assert.NoError(t, err) assert.NoError(t, err)

View File

@@ -10,13 +10,13 @@ import (
// Event represents a Nostr event. // Event represents a Nostr event.
type Event struct { type Event struct {
ID string ID ID
PubKey string PubKey PubKey
CreatedAt Timestamp CreatedAt Timestamp
Kind int Kind uint16
Tags Tags Tags Tags
Content string Content string
Sig string Sig [64]byte
} }
func (evt Event) String() string { func (evt Event) String() string {
@@ -24,37 +24,14 @@ func (evt Event) String() string {
return string(j) return string(j)
} }
// GetID computes the event ID and returns it as a hex string. // GetID serializes and returns the event ID as a string.
func (evt *Event) GetID() string { func (evt *Event) GetID() ID {
h := sha256.Sum256(evt.Serialize()) return sha256.Sum256(evt.Serialize())
return hex.EncodeToString(h[:])
} }
// CheckID checks if the implied ID matches the given ID more efficiently. // CheckID checks if the implied ID matches the given ID more efficiently.
func (evt *Event) CheckID() bool { func (evt *Event) CheckID() bool {
if len(evt.ID) != 64 { return evt.GetID() == evt.ID
return false
}
ser := make([]byte, 0, 100+len(evt.Content)+len(evt.Tags)*80)
ser = serializeEventInto(evt, ser)
h := sha256.Sum256(ser)
const hextable = "0123456789abcdef"
for i := 0; i < 32; i++ {
b := hextable[h[i]>>4]
if b != evt.ID[i*2] {
return false
}
b = hextable[h[i]&0x0f]
if b != evt.ID[i*2+1] {
return false
}
}
return true
} }
// Serialize outputs a byte array that can be hashed to produce the canonical event "id". // Serialize outputs a byte array that can be hashed to produce the canonical event "id".
@@ -68,13 +45,13 @@ func (evt *Event) Serialize() []byte {
func serializeEventInto(evt *Event, dst []byte) []byte { func serializeEventInto(evt *Event, dst []byte) []byte {
// the header portion is easy to serialize // the header portion is easy to serialize
// [0,"pubkey",created_at,kind,[ // [0,"pubkey",created_at,kind,[
dst = append(dst, "[0,\""...) dst = append(dst, `[0,"`...)
dst = append(dst, evt.PubKey...) dst = hex.AppendEncode(dst, evt.PubKey[:])
dst = append(dst, "\","...) dst = append(dst, `",`...)
dst = append(dst, strconv.FormatInt(int64(evt.CreatedAt), 10)...) dst = append(dst, strconv.FormatInt(int64(evt.CreatedAt), 10)...)
dst = append(dst, ',') dst = append(dst, `,`...)
dst = append(dst, strconv.Itoa(evt.Kind)...) dst = append(dst, strconv.FormatUint(uint64(evt.Kind), 10)...)
dst = append(dst, ',') dst = append(dst, `,`...)
// tags // tags
dst = append(dst, '[') dst = append(dst, '[')

View File

@@ -1,6 +1,8 @@
package nostr package nostr
import ( import (
"encoding/hex"
easyjson "github.com/mailru/easyjson" easyjson "github.com/mailru/easyjson"
jlexer "github.com/mailru/easyjson/jlexer" jlexer "github.com/mailru/easyjson/jlexer"
jwriter "github.com/mailru/easyjson/jwriter" jwriter "github.com/mailru/easyjson/jwriter"
@@ -23,6 +25,7 @@ func easyjsonF642ad3eDecodeGithubComNbdWtfGoNostr(in *jlexer.Lexer, out *Event)
return return
} }
in.Delim('{') in.Delim('{')
var reusableBuffer [64]byte
for !in.IsDelim('}') { for !in.IsDelim('}') {
key := in.UnsafeFieldName(true) key := in.UnsafeFieldName(true)
in.WantColon() in.WantColon()
@@ -33,13 +36,15 @@ func easyjsonF642ad3eDecodeGithubComNbdWtfGoNostr(in *jlexer.Lexer, out *Event)
} }
switch key { switch key {
case "id": case "id":
out.ID = in.String() hex.Decode(reusableBuffer[:], []byte(in.String()))
copy(out.ID[:], reusableBuffer[0:32])
case "pubkey": case "pubkey":
out.PubKey = in.String() hex.Decode(reusableBuffer[:], []byte(in.String()))
copy(out.PubKey[:], reusableBuffer[0:32])
case "created_at": case "created_at":
out.CreatedAt = Timestamp(in.Int64()) out.CreatedAt = Timestamp(in.Int64())
case "kind": case "kind":
out.Kind = in.Int() out.Kind = uint16(in.Int())
case "tags": case "tags":
if in.IsNull() { if in.IsNull() {
in.Skip() in.Skip()
@@ -83,7 +88,8 @@ func easyjsonF642ad3eDecodeGithubComNbdWtfGoNostr(in *jlexer.Lexer, out *Event)
case "content": case "content":
out.Content = in.String() out.Content = in.String()
case "sig": case "sig":
out.Sig = in.String() hex.Decode(reusableBuffer[:], []byte(in.String()))
copy(out.Sig[:], reusableBuffer[0:64])
} }
in.WantComma() in.WantComma()
} }
@@ -100,20 +106,20 @@ func easyjsonF642ad3eEncodeGithubComNbdWtfGoNostr(out *jwriter.Writer, in Event)
{ {
const prefix string = "\"kind\":" const prefix string = "\"kind\":"
out.RawString(prefix) out.RawString(prefix)
out.Int(in.Kind) out.Int(int(in.Kind))
} }
{ {
if in.ID != "" { if in.ID != [32]byte{} {
const prefix string = ",\"id\":" const prefix string = ",\"id\":"
out.RawString(prefix) out.RawString(prefix)
out.String(in.ID) out.String(hex.EncodeToString(in.ID[:]))
} }
} }
{ {
if in.PubKey != "" { if in.PubKey != [32]byte{} {
const prefix string = ",\"pubkey\":" const prefix string = ",\"pubkey\":"
out.RawString(prefix) out.RawString(prefix)
out.String(in.PubKey) out.String(hex.EncodeToString(in.PubKey[:]))
} }
} }
{ {
@@ -146,10 +152,10 @@ func easyjsonF642ad3eEncodeGithubComNbdWtfGoNostr(out *jwriter.Writer, in Event)
out.String(in.Content) out.String(in.Content)
} }
{ {
if in.Sig != "" { if in.Sig != [64]byte{} {
const prefix string = ",\"sig\":" const prefix string = ",\"sig\":"
out.RawString(prefix) out.RawString(prefix)
out.String(in.Sig) out.String(hex.EncodeToString(in.Sig[:]))
} }
} }
out.RawByte('}') out.RawByte('}')

View File

@@ -1,6 +1,7 @@
package nostr package nostr
import ( import (
"encoding/hex"
"fmt" "fmt"
"math/rand/v2" "math/rand/v2"
"testing" "testing"
@@ -34,15 +35,18 @@ func TestEventParsingAndVerifying(t *testing.T) {
} }
func TestEventSerialization(t *testing.T) { func TestEventSerialization(t *testing.T) {
sig := [64]byte{}
hex.Decode(sig[:], []byte("ed08d2dd5b0f7b6a3cdc74643d4adee3158ddede9cc848e8cd97630c097001acc2d052d2d3ec2b7ac4708b2314b797106d1b3c107322e61b5e5cc2116e099b79"))
events := []Event{ events := []Event{
{ {
ID: "92570b321da503eac8014b23447301eb3d0bbdfbace0d11a4e4072e72bb7205d", ID: MustIDFromHex("92570b321da503eac8014b23447301eb3d0bbdfbace0d11a4e4072e72bb7205d"),
PubKey: "e9142f724955c5854de36324dab0434f97b15ec6b33464d56ebe491e3f559d1b", PubKey: MustPubKeyFromHex("e9142f724955c5854de36324dab0434f97b15ec6b33464d56ebe491e3f559d1b"),
Kind: KindEncryptedDirectMessage, Kind: KindEncryptedDirectMessage,
CreatedAt: Timestamp(1671028682), CreatedAt: Timestamp(1671028682),
Tags: Tags{Tag{"p", "f8340b2bde651576b75af61aa26c80e13c65029f00f7f64004eece679bf7059f"}}, Tags: Tags{Tag{"p", "f8340b2bde651576b75af61aa26c80e13c65029f00f7f64004eece679bf7059f"}},
Content: "you say yes, I say no", Content: "you say yes, I say no",
Sig: "ed08d2dd5b0f7b6a3cdc74643d4adee3158ddede9cc848e8cd97630c097001acc2d052d2d3ec2b7ac4708b2314b797106d1b3c107322e61b5e5cc2116e099b79", Sig: sig,
}, },
} }
@@ -73,7 +77,7 @@ func TestEventSerialization(t *testing.T) {
} }
} }
func mustSignEvent(t *testing.T, privkey string, event *Event) { func mustSignEvent(t *testing.T, privkey [32]byte, event *Event) {
t.Helper() t.Helper()
if err := event.Sign(privkey); err != nil { if err := event.Sign(privkey); err != nil {
t.Fatalf("event.Sign: %v", err) t.Fatalf("event.Sign: %v", err)

View File

@@ -9,8 +9,8 @@ import (
"time" "time"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/nip19" "fiatjaf.com/nostrlib/nip19"
) )
func main() { func main() {

View File

@@ -6,12 +6,10 @@ import (
"github.com/mailru/easyjson" "github.com/mailru/easyjson"
) )
type Filters []Filter
type Filter struct { type Filter struct {
IDs []string IDs []ID
Kinds []int Kinds []uint16
Authors []string Authors []PubKey
Tags TagMap Tags TagMap
Since *Timestamp Since *Timestamp
Until *Timestamp Until *Timestamp
@@ -24,29 +22,6 @@ type Filter struct {
type TagMap map[string][]string type TagMap map[string][]string
func (eff Filters) String() string {
j, _ := json.Marshal(eff)
return string(j)
}
func (eff Filters) Match(event *Event) bool {
for _, filter := range eff {
if filter.Matches(event) {
return true
}
}
return false
}
func (eff Filters) MatchIgnoringTimestampConstraints(event *Event) bool {
for _, filter := range eff {
if filter.MatchesIgnoringTimestampConstraints(event) {
return true
}
}
return false
}
func (ef Filter) String() string { func (ef Filter) String() string {
j, _ := easyjson.Marshal(ef) j, _ := easyjson.Marshal(ef)
return string(j) return string(j)
@@ -99,11 +74,11 @@ func FilterEqual(a Filter, b Filter) bool {
return false return false
} }
if !similar(a.IDs, b.IDs) { if !similarID(a.IDs, b.IDs) {
return false return false
} }
if !similar(a.Authors, b.Authors) { if !similarPublicKey(a.Authors, b.Authors) {
return false return false
} }
@@ -142,14 +117,26 @@ func FilterEqual(a Filter, b Filter) bool {
func (ef Filter) Clone() Filter { func (ef Filter) Clone() Filter {
clone := Filter{ clone := Filter{
IDs: slices.Clone(ef.IDs),
Authors: slices.Clone(ef.Authors),
Kinds: slices.Clone(ef.Kinds), Kinds: slices.Clone(ef.Kinds),
Limit: ef.Limit, Limit: ef.Limit,
Search: ef.Search, Search: ef.Search,
LimitZero: ef.LimitZero, LimitZero: ef.LimitZero,
} }
if ef.IDs != nil {
clone.IDs = make([]ID, len(ef.IDs))
for i, src := range ef.IDs {
copy(clone.IDs[i][:], src[:])
}
}
if ef.Authors != nil {
clone.Authors = make([]PubKey, len(ef.Authors))
for i, src := range ef.Authors {
copy(clone.Authors[i][:], src[:])
}
}
if ef.Tags != nil { if ef.Tags != nil {
clone.Tags = make(TagMap, len(ef.Tags)) clone.Tags = make(TagMap, len(ef.Tags))
for k, v := range ef.Tags { for k, v := range ef.Tags {

View File

@@ -1,6 +1,8 @@
package nostr package nostr
import ( import (
"encoding/hex"
easyjson "github.com/mailru/easyjson" easyjson "github.com/mailru/easyjson"
jlexer "github.com/mailru/easyjson/jlexer" jlexer "github.com/mailru/easyjson/jlexer"
jwriter "github.com/mailru/easyjson/jwriter" jwriter "github.com/mailru/easyjson/jwriter"
@@ -41,17 +43,17 @@ func easyjson4d398eaaDecodeGithubComNbdWtfGoNostr(in *jlexer.Lexer, out *Filter)
in.Delim('[') in.Delim('[')
if out.IDs == nil { if out.IDs == nil {
if !in.IsDelim(']') { if !in.IsDelim(']') {
out.IDs = make([]string, 0, 20) out.IDs = make([]ID, 0, 20)
} else { } else {
out.IDs = []string{} out.IDs = []ID{}
} }
} else { } else {
out.IDs = (out.IDs)[:0] out.IDs = (out.IDs)[:0]
} }
for !in.IsDelim(']') { for !in.IsDelim(']') {
var v1 string id := [32]byte{}
v1 = string(in.String()) hex.Decode(id[:], []byte(in.String()))
out.IDs = append(out.IDs, v1) out.IDs = append(out.IDs, id)
in.WantComma() in.WantComma()
} }
in.Delim(']') in.Delim(']')
@@ -64,17 +66,15 @@ func easyjson4d398eaaDecodeGithubComNbdWtfGoNostr(in *jlexer.Lexer, out *Filter)
in.Delim('[') in.Delim('[')
if out.Kinds == nil { if out.Kinds == nil {
if !in.IsDelim(']') { if !in.IsDelim(']') {
out.Kinds = make([]int, 0, 8) out.Kinds = make([]uint16, 0, 8)
} else { } else {
out.Kinds = []int{} out.Kinds = []uint16{}
} }
} else { } else {
out.Kinds = (out.Kinds)[:0] out.Kinds = (out.Kinds)[:0]
} }
for !in.IsDelim(']') { for !in.IsDelim(']') {
var v2 int out.Kinds = append(out.Kinds, uint16(in.Int()))
v2 = int(in.Int())
out.Kinds = append(out.Kinds, v2)
in.WantComma() in.WantComma()
} }
in.Delim(']') in.Delim(']')
@@ -87,17 +87,17 @@ func easyjson4d398eaaDecodeGithubComNbdWtfGoNostr(in *jlexer.Lexer, out *Filter)
in.Delim('[') in.Delim('[')
if out.Authors == nil { if out.Authors == nil {
if !in.IsDelim(']') { if !in.IsDelim(']') {
out.Authors = make([]string, 0, 40) out.Authors = make([]PubKey, 0, 40)
} else { } else {
out.Authors = []string{} out.Authors = []PubKey{}
} }
} else { } else {
out.Authors = (out.Authors)[:0] out.Authors = (out.Authors)[:0]
} }
for !in.IsDelim(']') { for !in.IsDelim(']') {
var v3 string pk := [32]byte{}
v3 = string(in.String()) hex.Decode(pk[:], []byte(in.String()))
out.Authors = append(out.Authors, v3) out.Authors = append(out.Authors, pk)
in.WantComma() in.WantComma()
} }
in.Delim(']') in.Delim(']')
@@ -178,7 +178,7 @@ func easyjson4d398eaaEncodeGithubComNbdWtfGoNostr(out *jwriter.Writer, in Filter
if v4 > 0 { if v4 > 0 {
out.RawByte(',') out.RawByte(',')
} }
out.String(string(v5)) out.String(hex.EncodeToString(v5[:]))
} }
out.RawByte(']') out.RawByte(']')
} }
@@ -216,7 +216,7 @@ func easyjson4d398eaaEncodeGithubComNbdWtfGoNostr(out *jwriter.Writer, in Filter
if v8 > 0 { if v8 > 0 {
out.RawByte(',') out.RawByte(',')
} }
out.String(string(v9)) out.String(hex.EncodeToString(v9[:]))
} }
out.RawByte(']') out.RawByte(']')
} }

View File

@@ -28,7 +28,7 @@ func TestFilterUnmarshal(t *testing.T) {
func TestFilterMarshal(t *testing.T) { func TestFilterMarshal(t *testing.T) {
until := Timestamp(12345678) until := Timestamp(12345678)
filterj, err := json.Marshal(Filter{ filterj, err := json.Marshal(Filter{
Kinds: []int{KindTextNote, KindRecommendServer, KindEncryptedDirectMessage}, Kinds: []uint16{KindTextNote, KindRecommendServer, KindEncryptedDirectMessage},
Tags: TagMap{"fruit": {"banana", "mango"}}, Tags: TagMap{"fruit": {"banana", "mango"}},
Until: &until, Until: &until,
}) })
@@ -60,7 +60,7 @@ func TestFilterUnmarshalWithLimitZero(t *testing.T) {
func TestFilterMarshalWithLimitZero(t *testing.T) { func TestFilterMarshalWithLimitZero(t *testing.T) {
until := Timestamp(12345678) until := Timestamp(12345678)
filterj, err := json.Marshal(Filter{ filterj, err := json.Marshal(Filter{
Kinds: []int{KindTextNote, KindRecommendServer, KindEncryptedDirectMessage}, Kinds: []uint16{KindTextNote, KindRecommendServer, KindEncryptedDirectMessage},
Tags: TagMap{"fruit": {"banana", "mango"}}, Tags: TagMap{"fruit": {"banana", "mango"}},
Until: &until, Until: &until,
LimitZero: true, LimitZero: true,
@@ -83,50 +83,50 @@ func TestFilterMatchingLive(t *testing.T) {
func TestFilterEquality(t *testing.T) { func TestFilterEquality(t *testing.T) {
assert.True(t, FilterEqual( assert.True(t, FilterEqual(
Filter{Kinds: []int{KindEncryptedDirectMessage, KindDeletion}}, Filter{Kinds: []uint16{KindEncryptedDirectMessage, KindDeletion}},
Filter{Kinds: []int{KindEncryptedDirectMessage, KindDeletion}}, Filter{Kinds: []uint16{KindEncryptedDirectMessage, KindDeletion}},
), "kinds filters should be equal") ), "kinds filters should be equal")
assert.True(t, FilterEqual( assert.True(t, FilterEqual(
Filter{Kinds: []int{KindEncryptedDirectMessage, KindDeletion}, Tags: TagMap{"letter": {"a", "b"}}}, Filter{Kinds: []uint16{KindEncryptedDirectMessage, KindDeletion}, Tags: TagMap{"letter": {"a", "b"}}},
Filter{Kinds: []int{KindEncryptedDirectMessage, KindDeletion}, Tags: TagMap{"letter": {"b", "a"}}}, Filter{Kinds: []uint16{KindEncryptedDirectMessage, KindDeletion}, Tags: TagMap{"letter": {"b", "a"}}},
), "kind+tags filters should be equal") ), "kind+tags filters should be equal")
tm := Now() tm := Now()
assert.True(t, FilterEqual( assert.True(t, FilterEqual(
Filter{ Filter{
Kinds: []int{KindEncryptedDirectMessage, KindDeletion}, Kinds: []uint16{KindEncryptedDirectMessage, KindDeletion},
Tags: TagMap{"letter": {"a", "b"}, "fruit": {"banana"}}, Tags: TagMap{"letter": {"a", "b"}, "fruit": {"banana"}},
Since: &tm, Since: &tm,
IDs: []string{"aaaa", "bbbb"}, IDs: []ID{{'a', 'a'}, {'b', 'b'}},
}, },
Filter{ Filter{
Kinds: []int{KindDeletion, KindEncryptedDirectMessage}, Kinds: []uint16{KindDeletion, KindEncryptedDirectMessage},
Tags: TagMap{"letter": {"a", "b"}, "fruit": {"banana"}}, Tags: TagMap{"letter": {"a", "b"}, "fruit": {"banana"}},
Since: &tm, Since: &tm,
IDs: []string{"aaaa", "bbbb"}, IDs: []ID{{'a', 'a'}, {'b', 'b'}},
}, },
), "kind+2tags+since+ids filters should be equal") ), "kind+2tags+since+ids filters should be equal")
assert.False(t, FilterEqual( assert.False(t, FilterEqual(
Filter{Kinds: []int{KindTextNote, KindEncryptedDirectMessage, KindDeletion}}, Filter{Kinds: []uint16{KindTextNote, KindEncryptedDirectMessage, KindDeletion}},
Filter{Kinds: []int{KindEncryptedDirectMessage, KindDeletion, KindRepost}}, Filter{Kinds: []uint16{KindEncryptedDirectMessage, KindDeletion, KindRepost}},
), "kinds filters shouldn't be equal") ), "kinds filters shouldn't be equal")
} }
func TestFilterClone(t *testing.T) { func TestFilterClone(t *testing.T) {
ts := Now() - 60*60 ts := Now() - 60*60
flt := Filter{ flt := Filter{
Kinds: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, Kinds: []uint16{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
Tags: TagMap{"letter": {"a", "b"}, "fruit": {"banana"}}, Tags: TagMap{"letter": {"a", "b"}, "fruit": {"banana"}},
Since: &ts, Since: &ts,
IDs: []string{"9894b4b5cb5166d23ee8899a4151cf0c66aec00bde101982a13b8e8ceb972df9"}, IDs: []ID{IDFromHex("9894b4b5cb5166d23ee8899a4151cf0c66aec00bde101982a13b8e8ceb972df9")},
} }
clone := flt.Clone() clone := flt.Clone()
assert.True(t, FilterEqual(flt, clone), "clone is not equal:\n %v !=\n %v", flt, clone) assert.True(t, FilterEqual(flt, clone), "clone is not equal:\n %v !=\n %v", flt, clone)
clone1 := flt.Clone() clone1 := flt.Clone()
clone1.IDs = append(clone1.IDs, "88f0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d") clone1.IDs = append(clone1.IDs, IDFromHex("88f0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"))
assert.False(t, FilterEqual(flt, clone1), "modifying the clone ids should cause it to not be equal anymore") assert.False(t, FilterEqual(flt, clone1), "modifying the clone ids should cause it to not be equal anymore")
clone2 := flt.Clone() clone2 := flt.Clone()
@@ -143,11 +143,11 @@ func TestFilterClone(t *testing.T) {
} }
func TestTheoreticalLimit(t *testing.T) { func TestTheoreticalLimit(t *testing.T) {
require.Equal(t, 6, GetTheoreticalLimit(Filter{IDs: []string{"a", "b", "c", "d", "e", "f"}})) require.Equal(t, 6, GetTheoreticalLimit(Filter{IDs: []ID{{'a'}, {'b'}, {'c'}, {'d'}, {'e'}, {'f'}}}))
require.Equal(t, 9, GetTheoreticalLimit(Filter{Authors: []string{"a", "b", "c"}, Kinds: []int{3, 0, 10002}})) require.Equal(t, 9, GetTheoreticalLimit(Filter{Authors: []PubKey{{'a'}, {'b'}, {'c'}}, Kinds: []uint16{3, 0, 10002}}))
require.Equal(t, 4, GetTheoreticalLimit(Filter{Authors: []string{"a", "b", "c", "d"}, Kinds: []int{10050}})) require.Equal(t, 4, GetTheoreticalLimit(Filter{Authors: []PubKey{{'a'}, {'b'}, {'c'}, {'d'}}, Kinds: []uint16{10050}}))
require.Equal(t, -1, GetTheoreticalLimit(Filter{Authors: []string{"a", "b", "c", "d"}})) require.Equal(t, -1, GetTheoreticalLimit(Filter{Authors: []PubKey{{'a'}, {'b'}, {'c'}, {'d'}}}))
require.Equal(t, -1, GetTheoreticalLimit(Filter{Kinds: []int{3, 0, 10002}})) require.Equal(t, -1, GetTheoreticalLimit(Filter{Kinds: []uint16{3, 0, 10002}}))
require.Equal(t, 24, GetTheoreticalLimit(Filter{Authors: []string{"a", "b", "c", "d", "e", "f"}, Kinds: []int{30023, 30024}, Tags: TagMap{"d": []string{"aaa", "bbb"}}})) require.Equal(t, 24, GetTheoreticalLimit(Filter{Authors: []PubKey{{'a'}, {'b'}, {'c'}, {'d'}, {'e'}, {'f'}}, Kinds: []uint16{30023, 30024}, Tags: TagMap{"d": []string{"aaa", "bbb"}}}))
require.Equal(t, -1, GetTheoreticalLimit(Filter{Authors: []string{"a", "b", "c", "d", "e", "f"}, Kinds: []int{30023, 30024}})) require.Equal(t, -1, GetTheoreticalLimit(Filter{Authors: []PubKey{{'a'}, {'b'}, {'c'}, {'d'}, {'e'}, {'f'}}, Kinds: []uint16{30023, 30024}}))
} }

2
go.mod
View File

@@ -1,4 +1,4 @@
module github.com/nbd-wtf/go-nostr module fiatjaf.com/nostr
go 1.24.1 go 1.24.1

View File

@@ -1,6 +1,7 @@
package nostr package nostr
import ( import (
"encoding/hex"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@@ -49,6 +50,48 @@ func similar[E constraints.Ordered](as, bs []E) bool {
return true return true
} }
func similarID(as, bs []ID) bool {
if len(as) != len(bs) {
return false
}
for _, a := range as {
for _, b := range bs {
if b == a {
goto next
}
}
// didn't find a B that corresponded to the current A
return false
next:
continue
}
return true
}
func similarPublicKey(as, bs []PubKey) bool {
if len(as) != len(bs) {
return false
}
for _, a := range as {
for _, b := range bs {
if b == a {
goto next
}
}
// didn't find a B that corresponded to the current A
return false
next:
continue
}
return true
}
// Escaping strings for JSON encoding according to RFC8259. // Escaping strings for JSON encoding according to RFC8259.
// Also encloses result in quotation marks "". // Also encloses result in quotation marks "".
func escapeString(dst []byte, s string) []byte { func escapeString(dst []byte, s string) []byte {
@@ -140,11 +183,11 @@ func extractSubID(jsonStr string) string {
return jsonStr[start : start+end] return jsonStr[start : start+end]
} }
func extractEventID(jsonStr string) string { func extractEventID(jsonStr string) ID {
// look for "id" pattern // look for "id" pattern
start := strings.Index(jsonStr, `"id"`) start := strings.Index(jsonStr, `"id"`)
if start == -1 { if start == -1 {
return "" return [32]byte{}
} }
// move to the next quote // move to the next quote
@@ -152,14 +195,16 @@ func extractEventID(jsonStr string) string {
start += 4 + offset + 1 start += 4 + offset + 1
// get 64 characters of the id // get 64 characters of the id
return jsonStr[start : start+64] var id [32]byte
hex.Decode(id[:], unsafe.Slice(unsafe.StringData(jsonStr[start:start+64]), 64))
return id
} }
func extractEventPubKey(jsonStr string) string { func extractEventPubKey(jsonStr string) PubKey {
// look for "pubkey" pattern // look for "pubkey" pattern
start := strings.Index(jsonStr, `"pubkey"`) start := strings.Index(jsonStr, `"pubkey"`)
if start == -1 { if start == -1 {
return "" return PubKey{}
} }
// move to the next quote // move to the next quote
@@ -167,7 +212,9 @@ func extractEventPubKey(jsonStr string) string {
start += 8 + offset + 1 start += 8 + offset + 1
// get 64 characters of the pubkey // get 64 characters of the pubkey
return jsonStr[start : start+64] var pk [32]byte
hex.Decode(pk[:], unsafe.Slice(unsafe.StringData(jsonStr[start:start+64]), 64))
return pk
} }
func extractDTag(jsonStr string) string { func extractDTag(jsonStr string) string {

View File

@@ -5,8 +5,8 @@ import (
"errors" "errors"
"time" "time"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/nip46" "fiatjaf.com/nostrlib/nip46"
) )
var _ nostr.Keyer = (*BunkerSigner)(nil) var _ nostr.Keyer = (*BunkerSigner)(nil)

View File

@@ -4,9 +4,9 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/nip44" "fiatjaf.com/nostrlib/nip44"
"github.com/nbd-wtf/go-nostr/nip49" "fiatjaf.com/nostrlib/nip49"
) )
var _ nostr.Keyer = (*EncryptedKeySigner)(nil) var _ nostr.Keyer = (*EncryptedKeySigner)(nil)

View File

@@ -7,11 +7,11 @@ import (
"strings" "strings"
"time" "time"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/nip05" "fiatjaf.com/nostrlib/nip05"
"github.com/nbd-wtf/go-nostr/nip19" "fiatjaf.com/nostrlib/nip19"
"github.com/nbd-wtf/go-nostr/nip46" "fiatjaf.com/nostrlib/nip46"
"github.com/nbd-wtf/go-nostr/nip49" "fiatjaf.com/nostrlib/nip49"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
) )

View File

@@ -3,7 +3,7 @@ package keyer
import ( import (
"context" "context"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
var _ nostr.Keyer = (*ManualSigner)(nil) var _ nostr.Keyer = (*ManualSigner)(nil)

View File

@@ -3,8 +3,8 @@ package keyer
import ( import (
"context" "context"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/nip44" "fiatjaf.com/nostrlib/nip44"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
) )

43
keys.go
View File

@@ -2,48 +2,27 @@ package nostr
import ( import (
"crypto/rand" "crypto/rand"
"encoding/hex"
"fmt" "fmt"
"io" "io"
"math/big"
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcec/v2/schnorr"
) )
func GeneratePrivateKey() string { func GeneratePrivateKey() [32]byte {
params := btcec.S256().Params() var sk [32]byte
one := new(big.Int).SetInt64(1) if _, err := io.ReadFull(rand.Reader, sk[:]); err != nil {
panic(fmt.Errorf("failed to read random bytes when generating private key"))
b := make([]byte, params.BitSize/8+8) }
if _, err := io.ReadFull(rand.Reader, b); err != nil { return sk
return ""
} }
k := new(big.Int).SetBytes(b) func GetPublicKey(sk [32]byte) PubKey {
n := new(big.Int).Sub(params.N, one) _, pk := btcec.PrivKeyFromBytes(sk[:])
k.Mod(k, n) return [32]byte(pk.SerializeCompressed()[1:])
k.Add(k, one)
return fmt.Sprintf("%064x", k.Bytes())
} }
func GetPublicKey(sk string) (string, error) { func IsValidPublicKey(pk [32]byte) bool {
b, err := hex.DecodeString(sk) _, err := schnorr.ParsePubKey(pk[:])
if err != nil {
return "", err
}
_, pk := btcec.PrivKeyFromBytes(b)
return hex.EncodeToString(schnorr.SerializePubKey(pk)), nil
}
func IsValidPublicKey(pk string) bool {
if !isLowerHex(pk) {
return false
}
v, _ := hex.DecodeString(pk)
_, err := schnorr.ParsePubKey(v)
return err == nil return err == nil
} }

272
kinds.go
View File

@@ -1,152 +1,152 @@
package nostr package nostr
const ( const (
KindProfileMetadata int = 0 KindProfileMetadata uint16 = 0
KindTextNote int = 1 KindTextNote uint16 = 1
KindRecommendServer int = 2 KindRecommendServer uint16 = 2
KindFollowList int = 3 KindFollowList uint16 = 3
KindEncryptedDirectMessage int = 4 KindEncryptedDirectMessage uint16 = 4
KindDeletion int = 5 KindDeletion uint16 = 5
KindRepost int = 6 KindRepost uint16 = 6
KindReaction int = 7 KindReaction uint16 = 7
KindBadgeAward int = 8 KindBadgeAward uint16 = 8
KindSimpleGroupChatMessage int = 9 KindSimpleGroupChatMessage uint16 = 9
KindSimpleGroupThreadedReply int = 10 KindSimpleGroupThreadedReply uint16 = 10
KindSimpleGroupThread int = 11 KindSimpleGroupThread uint16 = 11
KindSimpleGroupReply int = 12 KindSimpleGroupReply uint16 = 12
KindSeal int = 13 KindSeal uint16 = 13
KindDirectMessage int = 14 KindDirectMessage uint16 = 14
KindGenericRepost int = 16 KindGenericRepost uint16 = 16
KindReactionToWebsite int = 17 KindReactionToWebsite uint16 = 17
KindChannelCreation int = 40 KindChannelCreation uint16 = 40
KindChannelMetadata int = 41 KindChannelMetadata uint16 = 41
KindChannelMessage int = 42 KindChannelMessage uint16 = 42
KindChannelHideMessage int = 43 KindChannelHideMessage uint16 = 43
KindChannelMuteUser int = 44 KindChannelMuteUser uint16 = 44
KindChess int = 64 KindChess uint16 = 64
KindMergeRequests int = 818 KindMergeRequests uint16 = 818
KindComment int = 1111 KindComment uint16 = 1111
KindBid int = 1021 KindBid uint16 = 1021
KindBidConfirmation int = 1022 KindBidConfirmation uint16 = 1022
KindOpenTimestamps int = 1040 KindOpenTimestamps uint16 = 1040
KindGiftWrap int = 1059 KindGiftWrap uint16 = 1059
KindFileMetadata int = 1063 KindFileMetadata uint16 = 1063
KindLiveChatMessage int = 1311 KindLiveChatMessage uint16 = 1311
KindPatch int = 1617 KindPatch uint16 = 1617
KindIssue int = 1621 KindIssue uint16 = 1621
KindReply int = 1622 KindReply uint16 = 1622
KindStatusOpen int = 1630 KindStatusOpen uint16 = 1630
KindStatusApplied int = 1631 KindStatusApplied uint16 = 1631
KindStatusClosed int = 1632 KindStatusClosed uint16 = 1632
KindStatusDraft int = 1633 KindStatusDraft uint16 = 1633
KindProblemTracker int = 1971 KindProblemTracker uint16 = 1971
KindReporting int = 1984 KindReporting uint16 = 1984
KindLabel int = 1985 KindLabel uint16 = 1985
KindRelayReviews int = 1986 KindRelayReviews uint16 = 1986
KindAIEmbeddings int = 1987 KindAIEmbeddings uint16 = 1987
KindTorrent int = 2003 KindTorrent uint16 = 2003
KindTorrentComment int = 2004 KindTorrentComment uint16 = 2004
KindCoinjoinPool int = 2022 KindCoinjoinPool uint16 = 2022
KindCommunityPostApproval int = 4550 KindCommunityPostApproval uint16 = 4550
KindJobFeedback int = 7000 KindJobFeedback uint16 = 7000
KindSimpleGroupPutUser int = 9000 KindSimpleGroupPutUser uint16 = 9000
KindSimpleGroupRemoveUser int = 9001 KindSimpleGroupRemoveUser uint16 = 9001
KindSimpleGroupEditMetadata int = 9002 KindSimpleGroupEditMetadata uint16 = 9002
KindSimpleGroupDeleteEvent int = 9005 KindSimpleGroupDeleteEvent uint16 = 9005
KindSimpleGroupCreateGroup int = 9007 KindSimpleGroupCreateGroup uint16 = 9007
KindSimpleGroupDeleteGroup int = 9008 KindSimpleGroupDeleteGroup uint16 = 9008
KindSimpleGroupCreateInvite int = 9009 KindSimpleGroupCreateInvite uint16 = 9009
KindSimpleGroupJoinRequest int = 9021 KindSimpleGroupJoinRequest uint16 = 9021
KindSimpleGroupLeaveRequest int = 9022 KindSimpleGroupLeaveRequest uint16 = 9022
KindZapGoal int = 9041 KindZapGoal uint16 = 9041
KindNutZap int = 9321 KindNutZap uint16 = 9321
KindTidalLogin int = 9467 KindTidalLogin uint16 = 9467
KindZapRequest int = 9734 KindZapRequest uint16 = 9734
KindZap int = 9735 KindZap uint16 = 9735
KindHighlights int = 9802 KindHighlights uint16 = 9802
KindMuteList int = 10000 KindMuteList uint16 = 10000
KindPinList int = 10001 KindPinList uint16 = 10001
KindRelayListMetadata int = 10002 KindRelayListMetadata uint16 = 10002
KindBookmarkList int = 10003 KindBookmarkList uint16 = 10003
KindCommunityList int = 10004 KindCommunityList uint16 = 10004
KindPublicChatList int = 10005 KindPublicChatList uint16 = 10005
KindBlockedRelayList int = 10006 KindBlockedRelayList uint16 = 10006
KindSearchRelayList int = 10007 KindSearchRelayList uint16 = 10007
KindSimpleGroupList int = 10009 KindSimpleGroupList uint16 = 10009
KindInterestList int = 10015 KindInterestList uint16 = 10015
KindNutZapInfo int = 10019 KindNutZapInfo uint16 = 10019
KindEmojiList int = 10030 KindEmojiList uint16 = 10030
KindDMRelayList int = 10050 KindDMRelayList uint16 = 10050
KindUserServerList int = 10063 KindUserServerList uint16 = 10063
KindFileStorageServerList int = 10096 KindFileStorageServerList uint16 = 10096
KindGoodWikiAuthorList int = 10101 KindGoodWikiAuthorList uint16 = 10101
KindGoodWikiRelayList int = 10102 KindGoodWikiRelayList uint16 = 10102
KindNWCWalletInfo int = 13194 KindNWCWalletInfo uint16 = 13194
KindLightningPubRPC int = 21000 KindLightningPubRPC uint16 = 21000
KindClientAuthentication int = 22242 KindClientAuthentication uint16 = 22242
KindNWCWalletRequest int = 23194 KindNWCWalletRequest uint16 = 23194
KindNWCWalletResponse int = 23195 KindNWCWalletResponse uint16 = 23195
KindNostrConnect int = 24133 KindNostrConnect uint16 = 24133
KindBlobs int = 24242 KindBlobs uint16 = 24242
KindHTTPAuth int = 27235 KindHTTPAuth uint16 = 27235
KindCategorizedPeopleList int = 30000 KindCategorizedPeopleList uint16 = 30000
KindCategorizedBookmarksList int = 30001 KindCategorizedBookmarksList uint16 = 30001
KindRelaySets int = 30002 KindRelaySets uint16 = 30002
KindBookmarkSets int = 30003 KindBookmarkSets uint16 = 30003
KindCuratedSets int = 30004 KindCuratedSets uint16 = 30004
KindCuratedVideoSets int = 30005 KindCuratedVideoSets uint16 = 30005
KindMuteSets int = 30007 KindMuteSets uint16 = 30007
KindProfileBadges int = 30008 KindProfileBadges uint16 = 30008
KindBadgeDefinition int = 30009 KindBadgeDefinition uint16 = 30009
KindInterestSets int = 30015 KindInterestSets uint16 = 30015
KindStallDefinition int = 30017 KindStallDefinition uint16 = 30017
KindProductDefinition int = 30018 KindProductDefinition uint16 = 30018
KindMarketplaceUI int = 30019 KindMarketplaceUI uint16 = 30019
KindProductSoldAsAuction int = 30020 KindProductSoldAsAuction uint16 = 30020
KindArticle int = 30023 KindArticle uint16 = 30023
KindDraftArticle int = 30024 KindDraftArticle uint16 = 30024
KindEmojiSets int = 30030 KindEmojiSets uint16 = 30030
KindModularArticleHeader int = 30040 KindModularArticleHeader uint16 = 30040
KindModularArticleContent int = 30041 KindModularArticleContent uint16 = 30041
KindReleaseArtifactSets int = 30063 KindReleaseArtifactSets uint16 = 30063
KindApplicationSpecificData int = 30078 KindApplicationSpecificData uint16 = 30078
KindLiveEvent int = 30311 KindLiveEvent uint16 = 30311
KindUserStatuses int = 30315 KindUserStatuses uint16 = 30315
KindClassifiedListing int = 30402 KindClassifiedListing uint16 = 30402
KindDraftClassifiedListing int = 30403 KindDraftClassifiedListing uint16 = 30403
KindRepositoryAnnouncement int = 30617 KindRepositoryAnnouncement uint16 = 30617
KindRepositoryState int = 30618 KindRepositoryState uint16 = 30618
KindSimpleGroupMetadata int = 39000 KindSimpleGroupMetadata uint16 = 39000
KindSimpleGroupAdmins int = 39001 KindSimpleGroupAdmins uint16 = 39001
KindSimpleGroupMembers int = 39002 KindSimpleGroupMembers uint16 = 39002
KindSimpleGroupRoles int = 39003 KindSimpleGroupRoles uint16 = 39003
KindWikiArticle int = 30818 KindWikiArticle uint16 = 30818
KindRedirects int = 30819 KindRedirects uint16 = 30819
KindFeed int = 31890 KindFeed uint16 = 31890
KindDateCalendarEvent int = 31922 KindDateCalendarEvent uint16 = 31922
KindTimeCalendarEvent int = 31923 KindTimeCalendarEvent uint16 = 31923
KindCalendar int = 31924 KindCalendar uint16 = 31924
KindCalendarEventRSVP int = 31925 KindCalendarEventRSVP uint16 = 31925
KindHandlerRecommendation int = 31989 KindHandlerRecommendation uint16 = 31989
KindHandlerInformation int = 31990 KindHandlerInformation uint16 = 31990
KindVideoEvent int = 34235 KindVideoEvent uint16 = 34235
KindShortVideoEvent int = 34236 KindShortVideoEvent uint16 = 34236
KindVideoViewEvent int = 34237 KindVideoViewEvent uint16 = 34237
KindCommunityDefinition int = 34550 KindCommunityDefinition uint16 = 34550
) )
func IsRegularKind(kind int) bool { func IsRegularKind(kind uint16) bool {
return kind < 10000 && kind != 0 && kind != 3 return kind < 10000 && kind != 0 && kind != 3
} }
func IsReplaceableKind(kind int) bool { func IsReplaceableKind(kind uint16) bool {
return kind == 0 || kind == 3 || (10000 <= kind && kind < 20000) return kind == 0 || kind == 3 || (10000 <= kind && kind < 20000)
} }
func IsEphemeralKind(kind int) bool { func IsEphemeralKind(kind uint16) bool {
return 20000 <= kind && kind < 30000 return 20000 <= kind && kind < 30000
} }
func IsAddressableKind(kind int) bool { func IsAddressableKind(kind uint16) bool {
return 30000 <= kind && kind < 40000 return 30000 <= kind && kind < 40000
} }

View File

@@ -4,7 +4,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )

View File

@@ -8,7 +8,7 @@ import (
"strings" "strings"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
var NIP05_REGEX = regexp.MustCompile(`^(?:([\w.+-]+)@)?([\w_-]+(\.[\w_-]+)+)$`) var NIP05_REGEX = regexp.MustCompile(`^(?:([\w.+-]+)@)?([\w_-]+(\.[\w_-]+)+)$`)

View File

@@ -1,6 +1,6 @@
package nip10 package nip10
import "github.com/nbd-wtf/go-nostr" import "fiatjaf.com/nostrlib"
func GetThreadRoot(tags nostr.Tags) *nostr.EventPointer { func GetThreadRoot(tags nostr.Tags) *nostr.EventPointer {
for _, tag := range tags { for _, tag := range tags {

View File

@@ -7,7 +7,7 @@ import (
"time" "time"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
// Fetch fetches the NIP-11 metadata for a relay. // Fetch fetches the NIP-11 metadata for a relay.

View File

@@ -9,7 +9,7 @@ import (
"runtime" "runtime"
"strconv" "strconv"
nostr "github.com/nbd-wtf/go-nostr" nostr "fiatjaf.com/nostrlib"
) )
var ( var (

View File

@@ -8,7 +8,7 @@ import (
"testing" "testing"
"time" "time"
nostr "github.com/nbd-wtf/go-nostr" nostr "fiatjaf.com/nostrlib"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )

View File

@@ -1,6 +1,6 @@
package nip14 package nip14
import "github.com/nbd-wtf/go-nostr" import "fiatjaf.com/nostrlib"
func GetSubject(tags nostr.Tags) string { func GetSubject(tags nostr.Tags) string {
for _, tag := range tags { for _, tag := range tags {

View File

@@ -5,8 +5,8 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/nip59" "fiatjaf.com/nostrlib/nip59"
) )
func GetDMRelays(ctx context.Context, pubkey string, pool *nostr.SimplePool, relaysToQuery []string) []string { func GetDMRelays(ctx context.Context, pubkey string, pool *nostr.SimplePool, relaysToQuery []string) []string {

View File

@@ -7,7 +7,7 @@ import (
"fmt" "fmt"
"github.com/btcsuite/btcd/btcutil/bech32" "github.com/btcsuite/btcd/btcutil/bech32"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
func Decode(bech32string string) (prefix string, value any, err error) { func Decode(bech32string string) (prefix string, value any, err error) {

View File

@@ -3,7 +3,7 @@ package nip19
import ( import (
"testing" "testing"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )

View File

@@ -1,8 +1,8 @@
package nip22 package nip22
import ( import (
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
"github.com/nbd-wtf/go-nostr/nip73" "fiatjaf.com/nostr/nip73"
) )
func GetThreadRoot(tags nostr.Tags) nostr.Pointer { func GetThreadRoot(tags nostr.Tags) nostr.Pointer {

View File

@@ -6,7 +6,7 @@ import (
"slices" "slices"
"strings" "strings"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
type GroupAddress struct { type GroupAddress struct {

View File

@@ -3,7 +3,7 @@ package nip29
import ( import (
"slices" "slices"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
type Role struct { type Role struct {

View File

@@ -1,6 +1,6 @@
package nip31 package nip31
import "github.com/nbd-wtf/go-nostr" import "fiatjaf.com/nostrlib"
func GetAlt(event nostr.Event) string { func GetAlt(event nostr.Event) string {
for _, tag := range event.Tags { for _, tag := range event.Tags {

View File

@@ -4,7 +4,7 @@ import (
"strings" "strings"
"github.com/bluekeyes/go-gitdiff/gitdiff" "github.com/bluekeyes/go-gitdiff/gitdiff"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
type Patch struct { type Patch struct {

View File

@@ -4,7 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
type Repository struct { type Repository struct {

View File

@@ -3,7 +3,7 @@ package nip34
import ( import (
"strings" "strings"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
type RepositoryState struct { type RepositoryState struct {

View File

@@ -3,7 +3,7 @@ package nip40
import ( import (
"strconv" "strconv"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
// GetExpiration returns the expiration timestamp for this event, or -1 if no "expiration" tag exists or // GetExpiration returns the expiration timestamp for this event, or -1 if no "expiration" tag exists or

View File

@@ -5,7 +5,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
// CreateUnsignedAuthEvent creates an event which should be sent via an "AUTH" command. // CreateUnsignedAuthEvent creates an event which should be sent via an "AUTH" command.

View File

@@ -8,7 +8,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )

View File

@@ -4,7 +4,7 @@ import (
"iter" "iter"
"strconv" "strconv"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
func HyperLogLogEventPubkeyOffsetsAndReferencesForEvent(evt *nostr.Event) iter.Seq2[string, int] { func HyperLogLogEventPubkeyOffsetsAndReferencesForEvent(evt *nostr.Event) iter.Seq2[string, int] {

View File

@@ -3,7 +3,7 @@ package nip45
import ( import (
"strconv" "strconv"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
// HyperLogLogEventPubkeyOffsetForFilter returns the deterministic pubkey offset that will be used // HyperLogLogEventPubkeyOffsetForFilter returns the deterministic pubkey offset that will be used

View File

@@ -3,9 +3,9 @@ package nip46
import ( import (
"fmt" "fmt"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/nip04" "fiatjaf.com/nostrlib/nip04"
"github.com/nbd-wtf/go-nostr/nip44" "fiatjaf.com/nostrlib/nip44"
) )
type Session struct { type Session struct {

View File

@@ -9,9 +9,9 @@ import (
"sync/atomic" "sync/atomic"
"github.com/mailru/easyjson" "github.com/mailru/easyjson"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/nip04" "fiatjaf.com/nostrlib/nip04"
"github.com/nbd-wtf/go-nostr/nip44" "fiatjaf.com/nostrlib/nip44"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
) )

View File

@@ -7,9 +7,9 @@ import (
"sync" "sync"
"github.com/mailru/easyjson" "github.com/mailru/easyjson"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/nip04" "fiatjaf.com/nostrlib/nip04"
"github.com/nbd-wtf/go-nostr/nip44" "fiatjaf.com/nostrlib/nip44"
) )
var _ Signer = (*DynamicSigner)(nil) var _ Signer = (*DynamicSigner)(nil)

View File

@@ -6,7 +6,7 @@ import (
"strings" "strings"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
var json = jsoniter.ConfigFastest var json = jsoniter.ConfigFastest

View File

@@ -7,9 +7,9 @@ import (
"sync" "sync"
"github.com/mailru/easyjson" "github.com/mailru/easyjson"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/nip04" "fiatjaf.com/nostrlib/nip04"
"github.com/nbd-wtf/go-nostr/nip44" "fiatjaf.com/nostrlib/nip44"
) )
var _ Signer = (*StaticKeySigner)(nil) var _ Signer = (*StaticKeySigner)(nil)

View File

@@ -4,7 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/nbd-wtf/go-nostr/nip05" "fiatjaf.com/nostrlib/nip05"
) )
func queryWellKnownNostrJson(ctx context.Context, fullname string) (pubkey string, relays []string, err error) { func queryWellKnownNostrJson(ctx context.Context, fullname string) (pubkey string, relays []string, err error) {

View File

@@ -4,7 +4,7 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
type CalendarEventKind int type CalendarEventKind int

View File

@@ -4,7 +4,7 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
type LiveEvent struct { type LiveEvent struct {

View File

@@ -5,8 +5,8 @@ import (
"math/rand" "math/rand"
"github.com/mailru/easyjson" "github.com/mailru/easyjson"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/nip44" "fiatjaf.com/nostrlib/nip44"
) )
// GiftWrap takes a 'rumor', encrypts it with our own key, making a 'seal', then encrypts that with a nonce key and // GiftWrap takes a 'rumor', encrypts it with our own key, making a 'seal', then encrypts that with a nonce key and

View File

@@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
type HistoryEntry struct { type HistoryEntry struct {

View File

@@ -9,7 +9,7 @@ import (
"github.com/elnosh/gonuts/cashu/nuts/nut02" "github.com/elnosh/gonuts/cashu/nuts/nut02"
"github.com/elnosh/gonuts/cashu/nuts/nut04" "github.com/elnosh/gonuts/cashu/nuts/nut04"
"github.com/elnosh/gonuts/cashu/nuts/nut05" "github.com/elnosh/gonuts/cashu/nuts/nut05"
"github.com/nbd-wtf/go-nostr/nip60/client" "fiatjaf.com/nostrlib/nip60/client"
) )
type lightningSwapStatus int type lightningSwapStatus int

View File

@@ -7,8 +7,8 @@ import (
"github.com/elnosh/gonuts/cashu" "github.com/elnosh/gonuts/cashu"
"github.com/elnosh/gonuts/cashu/nuts/nut05" "github.com/elnosh/gonuts/cashu/nuts/nut05"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/nip60/client" "fiatjaf.com/nostrlib/nip60/client"
) )
func (w *Wallet) PayBolt11(ctx context.Context, invoice string, opts ...SendOption) (string, error) { func (w *Wallet) PayBolt11(ctx context.Context, invoice string, opts ...SendOption) (string, error) {

View File

@@ -7,8 +7,8 @@ import (
"github.com/elnosh/gonuts/cashu" "github.com/elnosh/gonuts/cashu"
"github.com/elnosh/gonuts/cashu/nuts/nut10" "github.com/elnosh/gonuts/cashu/nuts/nut10"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/nip60/client" "fiatjaf.com/nostrlib/nip60/client"
) )
type receiveSettings struct { type receiveSettings struct {

View File

@@ -6,7 +6,7 @@ import (
"github.com/elnosh/gonuts/cashu" "github.com/elnosh/gonuts/cashu"
"github.com/elnosh/gonuts/cashu/nuts/nut04" "github.com/elnosh/gonuts/cashu/nuts/nut04"
"github.com/nbd-wtf/go-nostr/nip60/client" "fiatjaf.com/nostrlib/nip60/client"
) )
func (w *Wallet) SendExternal( func (w *Wallet) SendExternal(

View File

@@ -11,8 +11,8 @@ import (
"github.com/elnosh/gonuts/cashu/nuts/nut02" "github.com/elnosh/gonuts/cashu/nuts/nut02"
"github.com/elnosh/gonuts/cashu/nuts/nut10" "github.com/elnosh/gonuts/cashu/nuts/nut10"
"github.com/elnosh/gonuts/cashu/nuts/nut11" "github.com/elnosh/gonuts/cashu/nuts/nut11"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/nip60/client" "fiatjaf.com/nostrlib/nip60/client"
) )
type SendOption func(opts *sendSettings) type SendOption func(opts *sendSettings)

View File

@@ -10,7 +10,7 @@ import (
"github.com/elnosh/gonuts/cashu/nuts/nut02" "github.com/elnosh/gonuts/cashu/nuts/nut02"
"github.com/elnosh/gonuts/cashu/nuts/nut03" "github.com/elnosh/gonuts/cashu/nuts/nut03"
"github.com/elnosh/gonuts/cashu/nuts/nut10" "github.com/elnosh/gonuts/cashu/nuts/nut10"
"github.com/nbd-wtf/go-nostr/nip60/client" "fiatjaf.com/nostrlib/nip60/client"
) )
type swapSettings struct { type swapSettings struct {

View File

@@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"github.com/elnosh/gonuts/cashu" "github.com/elnosh/gonuts/cashu"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
type Token struct { type Token struct {

View File

@@ -11,7 +11,7 @@ import (
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
type Wallet struct { type Wallet struct {

View File

@@ -10,8 +10,8 @@ import (
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/elnosh/gonuts/cashu" "github.com/elnosh/gonuts/cashu"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/keyer" "fiatjaf.com/nostrlib/keyer"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/exp/rand" "golang.org/x/exp/rand"
) )

View File

@@ -5,7 +5,7 @@ import (
"slices" "slices"
"github.com/elnosh/gonuts/cashu" "github.com/elnosh/gonuts/cashu"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
type Info struct { type Info struct {

View File

@@ -9,8 +9,8 @@ import (
"slices" "slices"
"strings" "strings"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/nip60" "fiatjaf.com/nostrlib/nip60"
) )
var NutzapsNotAccepted = errors.New("user doesn't accept nutzaps") var NutzapsNotAccepted = errors.New("user doesn't accept nutzaps")

View File

@@ -1,6 +1,6 @@
package nip73 package nip73
import "github.com/nbd-wtf/go-nostr" import "fiatjaf.com/nostr"
var _ nostr.Pointer = (*ExternalPointer)(nil) var _ nostr.Pointer = (*ExternalPointer)(nil)

View File

@@ -8,7 +8,7 @@ import (
"github.com/mailru/easyjson" "github.com/mailru/easyjson"
jwriter "github.com/mailru/easyjson/jwriter" jwriter "github.com/mailru/easyjson/jwriter"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
) )

View File

@@ -7,8 +7,8 @@ import (
"github.com/fiatjaf/eventstore" "github.com/fiatjaf/eventstore"
"github.com/fiatjaf/eventstore/slicestore" "github.com/fiatjaf/eventstore/slicestore"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/nip77" "fiatjaf.com/nostrlib/nip77"
) )
func main() { func main() {

View File

@@ -3,7 +3,7 @@ package negentropy
import ( import (
"fmt" "fmt"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
func (n *Negentropy) readTimestamp(reader *StringHexReader) (nostr.Timestamp, error) { func (n *Negentropy) readTimestamp(reader *StringHexReader) (nostr.Timestamp, error) {

View File

@@ -9,9 +9,9 @@ import (
"sync" "sync"
"testing" "testing"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/nip77/negentropy" "fiatjaf.com/nostrlib/nip77/negentropy"
"github.com/nbd-wtf/go-nostr/nip77/negentropy/storage/vector" "fiatjaf.com/nostrlib/nip77/negentropy/storage/vector"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )

View File

@@ -6,7 +6,7 @@ import (
"strings" "strings"
"unsafe" "unsafe"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
const ( const (

View File

@@ -5,7 +5,7 @@ import (
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"github.com/nbd-wtf/go-nostr/nip77/negentropy" "fiatjaf.com/nostrlib/nip77/negentropy"
) )
type Accumulator struct { type Accumulator struct {

View File

@@ -6,9 +6,9 @@ import (
"iter" "iter"
"slices" "slices"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
"github.com/nbd-wtf/go-nostr/nip77/negentropy" "fiatjaf.com/nostr/nip77/negentropy"
"github.com/nbd-wtf/go-nostr/nip77/negentropy/storage" "fiatjaf.com/nostr/nip77/negentropy/storage"
) )
type Vector struct { type Vector struct {

View File

@@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
const FingerprintSize = 16 const FingerprintSize = 16

View File

@@ -6,9 +6,9 @@ import (
"sync" "sync"
"testing" "testing"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/nip77/negentropy" "fiatjaf.com/nostrlib/nip77/negentropy"
"github.com/nbd-wtf/go-nostr/nip77/negentropy/storage/vector" "fiatjaf.com/nostrlib/nip77/negentropy/storage/vector"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )

View File

@@ -5,9 +5,9 @@ import (
"fmt" "fmt"
"sync" "sync"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/nip77/negentropy" "fiatjaf.com/nostrlib/nip77/negentropy"
"github.com/nbd-wtf/go-nostr/nip77/negentropy/storage/vector" "fiatjaf.com/nostrlib/nip77/negentropy/storage/vector"
) )
type direction struct { type direction struct {

View File

@@ -5,7 +5,7 @@ import (
"math" "math"
"net" "net"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
func DecodeRequest(req Request) (MethodParams, error) { func DecodeRequest(req Request) (MethodParams, error) {

View File

@@ -4,7 +4,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
type IMeta []IMetaEntry type IMeta []IMetaEntry

View File

@@ -3,7 +3,7 @@ package nip92
import ( import (
"testing" "testing"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )

View File

@@ -3,7 +3,7 @@ package nip94
import ( import (
"strings" "strings"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
func ParseFileMetadata(event nostr.Event) FileMetadata { func ParseFileMetadata(event nostr.Event) FileMetadata {

View File

@@ -14,7 +14,7 @@ import (
"strconv" "strconv"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
// Upload uploads a file to the provided req.Host. // Upload uploads a file to the provided req.Host.

View File

@@ -7,7 +7,7 @@ import (
"os" "os"
"testing" "testing"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )

View File

@@ -5,7 +5,7 @@ import (
"io" "io"
"net/http" "net/http"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
) )
// UploadRequest is a NIP96 upload request. // UploadRequest is a NIP96 upload request.

View File

@@ -1,6 +1,7 @@
package nostr package nostr
import ( import (
"encoding/hex"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
@@ -30,15 +31,15 @@ var (
// ProfilePointer represents a pointer to a Nostr profile. // ProfilePointer represents a pointer to a Nostr profile.
type ProfilePointer struct { type ProfilePointer struct {
PublicKey string `json:"pubkey"` PublicKey PubKey `json:"pubkey"`
Relays []string `json:"relays,omitempty"` 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). // 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) { func ProfilePointerFromTag(refTag Tag) (ProfilePointer, error) {
pk := refTag[1] pk, err := PubKeyFromHex(refTag[1])
if !IsValidPublicKey(pk) { if err != nil {
return ProfilePointer{}, fmt.Errorf("invalid pubkey '%s'", pk) return ProfilePointer{}, err
} }
pointer := ProfilePointer{ pointer := ProfilePointer{
@@ -54,29 +55,29 @@ func ProfilePointerFromTag(refTag Tag) (ProfilePointer, error) {
// MatchesEvent checks if the pointer matches an event. // MatchesEvent checks if the pointer matches an event.
func (ep ProfilePointer) MatchesEvent(_ Event) bool { return false } func (ep ProfilePointer) MatchesEvent(_ Event) bool { return false }
func (ep ProfilePointer) AsTagReference() string { return ep.PublicKey } func (ep ProfilePointer) AsFilter() Filter { return Filter{Authors: []PubKey{ep.PublicKey}} }
func (ep ProfilePointer) AsFilter() Filter { return Filter{Authors: []string{ep.PublicKey}} } func (ep ProfilePointer) AsTagReference() string { return hex.EncodeToString(ep.PublicKey[:]) }
func (ep ProfilePointer) AsTag() Tag { func (ep ProfilePointer) AsTag() Tag {
if len(ep.Relays) > 0 { if len(ep.Relays) > 0 {
return Tag{"p", ep.PublicKey, ep.Relays[0]} return Tag{"p", hex.EncodeToString(ep.PublicKey[:]), ep.Relays[0]}
} }
return Tag{"p", ep.PublicKey} return Tag{"p", hex.EncodeToString(ep.PublicKey[:])}
} }
// EventPointer represents a pointer to a nostr event. // EventPointer represents a pointer to a nostr event.
type EventPointer struct { type EventPointer struct {
ID string `json:"id"` ID ID `json:"id"`
Relays []string `json:"relays,omitempty"` Relays []string `json:"relays,omitempty"`
Author string `json:"author,omitempty"` Author PubKey `json:"author,omitempty"`
Kind int `json:"kind,omitempty"` Kind uint16 `json:"kind,omitempty"`
} }
// EventPointerFromTag creates an EventPointer from an "e" tag (but it could be other tag name, it isn't checked). // 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) { func EventPointerFromTag(refTag Tag) (EventPointer, error) {
id := refTag[1] id, err := IDFromHex(refTag[1])
if !IsValid32ByteHex(id) { if err != nil {
return EventPointer{}, fmt.Errorf("invalid id '%s'", id) return EventPointer{}, err
} }
pointer := EventPointer{ pointer := EventPointer{
@@ -86,35 +87,35 @@ func EventPointerFromTag(refTag Tag) (EventPointer, error) {
if relay := (refTag)[2]; IsValidRelayURL(relay) { if relay := (refTag)[2]; IsValidRelayURL(relay) {
pointer.Relays = []string{relay} pointer.Relays = []string{relay}
} }
if len(refTag) > 3 && IsValidPublicKey(refTag[3]) { if len(refTag) > 3 {
pointer.Author = (refTag)[3] if pk, err := PubKeyFromHex(refTag[3]); err == nil {
} else if len(refTag) > 4 && IsValidPublicKey(refTag[4]) { pointer.Author = pk
pointer.Author = (refTag)[4] }
} }
} }
return pointer, nil return pointer, nil
} }
func (ep EventPointer) MatchesEvent(evt Event) bool { return evt.ID == ep.ID } 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: []ID{ep.ID}} }
func (ep EventPointer) AsFilter() Filter { return Filter{IDs: []string{ep.ID}} } func (ep EventPointer) AsTagReference() string { return hex.EncodeToString(ep.ID[:]) }
// AsTag converts the pointer to a Tag. // AsTag converts the pointer to a Tag.
func (ep EventPointer) AsTag() Tag { func (ep EventPointer) AsTag() Tag {
if len(ep.Relays) > 0 { if len(ep.Relays) > 0 {
if ep.Author != "" { if ep.Author != [32]byte{} {
return Tag{"e", ep.ID, ep.Relays[0], ep.Author} return Tag{"e", hex.EncodeToString(ep.ID[:]), ep.Relays[0], hex.EncodeToString(ep.Author[:])}
} else { } else {
return Tag{"e", ep.ID, ep.Relays[0]} return Tag{"e", hex.EncodeToString(ep.ID[:]), ep.Relays[0]}
} }
} }
return Tag{"e", ep.ID} return Tag{"e", hex.EncodeToString(ep.ID[:])}
} }
// EntityPointer represents a pointer to a nostr entity (addressable event). // EntityPointer represents a pointer to a nostr entity (addressable event).
type EntityPointer struct { type EntityPointer struct {
PublicKey string `json:"pubkey"` PublicKey PubKey `json:"pubkey"`
Kind int `json:"kind,omitempty"` Kind uint16 `json:"kind,omitempty"`
Identifier string `json:"identifier,omitempty"` Identifier string `json:"identifier,omitempty"`
Relays []string `json:"relays,omitempty"` Relays []string `json:"relays,omitempty"`
} }
@@ -125,8 +126,10 @@ func EntityPointerFromTag(refTag Tag) (EntityPointer, error) {
if len(spl) != 3 { if len(spl) != 3 {
return EntityPointer{}, fmt.Errorf("invalid addr ref '%s'", refTag[1]) return EntityPointer{}, fmt.Errorf("invalid addr ref '%s'", refTag[1])
} }
if !IsValidPublicKey(spl[1]) {
return EntityPointer{}, fmt.Errorf("invalid addr pubkey '%s'", spl[1]) pk, err := PubKeyFromHex(spl[1])
if err != nil {
return EntityPointer{}, err
} }
kind, err := strconv.Atoi(spl[0]) kind, err := strconv.Atoi(spl[0])
@@ -135,8 +138,8 @@ func EntityPointerFromTag(refTag Tag) (EntityPointer, error) {
} }
pointer := EntityPointer{ pointer := EntityPointer{
Kind: kind, Kind: uint16(kind),
PublicKey: spl[1], PublicKey: pk,
Identifier: spl[2], Identifier: spl[2],
} }
if len(refTag) > 2 { if len(refTag) > 2 {
@@ -161,8 +164,8 @@ func (ep EntityPointer) AsTagReference() string {
func (ep EntityPointer) AsFilter() Filter { func (ep EntityPointer) AsFilter() Filter {
return Filter{ return Filter{
Kinds: []int{ep.Kind}, Kinds: []uint16{ep.Kind},
Authors: []string{ep.PublicKey}, Authors: []PubKey{ep.PublicKey},
Tags: TagMap{"d": []string{ep.Identifier}}, Tags: TagMap{"d": []string{ep.Identifier}},
} }
} }

77
pool.go
View File

@@ -12,7 +12,7 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/nbd-wtf/go-nostr/nip45/hyperloglog" "fiatjaf.com/nostr/nip45/hyperloglog"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
) )
@@ -29,8 +29,8 @@ type SimplePool struct {
cancel context.CancelCauseFunc cancel context.CancelCauseFunc
eventMiddleware func(RelayEvent) eventMiddleware func(RelayEvent)
duplicateMiddleware func(relay string, id string) duplicateMiddleware func(relay string, id ID)
queryMiddleware func(relay string, pubkey string, kind int) queryMiddleware func(relay string, pubkey PubKey, kind uint16)
// custom things not often used // custom things not often used
penaltyBoxMu sync.Mutex penaltyBoxMu sync.Mutex
@@ -139,7 +139,7 @@ func (h WithEventMiddleware) ApplyPoolOption(pool *SimplePool) {
} }
// WithDuplicateMiddleware is a function that will be called with all duplicate ids received. // WithDuplicateMiddleware is a function that will be called with all duplicate ids received.
type WithDuplicateMiddleware func(relay string, id string) type WithDuplicateMiddleware func(relay string, id ID)
func (h WithDuplicateMiddleware) ApplyPoolOption(pool *SimplePool) { func (h WithDuplicateMiddleware) ApplyPoolOption(pool *SimplePool) {
pool.duplicateMiddleware = h pool.duplicateMiddleware = h
@@ -147,7 +147,7 @@ func (h WithDuplicateMiddleware) ApplyPoolOption(pool *SimplePool) {
// WithAuthorKindQueryMiddleware 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). // 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) type WithAuthorKindQueryMiddleware func(relay string, pubkey PubKey, kind uint16)
func (h WithAuthorKindQueryMiddleware) ApplyPoolOption(pool *SimplePool) { func (h WithAuthorKindQueryMiddleware) ApplyPoolOption(pool *SimplePool) {
pool.queryMiddleware = h pool.queryMiddleware = h
@@ -271,7 +271,7 @@ func (pool *SimplePool) SubscribeMany(
filter Filter, filter Filter,
opts ...SubscriptionOption, opts ...SubscriptionOption,
) chan RelayEvent { ) chan RelayEvent {
return pool.subMany(ctx, urls, Filters{filter}, nil, opts...) return pool.subMany(ctx, urls, filter, nil, opts...)
} }
// FetchMany opens a subscription, much like SubscribeMany, but it ends as soon as all Relays // FetchMany opens a subscription, much like SubscribeMany, but it ends as soon as all Relays
@@ -282,17 +282,7 @@ func (pool *SimplePool) FetchMany(
filter Filter, filter Filter,
opts ...SubscriptionOption, opts ...SubscriptionOption,
) chan RelayEvent { ) chan RelayEvent {
return pool.SubManyEose(ctx, urls, Filters{filter}, opts...) return pool.SubManyEose(ctx, urls, filter, opts...)
}
// Deprecated: SubMany is deprecated: use SubscribeMany instead.
func (pool *SimplePool) SubMany(
ctx context.Context,
urls []string,
filters Filters,
opts ...SubscriptionOption,
) chan RelayEvent {
return pool.subMany(ctx, urls, filters, nil, opts...)
} }
// SubscribeManyNotifyEOSE is like SubscribeMany, but takes a channel that is closed when // SubscribeManyNotifyEOSE is like SubscribeMany, but takes a channel that is closed when
@@ -304,11 +294,11 @@ func (pool *SimplePool) SubscribeManyNotifyEOSE(
eoseChan chan struct{}, eoseChan chan struct{},
opts ...SubscriptionOption, opts ...SubscriptionOption,
) chan RelayEvent { ) chan RelayEvent {
return pool.subMany(ctx, urls, Filters{filter}, eoseChan, opts...) return pool.subMany(ctx, urls, filter, eoseChan, opts...)
} }
type ReplaceableKey struct { type ReplaceableKey struct {
PubKey string PubKey PubKey
D string D string
} }
@@ -363,7 +353,7 @@ func (pool *SimplePool) FetchManyReplaceable(
hasAuthed := false hasAuthed := false
subscribe: subscribe:
sub, err := relay.Subscribe(ctx, Filters{filter}, opts...) sub, err := relay.Subscribe(ctx, filter, opts...)
if err != nil { if err != nil {
debugLogf("error subscribing to %s with %v: %s", relay, filter, err) debugLogf("error subscribing to %s with %v: %s", relay, filter, err)
return return
@@ -414,14 +404,14 @@ func (pool *SimplePool) FetchManyReplaceable(
func (pool *SimplePool) subMany( func (pool *SimplePool) subMany(
ctx context.Context, ctx context.Context,
urls []string, urls []string,
filters Filters, filter Filter,
eoseChan chan struct{}, eoseChan chan struct{},
opts ...SubscriptionOption, opts ...SubscriptionOption,
) chan RelayEvent { ) chan RelayEvent {
ctx, cancel := context.WithCancelCause(ctx) ctx, cancel := context.WithCancelCause(ctx)
_ = cancel // do this so `go vet` will stop complaining _ = cancel // do this so `go vet` will stop complaining
events := make(chan RelayEvent) events := make(chan RelayEvent)
seenAlready := xsync.NewMapOf[string, Timestamp]() seenAlready := xsync.NewMapOf[ID, Timestamp]()
ticker := time.NewTicker(seenAlreadyDropTick) ticker := time.NewTicker(seenAlreadyDropTick)
eoseWg := sync.WaitGroup{} eoseWg := sync.WaitGroup{}
@@ -471,7 +461,6 @@ func (pool *SimplePool) subMany(
var sub *Subscription var sub *Subscription
if mh := pool.queryMiddleware; mh != nil { if mh := pool.queryMiddleware; mh != nil {
for _, filter := range filters {
if filter.Kinds != nil && filter.Authors != nil { if filter.Kinds != nil && filter.Authors != nil {
for _, kind := range filter.Kinds { for _, kind := range filter.Kinds {
for _, author := range filter.Authors { for _, author := range filter.Authors {
@@ -480,7 +469,6 @@ func (pool *SimplePool) subMany(
} }
} }
} }
}
relay, err := pool.EnsureRelay(nm) relay, err := pool.EnsureRelay(nm)
if err != nil { if err != nil {
@@ -497,13 +485,15 @@ func (pool *SimplePool) subMany(
hasAuthed = false hasAuthed = false
subscribe: subscribe:
sub, err = relay.Subscribe(ctx, filters, append(opts, WithCheckDuplicate(func(id, relay string) bool { sub, err = relay.Subscribe(ctx, filter, append(opts,
WithCheckDuplicate(func(id ID, relay string) bool {
_, exists := seenAlready.Load(id) _, exists := seenAlready.Load(id)
if exists && pool.duplicateMiddleware != nil { if exists && pool.duplicateMiddleware != nil {
pool.duplicateMiddleware(relay, id) pool.duplicateMiddleware(relay, id)
} }
return exists return exists
}))...) }),
)...)
if err != nil { if err != nil {
debugLogf("%s reconnecting because subscription died\n", nm) debugLogf("%s reconnecting because subscription died\n", nm)
goto reconnect goto reconnect
@@ -529,9 +519,7 @@ func (pool *SimplePool) subMany(
// so we will update the filters here to include only events seem from now on // so we will update the filters here to include only events seem from now on
// and try to reconnect until we succeed // and try to reconnect until we succeed
now := Now() now := Now()
for i := range filters { filter.Since = &now
filters[i].Since = &now
}
debugLogf("%s reconnecting because sub.Events is broken\n", nm) debugLogf("%s reconnecting because sub.Events is broken\n", nm)
goto reconnect goto reconnect
} }
@@ -591,25 +579,26 @@ func (pool *SimplePool) subMany(
func (pool *SimplePool) SubManyEose( func (pool *SimplePool) SubManyEose(
ctx context.Context, ctx context.Context,
urls []string, urls []string,
filters Filters, filter Filter,
opts ...SubscriptionOption, opts ...SubscriptionOption,
) chan RelayEvent { ) chan RelayEvent {
seenAlready := xsync.NewMapOf[string, struct{}]() seenAlready := xsync.NewMapOf[ID, struct{}]()
return pool.subManyEoseNonOverwriteCheckDuplicate(ctx, urls, filters, return pool.subManyEoseNonOverwriteCheckDuplicate(ctx, urls, filter,
WithCheckDuplicate(func(id, relay string) bool { WithCheckDuplicate(func(id ID, relay string) bool {
_, exists := seenAlready.LoadOrStore(id, struct{}{}) _, exists := seenAlready.LoadOrStore(id, struct{}{})
if exists && pool.duplicateMiddleware != nil { if exists && pool.duplicateMiddleware != nil {
pool.duplicateMiddleware(relay, id) pool.duplicateMiddleware(relay, id)
} }
return exists return exists
}), }),
opts...) opts...,
)
} }
func (pool *SimplePool) subManyEoseNonOverwriteCheckDuplicate( func (pool *SimplePool) subManyEoseNonOverwriteCheckDuplicate(
ctx context.Context, ctx context.Context,
urls []string, urls []string,
filters Filters, filter Filter,
wcd WithCheckDuplicate, wcd WithCheckDuplicate,
opts ...SubscriptionOption, opts ...SubscriptionOption,
) chan RelayEvent { ) chan RelayEvent {
@@ -633,7 +622,6 @@ func (pool *SimplePool) subManyEoseNonOverwriteCheckDuplicate(
defer wg.Done() defer wg.Done()
if mh := pool.queryMiddleware; mh != nil { if mh := pool.queryMiddleware; mh != nil {
for _, filter := range filters {
if filter.Kinds != nil && filter.Authors != nil { if filter.Kinds != nil && filter.Authors != nil {
for _, kind := range filter.Kinds { for _, kind := range filter.Kinds {
for _, author := range filter.Authors { for _, author := range filter.Authors {
@@ -642,20 +630,19 @@ func (pool *SimplePool) subManyEoseNonOverwriteCheckDuplicate(
} }
} }
} }
}
relay, err := pool.EnsureRelay(nm) relay, err := pool.EnsureRelay(nm)
if err != nil { if err != nil {
debugLogf("error connecting to %s with %v: %s", nm, filters, err) debugLogf("error connecting to %s with %v: %s", nm, filter, err)
return return
} }
hasAuthed := false hasAuthed := false
subscribe: subscribe:
sub, err := relay.Subscribe(ctx, filters, opts...) sub, err := relay.Subscribe(ctx, filter, opts...)
if err != nil { if err != nil {
debugLogf("error subscribing to %s with %v: %s", relay, filters, err) debugLogf("error subscribing to %s with %v: %s", relay, filter, err)
return return
} }
@@ -719,7 +706,7 @@ func (pool *SimplePool) CountMany(
if err != nil { if err != nil {
return return
} }
ce, err := relay.countInternal(ctx, Filters{filter}, opts...) ce, err := relay.countInternal(ctx, filter, opts...)
if err != nil { if err != nil {
return return
} }
@@ -742,7 +729,7 @@ func (pool *SimplePool) QuerySingle(
opts ...SubscriptionOption, opts ...SubscriptionOption,
) *RelayEvent { ) *RelayEvent {
ctx, cancel := context.WithCancelCause(ctx) ctx, cancel := context.WithCancelCause(ctx)
for ievt := range pool.SubManyEose(ctx, urls, Filters{filter}, opts...) { for ievt := range pool.SubManyEose(ctx, urls, filter, opts...) {
cancel(errors.New("got the first event and ended successfully")) cancel(errors.New("got the first event and ended successfully"))
return &ievt return &ievt
} }
@@ -759,14 +746,14 @@ func (pool *SimplePool) BatchedSubManyEose(
res := make(chan RelayEvent) res := make(chan RelayEvent)
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(len(dfs)) wg.Add(len(dfs))
seenAlready := xsync.NewMapOf[string, struct{}]() seenAlready := xsync.NewMapOf[ID, struct{}]()
for _, df := range dfs { for _, df := range dfs {
go func(df DirectedFilter) { go func(df DirectedFilter) {
for ie := range pool.subManyEoseNonOverwriteCheckDuplicate(ctx, for ie := range pool.subManyEoseNonOverwriteCheckDuplicate(ctx,
[]string{df.Relay}, []string{df.Relay},
Filters{df.Filter}, df.Filter,
WithCheckDuplicate(func(id, relay string) bool { WithCheckDuplicate(func(id ID, relay string) bool {
_, exists := seenAlready.LoadOrStore(id, struct{}{}) _, exists := seenAlready.LoadOrStore(id, struct{}{})
if exists && pool.duplicateMiddleware != nil { if exists && pool.duplicateMiddleware != nil {
pool.duplicateMiddleware(relay, id) pool.duplicateMiddleware(relay, id)

View File

@@ -36,7 +36,7 @@ type Relay struct {
challenge string // NIP-42 challenge, we only keep the last challenge string // NIP-42 challenge, we only keep the last
noticeHandler func(string) // NIP-01 NOTICEs noticeHandler func(string) // NIP-01 NOTICEs
customHandler func(string) // nonstandard unparseable messages customHandler func(string) // nonstandard unparseable messages
okCallbacks *xsync.MapOf[string, func(bool, string)] okCallbacks *xsync.MapOf[ID, func(bool, string)]
writeQueue chan writeRequest writeQueue chan writeRequest
subscriptionChannelCloseQueue chan *Subscription subscriptionChannelCloseQueue chan *Subscription
@@ -58,7 +58,7 @@ func NewRelay(ctx context.Context, url string, opts ...RelayOption) *Relay {
connectionContext: ctx, connectionContext: ctx,
connectionContextCancel: cancel, connectionContextCancel: cancel,
Subscriptions: xsync.NewMapOf[int64, *Subscription](), Subscriptions: xsync.NewMapOf[int64, *Subscription](),
okCallbacks: xsync.NewMapOf[string, func(bool, string)](), okCallbacks: xsync.NewMapOf[ID, func(bool, string)](),
writeQueue: make(chan writeRequest), writeQueue: make(chan writeRequest),
subscriptionChannelCloseQueue: make(chan *Subscription), subscriptionChannelCloseQueue: make(chan *Subscription),
requestHeader: nil, requestHeader: nil,
@@ -351,7 +351,8 @@ func (r *Relay) Auth(ctx context.Context, sign func(event *Event) error) error {
return r.publish(ctx, authEvent.ID, &AuthEnvelope{Event: authEvent}) return r.publish(ctx, authEvent.ID, &AuthEnvelope{Event: authEvent})
} }
func (r *Relay) publish(ctx context.Context, id string, env Envelope) error { // publish can be used both for EVENT and for AUTH
func (r *Relay) publish(ctx context.Context, id ID, env Envelope) error {
var err error var err error
var cancel context.CancelFunc var cancel context.CancelFunc

View File

@@ -57,12 +57,11 @@ func TestPublish(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
} }
func makeKeyPair(t *testing.T) (priv, pub string) { func makeKeyPair(t *testing.T) (priv, pub [32]byte) {
t.Helper() t.Helper()
privkey := GeneratePrivateKey() privkey := GeneratePrivateKey()
pubkey, err := GetPublicKey(privkey) pubkey := GetPublicKey(privkey)
assert.NoError(t, err)
return privkey, pubkey return privkey, pubkey
} }

View File

@@ -74,7 +74,7 @@ func TestPublishBlocked(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
// send back a not ok nip-20 command result // send back a not ok nip-20 command result
res := []any{"OK", textNote.ID, false, "blocked"} res := []any{"OK", textNote.ID.String(), false, "blocked"}
websocket.JSON.Send(conn, res) websocket.JSON.Send(conn, res)
}) })
defer ws.Close() defer ws.Close()
@@ -175,12 +175,11 @@ var anyOriginHandshake = func(conf *websocket.Config, r *http.Request) error {
return nil return nil
} }
func makeKeyPair(t *testing.T) (priv, pub string) { func makeKeyPair(t *testing.T) (priv, pub [32]byte) {
t.Helper() t.Helper()
privkey := GeneratePrivateKey() privkey := GeneratePrivateKey()
pubkey, err := GetPublicKey(privkey) pubkey := GetPublicKey(privkey)
assert.NoError(t, err)
return privkey, pubkey return privkey, pubkey
} }

View File

@@ -5,8 +5,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
"github.com/nbd-wtf/go-nostr/sdk/dataloader" "fiatjaf.com/nostr/sdk/dataloader"
) )
// this is similar to replaceable_loader and reuses logic from that. // this is similar to replaceable_loader and reuses logic from that.
@@ -21,16 +21,16 @@ const (
) )
func (sys *System) initializeAddressableDataloaders() { func (sys *System) initializeAddressableDataloaders() {
sys.addressableLoaders = make([]*dataloader.Loader[string, []*nostr.Event], 4) sys.addressableLoaders = make([]*dataloader.Loader[nostr.PubKey, []*nostr.Event], 4)
sys.addressableLoaders[kind_30000] = sys.createAddressableDataloader(30000) sys.addressableLoaders[kind_30000] = sys.createAddressableDataloader(30000)
sys.addressableLoaders[kind_30002] = sys.createAddressableDataloader(30002) sys.addressableLoaders[kind_30002] = sys.createAddressableDataloader(30002)
sys.addressableLoaders[kind_30015] = sys.createAddressableDataloader(30015) sys.addressableLoaders[kind_30015] = sys.createAddressableDataloader(30015)
sys.addressableLoaders[kind_30030] = sys.createAddressableDataloader(30030) sys.addressableLoaders[kind_30030] = sys.createAddressableDataloader(30030)
} }
func (sys *System) createAddressableDataloader(kind int) *dataloader.Loader[string, []*nostr.Event] { func (sys *System) createAddressableDataloader(kind uint16) *dataloader.Loader[nostr.PubKey, []*nostr.Event] {
return dataloader.NewBatchedLoader( return dataloader.NewBatchedLoader(
func(ctxs []context.Context, pubkeys []string) map[string]dataloader.Result[[]*nostr.Event] { func(ctxs []context.Context, pubkeys []nostr.PubKey) map[nostr.PubKey]dataloader.Result[[]*nostr.Event] {
return sys.batchLoadAddressableEvents(ctxs, kind, pubkeys) return sys.batchLoadAddressableEvents(ctxs, kind, pubkeys)
}, },
dataloader.Options{ dataloader.Options{
@@ -42,11 +42,11 @@ func (sys *System) createAddressableDataloader(kind int) *dataloader.Loader[stri
func (sys *System) batchLoadAddressableEvents( func (sys *System) batchLoadAddressableEvents(
ctxs []context.Context, ctxs []context.Context,
kind int, kind uint16,
pubkeys []string, pubkeys []nostr.PubKey,
) map[string]dataloader.Result[[]*nostr.Event] { ) map[nostr.PubKey]dataloader.Result[[]*nostr.Event] {
batchSize := len(pubkeys) batchSize := len(pubkeys)
results := make(map[string]dataloader.Result[[]*nostr.Event], batchSize) results := make(map[nostr.PubKey]dataloader.Result[[]*nostr.Event], batchSize)
relayFilter := make([]nostr.DirectedFilter, 0, max(3, batchSize*2)) relayFilter := make([]nostr.DirectedFilter, 0, max(3, batchSize*2))
relayFilterIndex := make(map[string]int, max(3, batchSize*2)) relayFilterIndex := make(map[string]int, max(3, batchSize*2))
@@ -62,7 +62,7 @@ func (sys *System) batchLoadAddressableEvents(
defer cancel() defer cancel()
// build batched queries for the external relays // build batched queries for the external relays
go func(i int, pubkey string) { go func(i int, pubkey nostr.PubKey) {
// gather relays we'll use for this pubkey // gather relays we'll use for this pubkey
relays := sys.determineRelaysToQuery(ctx, pubkey, kind) relays := sys.determineRelaysToQuery(ctx, pubkey, kind)
@@ -77,8 +77,8 @@ func (sys *System) batchLoadAddressableEvents(
dfilter = nostr.DirectedFilter{ dfilter = nostr.DirectedFilter{
Relay: relay, Relay: relay,
Filter: nostr.Filter{ Filter: nostr.Filter{
Kinds: []int{kind}, Kinds: []uint16{kind},
Authors: make([]string, 0, batchSize-i /* this and all pubkeys after this can be added */), Authors: make([]nostr.PubKey, 0, batchSize-i /* this and all pubkeys after this can be added */),
}, },
} }
idx = len(relayFilter) idx = len(relayFilter)

View File

@@ -3,8 +3,8 @@ package cache
import "time" import "time"
type Cache32[V any] interface { type Cache32[V any] interface {
Get(k string) (v V, ok bool) Get(k [32]byte) (v V, ok bool)
Delete(k string) Delete(k [32]byte)
Set(k string, v V) bool Set(k [32]byte, v V) bool
SetWithTTL(k string, v V, d time.Duration) bool SetWithTTL(k [32]byte, v V, d time.Duration) bool
} }

View File

@@ -2,47 +2,33 @@ package cache_memory
import ( import (
"encoding/binary" "encoding/binary"
"encoding/hex"
"time" "time"
"github.com/dgraph-io/ristretto" "github.com/dgraph-io/ristretto"
) )
type RistrettoCache[V any] struct { type RistrettoCache[V any] struct {
Cache *ristretto.Cache[string, V] Cache *ristretto.Cache[uint64, V]
} }
func New32[V any](max int64) *RistrettoCache[V] { func New[V any](max int64) *RistrettoCache[V] {
cache, _ := ristretto.NewCache(&ristretto.Config[string, V]{ cache, _ := ristretto.NewCache(&ristretto.Config[uint64, V]{
NumCounters: max * 10, NumCounters: max * 10,
MaxCost: max, MaxCost: max,
BufferItems: 64, BufferItems: 64,
KeyToHash: func(key string) (uint64, uint64) { return h32(key), 0 }, KeyToHash: func(key uint64) (uint64, uint64) { return key, 0 },
}) })
return &RistrettoCache[V]{Cache: cache} return &RistrettoCache[V]{Cache: cache}
} }
func (s RistrettoCache[V]) Get(k string) (v V, ok bool) { return s.Cache.Get(k) } func (s RistrettoCache[V]) Get(k [32]byte) (v V, ok bool) {
func (s RistrettoCache[V]) Delete(k string) { s.Cache.Del(k) } return s.Cache.Get(binary.BigEndian.Uint64(k[32-8:]))
func (s RistrettoCache[V]) Set(k string, v V) bool { return s.Cache.Set(k, v, 1) } }
func (s RistrettoCache[V]) SetWithTTL(k string, v V, d time.Duration) bool { func (s RistrettoCache[V]) Delete(k [32]byte) { s.Cache.Del(binary.BigEndian.Uint64(k[32-8:])) }
return s.Cache.SetWithTTL(k, v, 1, d) func (s RistrettoCache[V]) Set(k [32]byte, v V) bool {
return s.Cache.Set(binary.BigEndian.Uint64(k[32-8:]), v, 1)
} }
func h32(key string) uint64 { func (s RistrettoCache[V]) SetWithTTL(k [32]byte, v V, d time.Duration) bool {
// we get an event id or pubkey as hex, return s.Cache.SetWithTTL(binary.BigEndian.Uint64(k[32-8:]), v, 1, d)
// so just extract the last 8 bytes from it and turn them into a uint64
return shortUint64(key)
}
func shortUint64(idOrPubkey string) uint64 {
length := len(idOrPubkey)
if length < 8 {
return 0
}
b, err := hex.DecodeString(idOrPubkey[length-8:])
if err != nil {
return 0
}
return uint64(binary.BigEndian.Uint32(b))
} }

View File

@@ -1,22 +1,21 @@
package sdk package sdk
import ( import (
"encoding/hex"
"fmt"
"slices" "slices"
"github.com/nbd-wtf/go-nostr/sdk/kvstore" "fiatjaf.com/nostr"
"fiatjaf.com/nostr/sdk/kvstore"
) )
const eventRelayPrefix = byte('r') const eventRelayPrefix = byte('r')
// makeEventRelayKey creates a key for storing event relay information. // makeEventRelayKey creates a key for storing event relay information.
// It uses the first 8 bytes of the event ID to create a compact key. // It uses the first 8 bytes of the event ID to create a compact key.
func makeEventRelayKey(eventID []byte) []byte { func makeEventRelayKey(id nostr.ID) []byte {
// format: 'r' + first 8 bytes of event ID // format: 'r' + first 8 bytes of event ID
key := make([]byte, 9) key := make([]byte, 9)
key[0] = eventRelayPrefix key[0] = eventRelayPrefix
copy(key[1:], eventID[:8]) copy(key[1:], id[:8])
return key return key
} }
@@ -75,15 +74,9 @@ func decodeRelayList(data []byte) []string {
// trackEventRelay records that an event was seen on a particular relay. // 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. // 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) { func (sys *System) trackEventRelay(id nostr.ID, relay string, onlyIfItExists bool) {
// decode the event ID hex into bytes
idBytes, err := hex.DecodeString(eventID)
if err != nil || len(idBytes) < 8 {
return
}
// get the key for this event // get the key for this event
key := makeEventRelayKey(idBytes) key := makeEventRelayKey(id)
// update the relay list atomically // update the relay list atomically
sys.KVStore.Update(key, func(data []byte) ([]byte, error) { sys.KVStore.Update(key, func(data []byte) ([]byte, error) {
@@ -111,15 +104,9 @@ func (sys *System) trackEventRelay(eventID string, relay string, onlyIfItExists
// GetEventRelays returns all known relay URLs an event is known to be available on. // GetEventRelays returns all known relay URLs an event is known to be available on.
// It is based on information kept on KVStore. // It is based on information kept on KVStore.
func (sys *System) GetEventRelays(eventID string) ([]string, error) { func (sys *System) GetEventRelays(id nostr.ID) ([]string, error) {
// decode the event ID hex into bytes
idBytes, err := hex.DecodeString(eventID)
if err != nil || len(idBytes) < 8 {
return nil, fmt.Errorf("invalid event id")
}
// get the key for this event // get the key for this event
key := makeEventRelayKey(idBytes) key := makeEventRelayKey(id)
// get stored relay list // get stored relay list
data, err := sys.KVStore.Get(key) data, err := sys.KVStore.Get(key)

View File

@@ -8,7 +8,7 @@ import (
"sync" "sync"
"sync/atomic" "sync/atomic"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
) )
const ( const (
@@ -29,7 +29,7 @@ func makePubkeyStreamKey(prefix byte, pubkey string) []byte {
// each pubkey (stored in KVStore) onwards. // each pubkey (stored in KVStore) onwards.
func (sys *System) StreamLiveFeed( func (sys *System) StreamLiveFeed(
ctx context.Context, ctx context.Context,
pubkeys []string, pubkeys []nostr.PubKey,
kinds []int, kinds []int,
) (<-chan *nostr.Event, error) { ) (<-chan *nostr.Event, error) {
events := make(chan *nostr.Event) events := make(chan *nostr.Event)

View File

@@ -5,9 +5,9 @@ import (
"testing" "testing"
"time" "time"
"fiatjaf.com/nostr"
"github.com/fiatjaf/eventstore/slicestore" "github.com/fiatjaf/eventstore/slicestore"
"github.com/fiatjaf/khatru" "github.com/fiatjaf/khatru"
"github.com/nbd-wtf/go-nostr"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -56,9 +56,9 @@ func TestStreamLiveFeed(t *testing.T) {
// generate two random keypairs for testing // generate two random keypairs for testing
sk1 := nostr.GeneratePrivateKey() sk1 := nostr.GeneratePrivateKey()
pk1, _ := nostr.GetPublicKey(sk1) pk1 := nostr.GetPublicKey(sk1)
sk2 := nostr.GeneratePrivateKey() sk2 := nostr.GeneratePrivateKey()
pk2, _ := nostr.GetPublicKey(sk2) pk2 := nostr.GetPublicKey(sk2)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() defer cancel()
@@ -131,7 +131,7 @@ func TestStreamLiveFeed(t *testing.T) {
go sys.Pool.PublishMany(ctx, []string{"ws://localhost:48482", "ws://localhost:48483"}, evt2) go sys.Pool.PublishMany(ctx, []string{"ws://localhost:48482", "ws://localhost:48483"}, evt2)
// start streaming events for both pubkeys // start streaming events for both pubkeys
events, err := sys.StreamLiveFeed(ctx, []string{pk1, pk2}, []int{1}) events, err := sys.StreamLiveFeed(ctx, []nostr.PubKey{pk1, pk2}, []int{1})
if err != nil { if err != nil {
t.Fatalf("failed to start streaming: %v", err) t.Fatalf("failed to start streaming: %v", err)
} }

View File

@@ -1,14 +1,13 @@
package badgerh package badgerh
import ( import (
"encoding/hex"
"fmt" "fmt"
"math" "math"
"slices" "slices"
"fiatjaf.com/nostr"
"fiatjaf.com/nostr/sdk/hints"
"github.com/dgraph-io/badger/v4" "github.com/dgraph-io/badger/v4"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/sdk/hints"
) )
var _ hints.HintsDB = (*BadgerHints)(nil) var _ hints.HintsDB = (*BadgerHints)(nil)
@@ -30,7 +29,7 @@ func (bh *BadgerHints) Close() {
bh.db.Close() bh.db.Close()
} }
func (bh *BadgerHints) Save(pubkey string, relay string, hintkey hints.HintKey, ts nostr.Timestamp) { func (bh *BadgerHints) Save(pubkey nostr.PubKey, relay string, hintkey hints.HintKey, ts nostr.Timestamp) {
if now := nostr.Now(); ts > now { if now := nostr.Now(); ts > now {
ts = now ts = now
} }
@@ -64,7 +63,7 @@ func (bh *BadgerHints) Save(pubkey string, relay string, hintkey hints.HintKey,
} }
} }
func (bh *BadgerHints) TopN(pubkey string, n int) []string { func (bh *BadgerHints) TopN(pubkey nostr.PubKey, n int) []string {
type relayScore struct { type relayScore struct {
relay string relay string
score int64 score int64
@@ -73,7 +72,7 @@ func (bh *BadgerHints) TopN(pubkey string, n int) []string {
scores := make([]relayScore, 0, n) scores := make([]relayScore, 0, n)
err := bh.db.View(func(txn *badger.Txn) error { err := bh.db.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions opts := badger.DefaultIteratorOptions
opts.Prefix, _ = hex.DecodeString(pubkey) opts.Prefix = pubkey[:]
it := txn.NewIterator(opts) it := txn.NewIterator(opts)
defer it.Close() defer it.Close()
@@ -112,7 +111,7 @@ func (bh *BadgerHints) TopN(pubkey string, n int) []string {
return result return result
} }
func (bh *BadgerHints) GetDetailedScores(pubkey string, n int) []hints.RelayScores { func (bh *BadgerHints) GetDetailedScores(pubkey nostr.PubKey, n int) []hints.RelayScores {
type relayScore struct { type relayScore struct {
relay string relay string
tss timestamps tss timestamps
@@ -121,11 +120,10 @@ func (bh *BadgerHints) GetDetailedScores(pubkey string, n int) []hints.RelayScor
scores := make([]relayScore, 0, n) scores := make([]relayScore, 0, n)
err := bh.db.View(func(txn *badger.Txn) error { err := bh.db.View(func(txn *badger.Txn) error {
prefix, _ := hex.DecodeString(pubkey)
it := txn.NewIterator(badger.DefaultIteratorOptions) it := txn.NewIterator(badger.DefaultIteratorOptions)
defer it.Close() defer it.Close()
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() { for it.Seek(pubkey[:]); it.ValidForPrefix(pubkey[:]); it.Next() {
item := it.Item() item := it.Item()
k := item.Key() k := item.Key()
relay := string(k[32:]) relay := string(k[32:])
@@ -172,7 +170,7 @@ func (bh *BadgerHints) PrintScores() {
it := txn.NewIterator(badger.DefaultIteratorOptions) it := txn.NewIterator(badger.DefaultIteratorOptions)
defer it.Close() defer it.Close()
var lastPubkey string var lastPubkey nostr.PubKey
i := 0 i := 0
for it.Seek(nil); it.Valid(); it.Next() { for it.Seek(nil); it.Valid(); it.Next() {

View File

@@ -2,20 +2,19 @@ package badgerh
import ( import (
"encoding/binary" "encoding/binary"
"encoding/hex"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
) )
func encodeKey(pubhintkey, relay string) []byte { func encodeKey(pubhintkey nostr.PubKey, relay string) []byte {
k := make([]byte, 32+len(relay)) k := make([]byte, 32+len(relay))
hex.Decode(k[0:32], []byte(pubhintkey)) copy(k[0:32], pubhintkey[:])
copy(k[32:], relay) copy(k[32:], relay)
return k return k
} }
func parseKey(k []byte) (pubkey string, relay string) { func parseKey(k []byte) (pubkey nostr.PubKey, relay string) {
pubkey = hex.EncodeToString(k[0:32]) pubkey = [32]byte(k[0:32])
relay = string(k[32:]) relay = string(k[32:])
return return
} }

View File

@@ -1,6 +1,8 @@
package hints package hints
import "github.com/nbd-wtf/go-nostr" import (
"fiatjaf.com/nostr"
)
type RelayScores struct { type RelayScores struct {
Relay string Relay string
@@ -9,8 +11,8 @@ type RelayScores struct {
} }
type HintsDB interface { type HintsDB interface {
TopN(pubkey string, n int) []string TopN(pubkey nostr.PubKey, n int) []string
Save(pubkey string, relay string, key HintKey, score nostr.Timestamp) Save(pubkey nostr.PubKey, relay string, key HintKey, score nostr.Timestamp)
PrintScores() PrintScores()
GetDetailedScores(pubkey string, n int) []RelayScores GetDetailedScores(pubkey nostr.PubKey, n int) []RelayScores
} }

View File

@@ -1,6 +1,6 @@
package hints package hints
import "github.com/nbd-wtf/go-nostr" import "fiatjaf.com/nostrlib"
const END_OF_WORLD nostr.Timestamp = 2208999600 // 2040-01-01 const END_OF_WORLD nostr.Timestamp = 2208999600 // 2040-01-01

View File

@@ -6,8 +6,8 @@ import (
"slices" "slices"
"sync" "sync"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostrlib"
"github.com/nbd-wtf/go-nostr/sdk/hints" "fiatjaf.com/nostrlib/sdk/hints"
) )
var _ hints.HintsDB = (*HintDB)(nil) var _ hints.HintsDB = (*HintDB)(nil)

Some files were not shown because too many files have changed in this diff Show More