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)
[![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 Report Card](https://goreportcard.com/badge/github.com/nbd-wtf/go-nostr)](https://goreportcard.com/report/github.com/nbd-wtf/go-nostr)
[![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/fiatjaf.com/nostrlib.svg)](https://pkg.go.dev/fiatjaf.com/nostrlib)
[![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>
@@ -10,7 +10,7 @@ go-nostr
A set of useful things for [Nostr](https://github.com/nostr-protocol/nostr)-related software.
```bash
go get github.com/nbd-wtf/go-nostr
go get fiatjaf.com/nostrlib
```
### Generating a key
@@ -21,8 +21,8 @@ package main
import (
"fmt"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
"fiatjaf.com/nostrlib"
"fiatjaf.com/nostrlib/nip19"
)
func main() {

View File

@@ -13,7 +13,7 @@ import (
var defaultConnectionOptions = &ws.DialOptions{
CompressionMode: ws.CompressionContextTakeover,
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()
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.Greater(t, count, int64(0))

View File

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

View File

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

View File

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

View File

@@ -10,13 +10,13 @@ import (
// Event represents a Nostr event.
type Event struct {
ID string
PubKey string
ID ID
PubKey PubKey
CreatedAt Timestamp
Kind int
Kind uint16
Tags Tags
Content string
Sig string
Sig [64]byte
}
func (evt Event) String() string {
@@ -24,37 +24,14 @@ func (evt Event) String() string {
return string(j)
}
// GetID computes the event ID and returns it as a hex string.
func (evt *Event) GetID() string {
h := sha256.Sum256(evt.Serialize())
return hex.EncodeToString(h[:])
// GetID serializes and returns the event ID as a string.
func (evt *Event) GetID() ID {
return sha256.Sum256(evt.Serialize())
}
// CheckID checks if the implied ID matches the given ID more efficiently.
func (evt *Event) CheckID() bool {
if len(evt.ID) != 64 {
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
return evt.GetID() == evt.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 {
// the header portion is easy to serialize
// [0,"pubkey",created_at,kind,[
dst = append(dst, "[0,\""...)
dst = append(dst, evt.PubKey...)
dst = append(dst, "\","...)
dst = append(dst, `[0,"`...)
dst = hex.AppendEncode(dst, evt.PubKey[:])
dst = append(dst, `",`...)
dst = append(dst, strconv.FormatInt(int64(evt.CreatedAt), 10)...)
dst = append(dst, ',')
dst = append(dst, strconv.Itoa(evt.Kind)...)
dst = append(dst, ',')
dst = append(dst, `,`...)
dst = append(dst, strconv.FormatUint(uint64(evt.Kind), 10)...)
dst = append(dst, `,`...)
// tags
dst = append(dst, '[')

View File

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

View File

@@ -1,6 +1,7 @@
package nostr
import (
"encoding/hex"
"fmt"
"math/rand/v2"
"testing"
@@ -34,15 +35,18 @@ func TestEventParsingAndVerifying(t *testing.T) {
}
func TestEventSerialization(t *testing.T) {
sig := [64]byte{}
hex.Decode(sig[:], []byte("ed08d2dd5b0f7b6a3cdc74643d4adee3158ddede9cc848e8cd97630c097001acc2d052d2d3ec2b7ac4708b2314b797106d1b3c107322e61b5e5cc2116e099b79"))
events := []Event{
{
ID: "92570b321da503eac8014b23447301eb3d0bbdfbace0d11a4e4072e72bb7205d",
PubKey: "e9142f724955c5854de36324dab0434f97b15ec6b33464d56ebe491e3f559d1b",
ID: MustIDFromHex("92570b321da503eac8014b23447301eb3d0bbdfbace0d11a4e4072e72bb7205d"),
PubKey: MustPubKeyFromHex("e9142f724955c5854de36324dab0434f97b15ec6b33464d56ebe491e3f559d1b"),
Kind: KindEncryptedDirectMessage,
CreatedAt: Timestamp(1671028682),
Tags: Tags{Tag{"p", "f8340b2bde651576b75af61aa26c80e13c65029f00f7f64004eece679bf7059f"}},
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()
if err := event.Sign(privkey); err != nil {
t.Fatalf("event.Sign: %v", err)

View File

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

View File

@@ -6,12 +6,10 @@ import (
"github.com/mailru/easyjson"
)
type Filters []Filter
type Filter struct {
IDs []string
Kinds []int
Authors []string
IDs []ID
Kinds []uint16
Authors []PubKey
Tags TagMap
Since *Timestamp
Until *Timestamp
@@ -24,29 +22,6 @@ type Filter struct {
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 {
j, _ := easyjson.Marshal(ef)
return string(j)
@@ -99,11 +74,11 @@ func FilterEqual(a Filter, b Filter) bool {
return false
}
if !similar(a.IDs, b.IDs) {
if !similarID(a.IDs, b.IDs) {
return false
}
if !similar(a.Authors, b.Authors) {
if !similarPublicKey(a.Authors, b.Authors) {
return false
}
@@ -142,14 +117,26 @@ func FilterEqual(a Filter, b Filter) bool {
func (ef Filter) Clone() Filter {
clone := Filter{
IDs: slices.Clone(ef.IDs),
Authors: slices.Clone(ef.Authors),
Kinds: slices.Clone(ef.Kinds),
Limit: ef.Limit,
Search: ef.Search,
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 {
clone.Tags = make(TagMap, len(ef.Tags))
for k, v := range ef.Tags {

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
package nostr
import (
"encoding/hex"
"strconv"
"strings"
"sync"
@@ -49,6 +50,48 @@ func similar[E constraints.Ordered](as, bs []E) bool {
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.
// Also encloses result in quotation marks "".
func escapeString(dst []byte, s string) []byte {
@@ -140,11 +183,11 @@ func extractSubID(jsonStr string) string {
return jsonStr[start : start+end]
}
func extractEventID(jsonStr string) string {
func extractEventID(jsonStr string) ID {
// look for "id" pattern
start := strings.Index(jsonStr, `"id"`)
if start == -1 {
return ""
return [32]byte{}
}
// move to the next quote
@@ -152,14 +195,16 @@ func extractEventID(jsonStr string) string {
start += 4 + offset + 1
// 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
start := strings.Index(jsonStr, `"pubkey"`)
if start == -1 {
return ""
return PubKey{}
}
// move to the next quote
@@ -167,7 +212,9 @@ func extractEventPubKey(jsonStr string) string {
start += 8 + offset + 1
// 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 {

View File

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

View File

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

View File

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

View File

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

View File

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

41
keys.go
View File

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

272
kinds.go
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@ import (
"fmt"
"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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ package nip40
import (
"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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ import (
"context"
"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) {

View File

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

View File

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

View File

@@ -5,8 +5,8 @@ import (
"math/rand"
"github.com/mailru/easyjson"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip44"
"fiatjaf.com/nostrlib"
"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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,8 +11,8 @@ import (
"github.com/elnosh/gonuts/cashu/nuts/nut02"
"github.com/elnosh/gonuts/cashu/nuts/nut10"
"github.com/elnosh/gonuts/cashu/nuts/nut11"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip60/client"
"fiatjaf.com/nostrlib"
"fiatjaf.com/nostrlib/nip60/client"
)
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/nut03"
"github.com/elnosh/gonuts/cashu/nuts/nut10"
"github.com/nbd-wtf/go-nostr/nip60/client"
"fiatjaf.com/nostrlib/nip60/client"
)
type swapSettings struct {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

103
pool.go
View File

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

View File

@@ -36,7 +36,7 @@ type Relay struct {
challenge string // NIP-42 challenge, we only keep the last
noticeHandler func(string) // NIP-01 NOTICEs
customHandler func(string) // nonstandard unparseable messages
okCallbacks *xsync.MapOf[string, func(bool, string)]
okCallbacks *xsync.MapOf[ID, func(bool, string)]
writeQueue chan writeRequest
subscriptionChannelCloseQueue chan *Subscription
@@ -58,7 +58,7 @@ func NewRelay(ctx context.Context, url string, opts ...RelayOption) *Relay {
connectionContext: ctx,
connectionContextCancel: cancel,
Subscriptions: xsync.NewMapOf[int64, *Subscription](),
okCallbacks: xsync.NewMapOf[string, func(bool, string)](),
okCallbacks: xsync.NewMapOf[ID, func(bool, string)](),
writeQueue: make(chan writeRequest),
subscriptionChannelCloseQueue: make(chan *Subscription),
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})
}
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 cancel context.CancelFunc

View File

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

View File

@@ -74,7 +74,7 @@ func TestPublishBlocked(t *testing.T) {
assert.NoError(t, err)
// 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)
})
defer ws.Close()
@@ -175,12 +175,11 @@ var anyOriginHandshake = func(conf *websocket.Config, r *http.Request) error {
return nil
}
func makeKeyPair(t *testing.T) (priv, pub string) {
func makeKeyPair(t *testing.T) (priv, pub [32]byte) {
t.Helper()
privkey := GeneratePrivateKey()
pubkey, err := GetPublicKey(privkey)
assert.NoError(t, err)
pubkey := GetPublicKey(privkey)
return privkey, pubkey
}

View File

@@ -5,8 +5,8 @@ import (
"sync"
"time"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/sdk/dataloader"
"fiatjaf.com/nostr"
"fiatjaf.com/nostr/sdk/dataloader"
)
// this is similar to replaceable_loader and reuses logic from that.
@@ -21,16 +21,16 @@ const (
)
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_30002] = sys.createAddressableDataloader(30002)
sys.addressableLoaders[kind_30015] = sys.createAddressableDataloader(30015)
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(
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)
},
dataloader.Options{
@@ -42,11 +42,11 @@ func (sys *System) createAddressableDataloader(kind int) *dataloader.Loader[stri
func (sys *System) batchLoadAddressableEvents(
ctxs []context.Context,
kind int,
pubkeys []string,
) map[string]dataloader.Result[[]*nostr.Event] {
kind uint16,
pubkeys []nostr.PubKey,
) map[nostr.PubKey]dataloader.Result[[]*nostr.Event] {
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))
relayFilterIndex := make(map[string]int, max(3, batchSize*2))
@@ -62,7 +62,7 @@ func (sys *System) batchLoadAddressableEvents(
defer cancel()
// 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
relays := sys.determineRelaysToQuery(ctx, pubkey, kind)
@@ -77,8 +77,8 @@ func (sys *System) batchLoadAddressableEvents(
dfilter = nostr.DirectedFilter{
Relay: relay,
Filter: nostr.Filter{
Kinds: []int{kind},
Authors: make([]string, 0, batchSize-i /* this and all pubkeys after this can be added */),
Kinds: []uint16{kind},
Authors: make([]nostr.PubKey, 0, batchSize-i /* this and all pubkeys after this can be added */),
},
}
idx = len(relayFilter)

View File

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

View File

@@ -2,47 +2,33 @@ package cache_memory
import (
"encoding/binary"
"encoding/hex"
"time"
"github.com/dgraph-io/ristretto"
)
type RistrettoCache[V any] struct {
Cache *ristretto.Cache[string, V]
Cache *ristretto.Cache[uint64, V]
}
func New32[V any](max int64) *RistrettoCache[V] {
cache, _ := ristretto.NewCache(&ristretto.Config[string, V]{
func New[V any](max int64) *RistrettoCache[V] {
cache, _ := ristretto.NewCache(&ristretto.Config[uint64, V]{
NumCounters: max * 10,
MaxCost: max,
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}
}
func (s RistrettoCache[V]) Get(k string) (v V, ok bool) { return s.Cache.Get(k) }
func (s RistrettoCache[V]) Delete(k string) { s.Cache.Del(k) }
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 {
return s.Cache.SetWithTTL(k, v, 1, d)
func (s RistrettoCache[V]) Get(k [32]byte) (v V, ok bool) {
return s.Cache.Get(binary.BigEndian.Uint64(k[32-8:]))
}
func (s RistrettoCache[V]) Delete(k [32]byte) { s.Cache.Del(binary.BigEndian.Uint64(k[32-8:])) }
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 {
// we get an event id or pubkey as hex,
// 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))
func (s RistrettoCache[V]) SetWithTTL(k [32]byte, v V, d time.Duration) bool {
return s.Cache.SetWithTTL(binary.BigEndian.Uint64(k[32-8:]), v, 1, d)
}

View File

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

View File

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

View File

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

View File

@@ -1,14 +1,13 @@
package badgerh
import (
"encoding/hex"
"fmt"
"math"
"slices"
"fiatjaf.com/nostr"
"fiatjaf.com/nostr/sdk/hints"
"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)
@@ -30,7 +29,7 @@ func (bh *BadgerHints) 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 {
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 {
relay string
score int64
@@ -73,7 +72,7 @@ func (bh *BadgerHints) TopN(pubkey string, n int) []string {
scores := make([]relayScore, 0, n)
err := bh.db.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.Prefix, _ = hex.DecodeString(pubkey)
opts.Prefix = pubkey[:]
it := txn.NewIterator(opts)
defer it.Close()
@@ -112,7 +111,7 @@ func (bh *BadgerHints) TopN(pubkey string, n int) []string {
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 {
relay string
tss timestamps
@@ -121,11 +120,10 @@ func (bh *BadgerHints) GetDetailedScores(pubkey string, n int) []hints.RelayScor
scores := make([]relayScore, 0, n)
err := bh.db.View(func(txn *badger.Txn) error {
prefix, _ := hex.DecodeString(pubkey)
it := txn.NewIterator(badger.DefaultIteratorOptions)
defer it.Close()
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
for it.Seek(pubkey[:]); it.ValidForPrefix(pubkey[:]); it.Next() {
item := it.Item()
k := item.Key()
relay := string(k[32:])
@@ -172,7 +170,7 @@ func (bh *BadgerHints) PrintScores() {
it := txn.NewIterator(badger.DefaultIteratorOptions)
defer it.Close()
var lastPubkey string
var lastPubkey nostr.PubKey
i := 0
for it.Seek(nil); it.Valid(); it.Next() {

View File

@@ -2,20 +2,19 @@ package badgerh
import (
"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))
hex.Decode(k[0:32], []byte(pubhintkey))
copy(k[0:32], pubhintkey[:])
copy(k[32:], relay)
return k
}
func parseKey(k []byte) (pubkey string, relay string) {
pubkey = hex.EncodeToString(k[0:32])
func parseKey(k []byte) (pubkey nostr.PubKey, relay string) {
pubkey = [32]byte(k[0:32])
relay = string(k[32:])
return
}

View File

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

View File

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

View File

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

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