From d733a31898a94de0433e1f7ec1e967702587a033 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Sun, 20 Apr 2025 18:11:21 -0300 Subject: [PATCH] convert to using nostrlib. --- blossom.go | 11 ++- bunker.go | 40 ++++++----- count.go | 21 +++--- curl.go | 2 +- decode.go | 8 +-- encode.go | 69 ++++++------------- encrypt_decrypt.go | 17 ++--- event.go | 29 ++++---- fetch.go | 22 +++--- flags.go | 157 ++++++++++++++++++++++++++++++++++++++++--- fs.go | 14 ++-- go.mod | 8 ++- go.sum | 14 ++-- helpers.go | 44 ++++++------ helpers_key.go | 63 ++++++++--------- key.go | 33 ++++----- main.go | 20 +++--- mcp.go | 72 ++++++++++++-------- musig2.go | 36 ++++------ nostrfs/asyncfile.go | 2 +- nostrfs/entitydir.go | 28 +++++--- nostrfs/eventdir.go | 30 +++++---- nostrfs/helpers.go | 11 ++- nostrfs/npubdir.go | 37 +++++----- nostrfs/root.go | 17 +++-- nostrfs/viewdir.go | 41 ++++++----- outbox.go | 10 +-- relay.go | 6 +- req.go | 45 +++++++------ serve.go | 32 +++++---- verify.go | 6 +- wallet.go | 41 ++++++----- 32 files changed, 568 insertions(+), 418 deletions(-) diff --git a/blossom.go b/blossom.go index f4b039f..eb4b168 100644 --- a/blossom.go +++ b/blossom.go @@ -5,8 +5,9 @@ import ( "fmt" "os" - "github.com/nbd-wtf/go-nostr/keyer" - "github.com/nbd-wtf/go-nostr/nipb0/blossom" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/keyer" + "fiatjaf.com/nostr/nipb0/blossom" "github.com/urfave/cli/v3" ) @@ -35,7 +36,11 @@ var blossomCmd = &cli.Command{ var client *blossom.Client pubkey := c.Args().First() if pubkey != "" { - client = blossom.NewClient(client.GetMediaServer(), keyer.NewReadOnlySigner(pubkey)) + if pk, err := nostr.PubKeyFromHex(pubkey); err != nil { + return fmt.Errorf("invalid public key '%s': %w", pubkey, err) + } else { + client = blossom.NewClient(client.GetMediaServer(), keyer.NewReadOnlySigner(pk)) + } } else { var err error client, err = getBlossomClient(ctx, c) diff --git a/bunker.go b/bunker.go index 12e8e32..2f412dc 100644 --- a/bunker.go +++ b/bunker.go @@ -10,10 +10,10 @@ import ( "sync" "time" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/nip19" + "fiatjaf.com/nostr/nip46" "github.com/fatih/color" - "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/nip19" - "github.com/nbd-wtf/go-nostr/nip46" "github.com/urfave/cli/v3" ) @@ -38,7 +38,7 @@ var bunker = &cli.Command{ Aliases: []string{"s"}, Usage: "secrets for which we will always respond", }, - &cli.StringSliceFlag{ + &PubKeySliceFlag{ Name: "authorized-keys", Aliases: []string{"k"}, Usage: "pubkeys for which we will always respond", @@ -49,7 +49,7 @@ var bunker = &cli.Command{ qs := url.Values{} relayURLs := make([]string, 0, c.Args().Len()) if relayUrls := c.Args().Slice(); len(relayUrls) > 0 { - relays := connectToAllRelays(ctx, c, relayUrls, nil) + relays := connectToAllRelays(ctx, c, relayUrls, nil, nostr.PoolOptions{}) if len(relays) == 0 { log("failed to connect to any of the given relays.\n") os.Exit(3) @@ -70,7 +70,7 @@ var bunker = &cli.Command{ } // other arguments - authorizedKeys := c.StringSlice("authorized-keys") + authorizedKeys := getPubKeySlice(c, "authorized-keys") authorizedSecrets := c.StringSlice("authorized-secrets") // this will be used to auto-authorize the next person who connects who isn't pre-authorized @@ -78,11 +78,8 @@ var bunker = &cli.Command{ newSecret := randString(12) // static information - pubkey, err := nostr.GetPublicKey(sec) - if err != nil { - return err - } - npub, _ := nip19.EncodePublicKey(pubkey) + pubkey := sec.Public() + npub := nip19.EncodeNpub(pubkey) // this function will be called every now and then printBunkerInfo := func() { @@ -91,7 +88,10 @@ var bunker = &cli.Command{ authorizedKeysStr := "" if len(authorizedKeys) != 0 { - authorizedKeysStr = "\n authorized keys:\n - " + colors.italic(strings.Join(authorizedKeys, "\n - ")) + authorizedKeysStr = "\n authorized keys:" + for _, pubkey := range authorizedKeys { + authorizedKeysStr += "\n - " + colors.italic(pubkey.Hex()) + } } authorizedSecretsStr := "" @@ -101,7 +101,7 @@ var bunker = &cli.Command{ preauthorizedFlags := "" for _, k := range authorizedKeys { - preauthorizedFlags += " -k " + k + preauthorizedFlags += " -k " + k.Hex() } for _, s := range authorizedSecrets { preauthorizedFlags += " -s " + s @@ -142,10 +142,12 @@ var bunker = &cli.Command{ // subscribe to relays now := nostr.Now() events := sys.Pool.SubscribeMany(ctx, relayURLs, nostr.Filter{ - Kinds: []int{nostr.KindNostrConnect}, - Tags: nostr.TagMap{"p": []string{pubkey}}, + Kinds: []nostr.Kind{nostr.KindNostrConnect}, + Tags: nostr.TagMap{"p": []string{pubkey.Hex()}}, Since: &now, LimitZero: true, + }, nostr.SubscriptionOptions{ + Label: "nak-bunker", }) signer := nip46.NewStaticKeySigner(sec) @@ -158,7 +160,7 @@ var bunker = &cli.Command{ cancelPreviousBunkerInfoPrint = cancel // asking user for authorization - signer.AuthorizeRequest = func(harmless bool, from string, secret string) bool { + signer.AuthorizeRequest = func(harmless bool, from nostr.PubKey, secret string) bool { if secret == newSecret { // store this key authorizedKeys = append(authorizedKeys, from) @@ -236,11 +238,13 @@ var bunker = &cli.Command{ } uri, err := url.Parse(c.Args().First()) - if err != nil || uri.Scheme != "nostrconnect" || !nostr.IsValidPublicKey(uri.Host) { + if err != nil || uri.Scheme != "nostrconnect" { return fmt.Errorf("invalid uri") } - return nil + // TODO + + return fmt.Errorf("this is not implemented yet") }, }, }, diff --git a/count.go b/count.go index c01b65e..905d0f0 100644 --- a/count.go +++ b/count.go @@ -6,9 +6,9 @@ import ( "os" "strings" - "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/nip45" - "github.com/nbd-wtf/go-nostr/nip45/hyperloglog" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/nip45" + "fiatjaf.com/nostr/nip45/hyperloglog" "github.com/urfave/cli/v3" ) @@ -18,7 +18,7 @@ var count = &cli.Command{ Description: `outputs a nip45 request (the flags are mostly the same as 'nak req').`, DisableSliceFlagSeparator: true, Flags: []cli.Flag{ - &cli.StringSliceFlag{ + &PubKeySliceFlag{ Name: "author", Aliases: []string{"a"}, Usage: "only accept events from these authors (pubkey as hex)", @@ -70,7 +70,7 @@ var count = &cli.Command{ biggerUrlSize := 0 relayUrls := c.Args().Slice() if len(relayUrls) > 0 { - relays := connectToAllRelays(ctx, c, relayUrls, nil) + relays := connectToAllRelays(ctx, c, relayUrls, nil, nostr.PoolOptions{}) if len(relays) == 0 { log("failed to connect to any of the given relays.\n") os.Exit(3) @@ -92,16 +92,13 @@ var count = &cli.Command{ filter := nostr.Filter{} - if authors := c.StringSlice("author"); len(authors) > 0 { + if authors := getPubKeySlice(c, "author"); len(authors) > 0 { filter.Authors = authors } - if ids := c.StringSlice("id"); len(ids) > 0 { - filter.IDs = ids - } if kinds64 := c.IntSlice("kind"); len(kinds64) > 0 { - kinds := make([]int, len(kinds64)) + kinds := make([]nostr.Kind, len(kinds64)) for i, v := range kinds64 { - kinds[i] = int(v) + kinds[i] = nostr.Kind(v) } filter.Kinds = kinds } @@ -151,7 +148,7 @@ var count = &cli.Command{ } for _, relayUrl := range relayUrls { relay, _ := sys.Pool.EnsureRelay(relayUrl) - count, hllRegisters, err := relay.Count(ctx, nostr.Filters{filter}) + count, hllRegisters, err := relay.Count(ctx, filter, nostr.SubscriptionOptions{}) fmt.Fprintf(os.Stderr, "%s%s: ", strings.Repeat(" ", biggerUrlSize-len(relayUrl)), relayUrl) if err != nil { diff --git a/curl.go b/curl.go index 973746d..d3d6e83 100644 --- a/curl.go +++ b/curl.go @@ -8,7 +8,7 @@ import ( "os/exec" "strings" - "github.com/nbd-wtf/go-nostr" + "fiatjaf.com/nostr" "github.com/urfave/cli/v3" "golang.org/x/exp/slices" ) diff --git a/decode.go b/decode.go index 4ad2b13..5bda33b 100644 --- a/decode.go +++ b/decode.go @@ -5,10 +5,10 @@ import ( "encoding/hex" "strings" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/nip19" + "fiatjaf.com/nostr/sdk" "github.com/urfave/cli/v3" - "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/nip19" - "github.com/nbd-wtf/go-nostr/sdk" ) var decode = &cli.Command{ @@ -73,7 +73,7 @@ var decode = &cli.Command{ } } else if prefix, value, err := nip19.Decode(input); err == nil && prefix == "nsec" { decodeResult.PrivateKey.PrivateKey = value.(string) - decodeResult.PrivateKey.PublicKey, _ = nostr.GetPublicKey(value.(string)) + decodeResult.PrivateKey.PublicKey = nostr.GetPublicKey(value.(nostr.SecretKey)) } else { ctx = lineProcessingError(ctx, "couldn't decode input '%s': %s", input, err) continue diff --git a/encode.go b/encode.go index c8dd419..c5f9db9 100644 --- a/encode.go +++ b/encode.go @@ -4,8 +4,8 @@ import ( "context" "fmt" - "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/nip19" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/nip19" "github.com/urfave/cli/v3" ) @@ -33,16 +33,13 @@ var encode = &cli.Command{ DisableSliceFlagSeparator: true, Action: func(ctx context.Context, c *cli.Command) error { for target := range getStdinLinesOrArguments(c.Args()) { - if ok := nostr.IsValidPublicKey(target); !ok { - ctx = lineProcessingError(ctx, "invalid public key: %s", target) + pk, err := nostr.PubKeyFromHexCheap(target) + if err != nil { + ctx = lineProcessingError(ctx, "invalid public key '%s': %w", target, err) continue } - if npub, err := nip19.EncodePublicKey(target); err == nil { - stdout(npub) - } else { - return err - } + stdout(nip19.EncodeNpub(pk)) } exitIfLineProcessingError(ctx) @@ -55,16 +52,13 @@ var encode = &cli.Command{ DisableSliceFlagSeparator: true, Action: func(ctx context.Context, c *cli.Command) error { for target := range getStdinLinesOrArguments(c.Args()) { - if ok := nostr.IsValid32ByteHex(target); !ok { - ctx = lineProcessingError(ctx, "invalid private key: %s", target) + sk, err := nostr.SecretKeyFromHex(target) + if err != nil { + ctx = lineProcessingError(ctx, "invalid private key '%s': %w", target, err) continue } - if npub, err := nip19.EncodePrivateKey(target); err == nil { - stdout(npub) - } else { - return err - } + stdout(nip19.EncodeNsec(sk)) } exitIfLineProcessingError(ctx) @@ -84,8 +78,9 @@ var encode = &cli.Command{ DisableSliceFlagSeparator: true, Action: func(ctx context.Context, c *cli.Command) error { for target := range getStdinLinesOrArguments(c.Args()) { - if ok := nostr.IsValid32ByteHex(target); !ok { - ctx = lineProcessingError(ctx, "invalid public key: %s", target) + pk, err := nostr.PubKeyFromHexCheap(target) + if err != nil { + ctx = lineProcessingError(ctx, "invalid public key '%s': %w", target, err) continue } @@ -94,11 +89,7 @@ var encode = &cli.Command{ return err } - if npub, err := nip19.EncodeProfile(target, relays); err == nil { - stdout(npub) - } else { - return err - } + stdout(nip19.EncodeNprofile(pk, relays)) } exitIfLineProcessingError(ctx) @@ -114,7 +105,7 @@ var encode = &cli.Command{ Aliases: []string{"r"}, Usage: "attach relay hints to nevent code", }, - &cli.StringFlag{ + &PubKeyFlag{ Name: "author", Aliases: []string{"a"}, Usage: "attach an author pubkey as a hint to the nevent code", @@ -123,28 +114,19 @@ var encode = &cli.Command{ DisableSliceFlagSeparator: true, Action: func(ctx context.Context, c *cli.Command) error { for target := range getStdinLinesOrArguments(c.Args()) { - if ok := nostr.IsValid32ByteHex(target); !ok { + id, err := nostr.IDFromHex(target) + if err != nil { ctx = lineProcessingError(ctx, "invalid event id: %s", target) continue } - author := c.String("author") - if author != "" { - if ok := nostr.IsValidPublicKey(author); !ok { - return fmt.Errorf("invalid 'author' public key") - } - } - + author := getPubKey(c, "author") relays := c.StringSlice("relay") if err := normalizeAndValidateRelayURLs(relays); err != nil { return err } - if npub, err := nip19.EncodeEvent(target, relays, author); err == nil { - stdout(npub) - } else { - return err - } + stdout(nip19.EncodeNevent(id, relays, author)) } exitIfLineProcessingError(ctx) @@ -161,7 +143,7 @@ var encode = &cli.Command{ Usage: "the \"d\" tag identifier of this replaceable event -- can also be read from stdin", Required: true, }, - &cli.StringFlag{ + &PubKeyFlag{ Name: "pubkey", Usage: "pubkey of the naddr author", Aliases: []string{"author", "a", "p"}, @@ -182,10 +164,7 @@ var encode = &cli.Command{ DisableSliceFlagSeparator: true, Action: func(ctx context.Context, c *cli.Command) error { for d := range getStdinLinesOrBlank() { - pubkey := c.String("pubkey") - if ok := nostr.IsValidPublicKey(pubkey); !ok { - return fmt.Errorf("invalid 'pubkey'") - } + pubkey := getPubKey(c, "pubkey") kind := c.Int("kind") if kind < 30000 || kind >= 40000 { @@ -205,11 +184,7 @@ var encode = &cli.Command{ return err } - if npub, err := nip19.EncodeEntity(pubkey, int(kind), d, relays); err == nil { - stdout(npub) - } else { - return err - } + stdout(nip19.EncodeNaddr(pubkey, nostr.Kind(kind), d, relays)) } exitIfLineProcessingError(ctx) diff --git a/encrypt_decrypt.go b/encrypt_decrypt.go index 930948a..457546f 100644 --- a/encrypt_decrypt.go +++ b/encrypt_decrypt.go @@ -4,9 +4,8 @@ import ( "context" "fmt" + "fiatjaf.com/nostr/nip04" "github.com/urfave/cli/v3" - "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/nip04" ) var encrypt = &cli.Command{ @@ -16,7 +15,7 @@ var encrypt = &cli.Command{ DisableSliceFlagSeparator: true, Flags: append( defaultKeyFlags, - &cli.StringFlag{ + &PubKeyFlag{ Name: "recipient-pubkey", Aliases: []string{"p", "tgt", "target", "pubkey"}, Required: true, @@ -27,10 +26,7 @@ var encrypt = &cli.Command{ }, ), Action: func(ctx context.Context, c *cli.Command) error { - target := c.String("recipient-pubkey") - if !nostr.IsValidPublicKey(target) { - return fmt.Errorf("target %s is not a valid public key", target) - } + target := getPubKey(c, "recipient-pubkey") plaintext := c.Args().First() @@ -81,7 +77,7 @@ var decrypt = &cli.Command{ DisableSliceFlagSeparator: true, Flags: append( defaultKeyFlags, - &cli.StringFlag{ + &PubKeyFlag{ Name: "sender-pubkey", Aliases: []string{"p", "src", "source", "pubkey"}, Required: true, @@ -92,10 +88,7 @@ var decrypt = &cli.Command{ }, ), Action: func(ctx context.Context, c *cli.Command) error { - source := c.String("sender-pubkey") - if !nostr.IsValidPublicKey(source) { - return fmt.Errorf("source %s is not a valid public key", source) - } + source := getPubKey(c, "sender-pubkey") ciphertext := c.Args().First() diff --git a/event.go b/event.go index f4b86bb..905e313 100644 --- a/event.go +++ b/event.go @@ -8,11 +8,11 @@ import ( "strings" "time" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/nip13" + "fiatjaf.com/nostr/nip19" "github.com/fatih/color" "github.com/mailru/easyjson" - "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/nip13" - "github.com/nbd-wtf/go-nostr/nip19" "github.com/urfave/cli/v3" ) @@ -141,9 +141,11 @@ example: if relayUrls := c.Args().Slice(); len(relayUrls) > 0 { relays = connectToAllRelays(ctx, c, relayUrls, nil, - nostr.WithAuthHandler(func(ctx context.Context, authEvent nostr.RelayEvent) error { - return authSigner(ctx, c, func(s string, args ...any) {}, authEvent) - }), + nostr.PoolOptions{ + AuthHandler: func(ctx context.Context, authEvent *nostr.Event) error { + return authSigner(ctx, c, func(s string, args ...any) {}, authEvent) + }, + }, ) if len(relays) == 0 { log("failed to connect to any of the given relays.\n") @@ -180,7 +182,7 @@ example: } if kind := c.Uint("kind"); slices.Contains(c.FlagNames(), "kind") { - evt.Kind = int(kind) + evt.Kind = nostr.Kind(kind) mustRehashAndResign = true } else if !kindWasSupplied { evt.Kind = 1 @@ -274,7 +276,7 @@ example: mustRehashAndResign = true } - if evt.Sig == "" || mustRehashAndResign { + if evt.Sig == [64]byte{} || mustRehashAndResign { if numSigners := c.Uint("musig"); numSigners > 1 { // must do musig pubkeys := c.StringSlice("musig-pubkey") @@ -364,7 +366,7 @@ example: low := unwrapAll(res.Error) // hack for some messages such as from relay.westernbtc.com - msg := strings.ReplaceAll(low.Error(), evt.PubKey, "author") + msg := strings.ReplaceAll(low.Error(), evt.PubKey.Hex(), "author") // do not allow the message to overflow the term window msg = clampMessage(msg, 20+len(res.RelayURL)) @@ -393,11 +395,9 @@ example: if strings.HasPrefix(err.Error(), "msg: auth-required:") && kr != nil && doAuth { // if the relay is requesting auth and we can auth, let's do it pk, _ := kr.GetPublicKey(ctx) - npub, _ := nip19.EncodePublicKey(pk) + npub := nip19.EncodeNpub(pk) log("authenticating as %s... ", color.YellowString("%s…%s", npub[0:7], npub[58:])) - if err := relay.Auth(ctx, func(authEvent *nostr.Event) error { - return kr.SignEvent(ctx, authEvent) - }); err == nil { + if err := relay.Auth(ctx, kr.SignEvent); err == nil { // try to publish again, but this time don't try to auth again doAuth = false goto publish @@ -410,8 +410,7 @@ example: } if len(successRelays) > 0 && c.Bool("nevent") { - nevent, _ := nip19.EncodeEvent(evt.ID, successRelays, evt.PubKey) - log(nevent + "\n") + log(nip19.EncodeNevent(evt.ID, successRelays, evt.PubKey) + "\n") } } diff --git a/fetch.go b/fetch.go index a2d141f..0bcef74 100644 --- a/fetch.go +++ b/fetch.go @@ -4,11 +4,11 @@ import ( "context" "fmt" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/nip05" + "fiatjaf.com/nostr/nip19" + "fiatjaf.com/nostr/sdk/hints" "github.com/urfave/cli/v3" - "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/sdk/hints" ) var fetch = &cli.Command{ @@ -36,7 +36,7 @@ var fetch = &cli.Command{ for code := range getStdinLinesOrArguments(c.Args()) { filter := nostr.Filter{} - var authorHint string + var authorHint nostr.PubKey relays := c.StringSlice("relay") if nip05.IsValidIdentifier(code) { @@ -63,15 +63,15 @@ var fetch = &cli.Command{ case "nevent": v := value.(nostr.EventPointer) filter.IDs = append(filter.IDs, v.ID) - if v.Author != "" { + if v.Author != nostr.ZeroPK { authorHint = v.Author } relays = append(relays, v.Relays...) case "note": - filter.IDs = append(filter.IDs, value.(string)) + filter.IDs = append(filter.IDs, value.([32]byte)) case "naddr": v := value.(nostr.EntityPointer) - filter.Kinds = []int{v.Kind} + filter.Kinds = []nostr.Kind{v.Kind} filter.Tags = nostr.TagMap{"d": []string{v.Identifier}} filter.Authors = append(filter.Authors, v.PublicKey) authorHint = v.PublicKey @@ -82,7 +82,7 @@ var fetch = &cli.Command{ authorHint = v.PublicKey relays = append(relays, v.Relays...) case "npub": - v := value.(string) + v := value.(nostr.PubKey) filter.Authors = append(filter.Authors, v) authorHint = v default: @@ -90,7 +90,7 @@ var fetch = &cli.Command{ } } - if authorHint != "" { + if authorHint != nostr.ZeroPK { for _, url := range relays { sys.Hints.Save(authorHint, nostr.NormalizeURL(url), hints.LastInHint, nostr.Now()) } @@ -113,7 +113,7 @@ var fetch = &cli.Command{ continue } - for ie := range sys.Pool.FetchMany(ctx, relays, filter) { + for ie := range sys.Pool.FetchMany(ctx, relays, filter, nostr.SubscriptionOptions{}) { stdout(ie.Event) } } diff --git a/flags.go b/flags.go index e9ddbcc..c3c18f4 100644 --- a/flags.go +++ b/flags.go @@ -6,14 +6,18 @@ import ( "strconv" "time" - "github.com/urfave/cli/v3" + "fiatjaf.com/nostr" "github.com/markusmobius/go-dateparser" - "github.com/nbd-wtf/go-nostr" + "github.com/urfave/cli/v3" ) +// +// +// + type NaturalTimeFlag = cli.FlagBase[nostr.Timestamp, struct{}, naturalTimeValue] -// wrap to satisfy golang's flag interface. +// wrap to satisfy flag interface. type naturalTimeValue struct { timestamp *nostr.Timestamp hasBeenSet bool @@ -39,11 +43,6 @@ func (t naturalTimeValue) ToString(b nostr.Timestamp) string { return fmt.Sprintf("%v", ts) } -// Timestamp constructor(for internal testing only) -func newTimestamp(timestamp nostr.Timestamp) *naturalTimeValue { - return &naturalTimeValue{timestamp: ×tamp} -} - // Below functions are to satisfy the flag.Value interface // Parses the string value to timestamp @@ -93,3 +92,145 @@ func (t *naturalTimeValue) Get() any { func getNaturalDate(cmd *cli.Command, name string) nostr.Timestamp { return cmd.Value(name).(nostr.Timestamp) } + +// +// +// + +type ( + PubKeyFlag = cli.FlagBase[nostr.PubKey, struct{}, pubkeyValue] +) + +// wrap to satisfy flag interface. +type pubkeyValue struct { + pubkey nostr.PubKey + hasBeenSet bool +} + +var _ cli.ValueCreator[nostr.PubKey, struct{}] = pubkeyValue{} + +// Below functions are to satisfy the ValueCreator interface + +func (t pubkeyValue) Create(val nostr.PubKey, p *nostr.PubKey, c struct{}) cli.Value { + *p = val + return &pubkeyValue{ + pubkey: val, + } +} + +func (t pubkeyValue) ToString(b nostr.PubKey) string { + return t.pubkey.String() +} + +// Below functions are to satisfy the flag.Value interface + +// Parses the string value to timestamp +func (t *pubkeyValue) Set(value string) error { + pk, err := nostr.PubKeyFromHex(value) + t.pubkey = pk + t.hasBeenSet = true + return err +} + +// String returns a readable representation of this value (for usage defaults) +func (t *pubkeyValue) String() string { + return fmt.Sprintf("%#v", t.pubkey) +} + +// Value returns the pubkey value stored in the flag +func (t *pubkeyValue) Value() nostr.PubKey { + return t.pubkey +} + +// Get returns the flag structure +func (t *pubkeyValue) Get() any { + return t.pubkey +} + +func getPubKey(cmd *cli.Command, name string) nostr.PubKey { + return cmd.Value(name).(nostr.PubKey) +} + +// +// +// + +type ( + pubkeySlice = cli.SliceBase[nostr.PubKey, struct{}, pubkeyValue] + PubKeySliceFlag = cli.FlagBase[[]nostr.PubKey, struct{}, pubkeySlice] +) + +func getPubKeySlice(cmd *cli.Command, name string) []nostr.PubKey { + return cmd.Value(name).([]nostr.PubKey) +} + +// +// +// + +type ( + IDFlag = cli.FlagBase[nostr.ID, struct{}, idValue] +) + +// wrap to satisfy flag interface. +type idValue struct { + id nostr.ID + hasBeenSet bool +} + +var _ cli.ValueCreator[nostr.ID, struct{}] = idValue{} + +// Below functions are to satisfy the ValueCreator interface + +func (t idValue) Create(val nostr.ID, p *nostr.ID, c struct{}) cli.Value { + *p = val + return &idValue{ + id: val, + } +} + +func (t idValue) ToString(b nostr.ID) string { + return t.id.String() +} + +// Below functions are to satisfy the flag.Value interface + +// Parses the string value to timestamp +func (t *idValue) Set(value string) error { + pk, err := nostr.IDFromHex(value) + t.id = pk + t.hasBeenSet = true + return err +} + +// String returns a readable representation of this value (for usage defaults) +func (t *idValue) String() string { + return fmt.Sprintf("%#v", t.id) +} + +// Value returns the id value stored in the flag +func (t *idValue) Value() nostr.ID { + return t.id +} + +// Get returns the flag structure +func (t *idValue) Get() any { + return t.id +} + +func getID(cmd *cli.Command, name string) nostr.ID { + return cmd.Value(name).(nostr.ID) +} + +// +// +// + +type ( + idSlice = cli.SliceBase[nostr.ID, struct{}, idValue] + IDSliceFlag = cli.FlagBase[[]nostr.ID, struct{}, idSlice] +) + +func getIDSlice(cmd *cli.Command, name string) []nostr.ID { + return cmd.Value(name).([]nostr.ID) +} diff --git a/fs.go b/fs.go index 4d4b886..f55ebbe 100644 --- a/fs.go +++ b/fs.go @@ -10,12 +10,12 @@ import ( "syscall" "time" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/keyer" "github.com/fatih/color" "github.com/fiatjaf/nak/nostrfs" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" - "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/keyer" "github.com/urfave/cli/v3" ) @@ -25,15 +25,9 @@ var fsCmd = &cli.Command{ Description: `(experimental)`, ArgsUsage: "", Flags: append(defaultKeyFlags, - &cli.StringFlag{ + &PubKeyFlag{ Name: "pubkey", Usage: "public key from where to to prepopulate directories", - Validator: func(pk string) error { - if nostr.IsValidPublicKey(pk) { - return nil - } - return fmt.Errorf("invalid public key '%s'", pk) - }, }, &cli.DurationFlag{ Name: "auto-publish-notes", @@ -58,7 +52,7 @@ var fsCmd = &cli.Command{ if signer, _, err := gatherKeyerFromArguments(ctx, c); err == nil { kr = signer } else { - kr = keyer.NewReadOnlyUser(c.String("pubkey")) + kr = keyer.NewReadOnlyUser(getPubKey(c, "pubkey")) } apnt := c.Duration("auto-publish-notes") diff --git a/go.mod b/go.mod index 95c9d4d..bbf639b 100644 --- a/go.mod +++ b/go.mod @@ -4,26 +4,25 @@ go 1.24.1 require ( fiatjaf.com/lib v0.3.1 + fiatjaf.com/nostr v0.0.1 github.com/bep/debounce v1.2.1 github.com/btcsuite/btcd/btcec/v2 v2.3.4 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 github.com/fatih/color v1.16.0 - github.com/fiatjaf/eventstore v0.16.2 - github.com/fiatjaf/khatru v0.17.4 github.com/hanwen/go-fuse/v2 v2.7.2 github.com/json-iterator/go v1.1.12 github.com/liamg/magic v0.0.1 github.com/mailru/easyjson v0.9.0 github.com/mark3labs/mcp-go v0.8.3 github.com/markusmobius/go-dateparser v1.2.3 - github.com/nbd-wtf/go-nostr v0.51.8 github.com/urfave/cli/v3 v3.0.0-beta1 golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 golang.org/x/term v0.30.0 ) require ( + github.com/FastFilter/xorfilter v0.2.1 // indirect github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 // indirect github.com/andybalholm/brotli v1.1.1 // indirect github.com/btcsuite/btcd v0.24.2 // indirect @@ -72,6 +71,9 @@ require ( golang.org/x/arch v0.15.0 // indirect golang.org/x/crypto v0.36.0 // indirect golang.org/x/net v0.37.0 // indirect + golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect ) + +replace fiatjaf.com/nostr => ../nostrlib diff --git a/go.sum b/go.sum index 7468d1a..c319162 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,11 @@ fiatjaf.com/lib v0.3.1 h1:/oFQwNtFRfV+ukmOCxfBEAuayoLwXp4wu2/fz5iHpwA= fiatjaf.com/lib v0.3.1/go.mod h1:Ycqq3+mJ9jAWu7XjbQI1cVr+OFgnHn79dQR5oTII47g= +github.com/FastFilter/xorfilter v0.2.1 h1:lbdeLG9BdpquK64ZsleBS8B4xO/QW1IM0gMzF7KaBKc= +github.com/FastFilter/xorfilter v0.2.1/go.mod h1:aumvdkhscz6YBZF9ZA/6O4fIoNod4YR50kIVGGZ7l9I= github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 h1:ClzzXMDDuUbWfNNZqGeYq4PnYOlwlOVIvSyNaIy0ykg= github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3/go.mod h1:we0YA5CsBbH5+/NUzC/AlMmxaDtWlXeNsqrwXjTzmzA= +github.com/PowerDNS/lmdb-go v1.9.3 h1:AUMY2pZT8WRpkEv39I9Id3MuoHd+NZbTVpNhruVkPTg= +github.com/PowerDNS/lmdb-go v1.9.3/go.mod h1:TE0l+EZK8Z1B4dx070ZxkWTlp8RG1mjN0/+FkFRQMtU= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= @@ -38,6 +42,8 @@ github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1 github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= @@ -77,10 +83,6 @@ github.com/fasthttp/websocket v1.5.12 h1:e4RGPpWW2HTbL3zV0Y/t7g0ub294LkiuXXUuTOU github.com/fasthttp/websocket v1.5.12/go.mod h1:I+liyL7/4moHojiOgUOIKEWm9EIxHqxZChS+aMFltyg= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/fiatjaf/eventstore v0.16.2 h1:h4rHwSwPcqAKqWUsAbYWUhDeSgm2Kp+PBkJc3FgBYu4= -github.com/fiatjaf/eventstore v0.16.2/go.mod h1:0gU8fzYO/bG+NQAVlHtJWOlt3JKKFefh5Xjj2d1dLIs= -github.com/fiatjaf/khatru v0.17.4 h1:VzcLUyBKMlP/CAG4iHJbDJmnZgzhbGLKLxJAUuLRogg= -github.com/fiatjaf/khatru v0.17.4/go.mod h1:VYQ7ZNhs3C1+E4gBnx+DtEgU0BrPdrl3XYF3H+mq6fg= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= @@ -149,8 +151,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/nbd-wtf/go-nostr v0.51.8 h1:CIoS+YqChcm4e1L1rfMZ3/mIwTz4CwApM2qx7MHNzmE= -github.com/nbd-wtf/go-nostr v0.51.8/go.mod h1:d6+DfvMWYG5pA3dmNMBJd6WCHVDDhkXbHqvfljf0Gzg= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -224,6 +224,8 @@ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/helpers.go b/helpers.go index 53a4c1b..ba4be7d 100644 --- a/helpers.go +++ b/helpers.go @@ -17,11 +17,12 @@ import ( "sync" "time" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/nip19" + "fiatjaf.com/nostr/nip42" + "fiatjaf.com/nostr/sdk" "github.com/fatih/color" jsoniter "github.com/json-iterator/go" - "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/nip19" - "github.com/nbd-wtf/go-nostr/sdk" "github.com/urfave/cli/v3" "golang.org/x/term" ) @@ -155,8 +156,8 @@ func connectToAllRelays( ctx context.Context, c *cli.Command, relayUrls []string, - preAuthSigner func(ctx context.Context, c *cli.Command, log func(s string, args ...any), authEvent nostr.RelayEvent) (err error), // if this exists we will force preauth - opts ...nostr.PoolOption, + preAuthSigner func(ctx context.Context, c *cli.Command, log func(s string, args ...any), authEvent *nostr.Event) (err error), // if this exists we will force preauth + opts nostr.PoolOptions, ) []*nostr.Relay { // first pass to check if these are valid relay URLs for _, url := range relayUrls { @@ -166,15 +167,12 @@ func connectToAllRelays( } } - sys.Pool = nostr.NewSimplePool(context.Background(), - append(opts, - nostr.WithEventMiddleware(sys.TrackEventHints), - nostr.WithPenaltyBox(), - nostr.WithRelayOptions( - nostr.WithRequestHeader(http.Header{textproto.CanonicalMIMEHeaderKey("user-agent"): {"nak/s"}}), - ), - )..., - ) + opts.EventMiddleware = sys.TrackEventHints + opts.PenaltyBox = true + opts.RelayOptions = nostr.RelayOptions{ + RequestHeader: http.Header{textproto.CanonicalMIMEHeaderKey("user-agent"): {"nak/s"}}, + } + sys.Pool = nostr.NewPool(opts) relays := make([]*nostr.Relay, 0, len(relayUrls)) @@ -236,7 +234,7 @@ func connectToSingleRelay( ctx context.Context, c *cli.Command, url string, - preAuthSigner func(ctx context.Context, c *cli.Command, log func(s string, args ...any), authEvent nostr.RelayEvent) (err error), + preAuthSigner func(ctx context.Context, c *cli.Command, log func(s string, args ...any), authEvent *nostr.Event) (err error), colorizepreamble func(c func(string, ...any) string), logthis func(s string, args ...any), ) *nostr.Relay { @@ -249,12 +247,12 @@ func connectToSingleRelay( time.Sleep(time.Millisecond * 200) for range 5 { - if err := relay.Auth(ctx, func(authEvent *nostr.Event) error { + if err := relay.Auth(ctx, func(ctx context.Context, authEvent *nostr.Event) error { challengeTag := authEvent.Tags.Find("challenge") if challengeTag[1] == "" { return fmt.Errorf("auth not received yet *****") // what a giant hack } - return preAuthSigner(ctx, c, logthis, nostr.RelayEvent{Event: authEvent, Relay: relay}) + return preAuthSigner(ctx, c, logthis, authEvent) }); err == nil { // auth succeeded goto preauthSuccess @@ -324,10 +322,10 @@ func supportsDynamicMultilineMagic() bool { return true } -func authSigner(ctx context.Context, c *cli.Command, log func(s string, args ...any), authEvent nostr.RelayEvent) (err error) { +func authSigner(ctx context.Context, c *cli.Command, log func(s string, args ...any), authEvent *nostr.Event) (err error) { defer func() { if err != nil { - cleanUrl, _ := strings.CutPrefix(authEvent.Relay.URL, "wss://") + cleanUrl, _ := strings.CutPrefix(nip42.GetRelayURLFromAuthEvent(*authEvent), "wss://") log("%s auth failed: %s", colors.errorf(cleanUrl), err) } }() @@ -341,10 +339,10 @@ func authSigner(ctx context.Context, c *cli.Command, log func(s string, args ... } pk, _ := kr.GetPublicKey(ctx) - npub, _ := nip19.EncodePublicKey(pk) + npub := nip19.EncodeNpub(pk) log("authenticating as %s... ", color.YellowString("%s…%s", npub[0:7], npub[58:])) - return kr.SignEvent(ctx, authEvent.Event) + return kr.SignEvent(ctx, authEvent) } func lineProcessingError(ctx context.Context, msg string, args ...any) context.Context { @@ -368,10 +366,6 @@ func randString(n int) string { return string(b) } -func leftPadKey(k string) string { - return strings.Repeat("0", 64-len(k)) + k -} - func unwrapAll(err error) error { low := err for n := low; n != nil; n = errors.Unwrap(low) { diff --git a/helpers_key.go b/helpers_key.go index d560001..972c11c 100644 --- a/helpers_key.go +++ b/helpers_key.go @@ -2,19 +2,18 @@ package main import ( "context" - "encoding/hex" "fmt" "os" "strings" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/keyer" + "fiatjaf.com/nostr/nip19" + "fiatjaf.com/nostr/nip46" + "fiatjaf.com/nostr/nip49" "github.com/chzyer/readline" "github.com/fatih/color" "github.com/urfave/cli/v3" - "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/keyer" - "github.com/nbd-wtf/go-nostr/nip19" - "github.com/nbd-wtf/go-nostr/nip46" - "github.com/nbd-wtf/go-nostr/nip49" ) var defaultKeyFlags = []cli.Flag{ @@ -39,10 +38,10 @@ var defaultKeyFlags = []cli.Flag{ }, } -func gatherKeyerFromArguments(ctx context.Context, c *cli.Command) (nostr.Keyer, string, error) { +func gatherKeyerFromArguments(ctx context.Context, c *cli.Command) (nostr.Keyer, nostr.SecretKey, error) { key, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c) if err != nil { - return nil, "", err + return nil, nostr.SecretKey{}, err } var kr nostr.Keyer @@ -55,23 +54,27 @@ func gatherKeyerFromArguments(ctx context.Context, c *cli.Command) (nostr.Keyer, return kr, key, err } -func gatherSecretKeyOrBunkerFromArguments(ctx context.Context, c *cli.Command) (string, *nip46.BunkerClient, error) { +func gatherSecretKeyOrBunkerFromArguments(ctx context.Context, c *cli.Command) (nostr.SecretKey, *nip46.BunkerClient, error) { var err error sec := c.String("sec") if strings.HasPrefix(sec, "bunker://") { // it's a bunker bunkerURL := sec - clientKey := c.String("connect-as") - if clientKey != "" { - clientKey = strings.Repeat("0", 64-len(clientKey)) + clientKey + clientKeyHex := c.String("connect-as") + var clientKey nostr.SecretKey + + if clientKeyHex != "" { + clientKey, err = nostr.SecretKeyFromHex(sec) } else { - clientKey = nostr.GeneratePrivateKey() + clientKey = nostr.Generate() } + bunker, err := nip46.ConnectBunker(ctx, clientKey, bunkerURL, nil, func(s string) { log(color.CyanString("[nip46]: open the following URL: %s"), s) }) - return "", bunker, err + + return nostr.SecretKey{}, bunker, err } // take private from flags, environment variable or default to 1 @@ -85,35 +88,35 @@ func gatherSecretKeyOrBunkerFromArguments(ctx context.Context, c *cli.Command) ( if c.Bool("prompt-sec") { if isPiped() { - return "", nil, fmt.Errorf("can't prompt for a secret key when processing data from a pipe, try again without --prompt-sec") + return nostr.SecretKey{}, nil, fmt.Errorf("can't prompt for a secret key when processing data from a pipe, try again without --prompt-sec") } sec, err = askPassword("type your secret key as ncryptsec, nsec or hex: ", nil) if err != nil { - return "", nil, fmt.Errorf("failed to get secret key: %w", err) + return nostr.SecretKey{}, nil, fmt.Errorf("failed to get secret key: %w", err) } } if strings.HasPrefix(sec, "ncryptsec1") { - sec, err = promptDecrypt(sec) + sk, err := promptDecrypt(sec) if err != nil { - return "", nil, fmt.Errorf("failed to decrypt: %w", err) + return nostr.SecretKey{}, nil, fmt.Errorf("failed to decrypt: %w", err) } - } else if bsec, err := hex.DecodeString(leftPadKey(sec)); err == nil { - sec = hex.EncodeToString(bsec) - } else if prefix, hexvalue, err := nip19.Decode(sec); err != nil { - return "", nil, fmt.Errorf("invalid nsec: %w", err) - } else if prefix == "nsec" { - sec = hexvalue.(string) + return sk, nil, nil } - if ok := nostr.IsValid32ByteHex(sec); !ok { - return "", nil, fmt.Errorf("invalid secret key") + if prefix, ski, err := nip19.Decode(sec); err == nil && prefix == "nsec" { + return ski.(nostr.SecretKey), nil, nil } - return sec, nil, nil + sk, err := nostr.SecretKeyFromHex(sec) + if err != nil { + return nostr.SecretKey{}, nil, fmt.Errorf("invalid secret key") + } + + return sk, nil, nil } -func promptDecrypt(ncryptsec string) (string, error) { +func promptDecrypt(ncryptsec string) (nostr.SecretKey, error) { for i := 1; i < 4; i++ { var attemptStr string if i > 1 { @@ -121,7 +124,7 @@ func promptDecrypt(ncryptsec string) (string, error) { } password, err := askPassword("type the password to decrypt your secret key"+attemptStr+": ", nil) if err != nil { - return "", err + return nostr.SecretKey{}, err } sec, err := nip49.Decrypt(ncryptsec, password) if err != nil { @@ -129,7 +132,7 @@ func promptDecrypt(ncryptsec string) (string, error) { } return sec, nil } - return "", fmt.Errorf("couldn't decrypt private key") + return nostr.SecretKey{}, fmt.Errorf("couldn't decrypt private key") } func askPassword(msg string, shouldAskAgain func(answer string) bool) (string, error) { diff --git a/key.go b/key.go index 4ccd5cb..2d5c115 100644 --- a/key.go +++ b/key.go @@ -6,13 +6,13 @@ import ( "fmt" "strings" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/nip19" + "fiatjaf.com/nostr/nip49" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/urfave/cli/v3" - "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/nip19" - "github.com/nbd-wtf/go-nostr/nip49" ) var key = &cli.Command{ @@ -35,8 +35,8 @@ var generate = &cli.Command{ Description: ``, DisableSliceFlagSeparator: true, Action: func(ctx context.Context, c *cli.Command) error { - sec := nostr.GeneratePrivateKey() - stdout(sec) + sec := nostr.Generate() + stdout(sec.Hex()) return nil }, } @@ -54,9 +54,8 @@ var public = &cli.Command{ }, }, Action: func(ctx context.Context, c *cli.Command) error { - for sec := range getSecretKeysFromStdinLinesOrSlice(ctx, c, c.Args().Slice()) { - b, _ := hex.DecodeString(sec) - _, pk := btcec.PrivKeyFromBytes(b) + for sk := range getSecretKeysFromStdinLinesOrSlice(ctx, c, c.Args().Slice()) { + _, pk := btcec.PrivKeyFromBytes(sk[:]) if c.Bool("with-parity") { stdout(hex.EncodeToString(pk.SerializeCompressed())) @@ -264,27 +263,31 @@ However, if the intent is to check if two existing Nostr pubkeys match a given c }, } -func getSecretKeysFromStdinLinesOrSlice(ctx context.Context, _ *cli.Command, keys []string) chan string { - ch := make(chan string) +func getSecretKeysFromStdinLinesOrSlice(ctx context.Context, _ *cli.Command, keys []string) chan nostr.SecretKey { + ch := make(chan nostr.SecretKey) go func() { for sec := range getStdinLinesOrArgumentsFromSlice(keys) { if sec == "" { continue } + + var sk nostr.SecretKey if strings.HasPrefix(sec, "nsec1") { _, data, err := nip19.Decode(sec) if err != nil { ctx = lineProcessingError(ctx, "invalid nsec code: %s", err) continue } - sec = data.(string) + sk = data.(nostr.SecretKey) } - sec = leftPadKey(sec) - if !nostr.IsValid32ByteHex(sec) { - ctx = lineProcessingError(ctx, "invalid hex key") + + sk, err := nostr.SecretKeyFromHex(sec) + if err != nil { + ctx = lineProcessingError(ctx, "invalid hex key: %s", err) continue } - ch <- sec + + ch <- sk } close(ch) }() diff --git a/main.go b/main.go index 44278cf..70d3954 100644 --- a/main.go +++ b/main.go @@ -7,9 +7,9 @@ import ( "os" "path/filepath" - "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/sdk" - "github.com/nbd-wtf/go-nostr/sdk/hints/memoryh" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/sdk" + "fiatjaf.com/nostr/sdk/hints/memoryh" "github.com/urfave/cli/v3" ) @@ -111,13 +111,13 @@ var app = &cli.Command{ sys = sdk.NewSystem() systemOperational: - sys.Pool = nostr.NewSimplePool(context.Background(), - nostr.WithAuthorKindQueryMiddleware(sys.TrackQueryAttempts), - nostr.WithEventMiddleware(sys.TrackEventHints), - nostr.WithRelayOptions( - nostr.WithRequestHeader(http.Header{textproto.CanonicalMIMEHeaderKey("user-agent"): {"nak/b"}}), - ), - ) + sys.Pool = nostr.NewPool(nostr.PoolOptions{ + AuthorKindQueryMiddleware: sys.TrackQueryAttempts, + EventMiddleware: sys.TrackEventHints, + RelayOptions: nostr.RelayOptions{ + RequestHeader: http.Header{textproto.CanonicalMIMEHeaderKey("user-agent"): {"nak/b"}}, + }, + }) return ctx, nil }, diff --git a/mcp.go b/mcp.go index d8c45fa..96e847a 100644 --- a/mcp.go +++ b/mcp.go @@ -6,11 +6,11 @@ import ( "os" "strings" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/nip19" + "fiatjaf.com/nostr/sdk" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" - "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/nip19" - "github.com/nbd-wtf/go-nostr/sdk" "github.com/urfave/cli/v3" ) @@ -19,13 +19,23 @@ var mcpServer = &cli.Command{ Usage: "pander to the AI gods", Description: ``, DisableSliceFlagSeparator: true, - Flags: []cli.Flag{}, + Flags: append( + defaultKeyFlags, + ), Action: func(ctx context.Context, c *cli.Command) error { s := server.NewMCPServer( "nak", version, ) + keyer, sk, err := gatherKeyerFromArguments(ctx, c) + if err != nil { + return err + } + if sk == nostr.KeyOne && !c.IsSet("sec") { + keyer = nil + } + s.AddTool(mcp.NewTool("publish_note", mcp.WithDescription("Publish a short note event to Nostr with the given text content"), mcp.WithString("content", mcp.Description("Arbitrary string to be published"), mcp.Required()), @@ -36,10 +46,6 @@ var mcpServer = &cli.Command{ mention, _ := optional[string](r, "mention") relay, _ := optional[string](r, "relay") - if mention != "" && !nostr.IsValidPublicKey(mention) { - return mcp.NewToolResultError("the given mention isn't a valid public key, it must be 32 bytes hex, like the ones returned by search_profile"), nil - } - sk := os.Getenv("NOSTR_SECRET_KEY") if sk == "" { sk = "0000000000000000000000000000000000000000000000000000000000000001" @@ -54,12 +60,19 @@ var mcpServer = &cli.Command{ } if mention != "" { - evt.Tags = append(evt.Tags, nostr.Tag{"p", mention}) + pk, err := nostr.PubKeyFromHex(mention) + if err != nil { + return mcp.NewToolResultError("the given mention isn't a valid public key, it must be 32 bytes hex, like the ones returned by search_profile. Got error: " + err.Error()), nil + } + + evt.Tags = append(evt.Tags, nostr.Tag{"p", pk.Hex()}) // their inbox relays - relays = sys.FetchInboxRelays(ctx, mention, 3) + relays = sys.FetchInboxRelays(ctx, pk, 3) } - evt.Sign(sk) + if err := keyer.SignEvent(ctx, &evt); err != nil { + return mcp.NewToolResultError("it was impossible to sign the event, so we can't proceed to publishwith publishing it."), nil + } // our write relays relays = append(relays, sys.FetchOutboxRelays(ctx, evt.PubKey, 3)...) @@ -115,7 +128,7 @@ var mcpServer = &cli.Command{ switch prefix { case "npub": - pm := sys.FetchProfileMetadata(ctx, data.(string)) + pm := sys.FetchProfileMetadata(ctx, data.(nostr.PubKey)) return mcp.NewToolResultText( fmt.Sprintf("this is a Nostr profile named '%s', their public key is '%s'", pm.ShortName(), pm.PubKey), @@ -149,19 +162,23 @@ var mcpServer = &cli.Command{ mcp.WithString("name", mcp.Description("Name to be searched"), mcp.Required()), ), func(ctx context.Context, r mcp.CallToolRequest) (*mcp.CallToolResult, error) { name := required[string](r, "name") - re := sys.Pool.QuerySingle(ctx, []string{"relay.nostr.band", "nostr.wine"}, nostr.Filter{Search: name, Kinds: []int{0}}) + re := sys.Pool.QuerySingle(ctx, []string{"relay.nostr.band", "nostr.wine"}, nostr.Filter{Search: name, Kinds: []nostr.Kind{0}}, nostr.SubscriptionOptions{}) if re == nil { return mcp.NewToolResultError("couldn't find anyone with that name"), nil } - return mcp.NewToolResultText(re.PubKey), nil + return mcp.NewToolResultText(re.PubKey.Hex()), nil }) s.AddTool(mcp.NewTool("get_outbox_relay_for_pubkey", mcp.WithDescription("Get the best relay from where to read notes from a specific Nostr user"), mcp.WithString("pubkey", mcp.Description("Public key of Nostr user we want to know the relay from where to read"), mcp.Required()), ), func(ctx context.Context, r mcp.CallToolRequest) (*mcp.CallToolResult, error) { - pubkey := required[string](r, "pubkey") + pubkey, err := nostr.PubKeyFromHex(required[string](r, "pubkey")) + if err != nil { + return mcp.NewToolResultError("the pubkey given isn't a valid public key, it must be 32 bytes hex, like the ones returned by search_profile. Got error: " + err.Error()), nil + } + res := sys.FetchOutboxRelays(ctx, pubkey, 1) return mcp.NewToolResultText(res[0]), nil }) @@ -171,31 +188,32 @@ var mcpServer = &cli.Command{ mcp.WithString("relay", mcp.Description("relay URL to send the query to"), mcp.Required()), mcp.WithNumber("kind", mcp.Description("event kind number to include in the 'kinds' field"), mcp.Required()), mcp.WithNumber("limit", mcp.Description("maximum number of events to query"), mcp.Required()), - mcp.WithString("pubkey", mcp.Description("pubkey to include in the 'authors' field")), + mcp.WithString("pubkey", mcp.Description("pubkey to include in the 'authors' field, if this is not given we will read any events from this relay")), ), func(ctx context.Context, r mcp.CallToolRequest) (*mcp.CallToolResult, error) { relay := required[string](r, "relay") kind := int(required[float64](r, "kind")) limit := int(required[float64](r, "limit")) - pubkey, _ := optional[string](r, "pubkey") - - if pubkey != "" && !nostr.IsValidPublicKey(pubkey) { - return mcp.NewToolResultError("the given pubkey isn't a valid public key, it must be 32 bytes hex, like the ones returned by search_profile"), nil - } + pubkey, hasPubKey := optional[string](r, "pubkey") filter := nostr.Filter{ Limit: limit, - Kinds: []int{kind}, - } - if pubkey != "" { - filter.Authors = []string{pubkey} + Kinds: []nostr.Kind{nostr.Kind(kind)}, } - events := sys.Pool.FetchMany(ctx, []string{relay}, filter) + if hasPubKey { + if pk, err := nostr.PubKeyFromHex(pubkey); err != nil { + return mcp.NewToolResultError("the pubkey given isn't a valid public key, it must be 32 bytes hex, like the ones returned by search_profile. Got error: " + err.Error()), nil + } else { + filter.Authors = append(filter.Authors, pk) + } + } + + events := sys.Pool.FetchMany(ctx, []string{relay}, filter, nostr.SubscriptionOptions{}) result := strings.Builder{} for ie := range events { result.WriteString("author public key: ") - result.WriteString(ie.PubKey) + result.WriteString(ie.PubKey.Hex()) result.WriteString("content: '") result.WriteString(ie.Content) result.WriteString("'") diff --git a/musig2.go b/musig2.go index 1d87f32..eacb96e 100644 --- a/musig2.go +++ b/musig2.go @@ -9,39 +9,39 @@ import ( "strconv" "strings" + "fiatjaf.com/nostr" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/nbd-wtf/go-nostr" ) -func getMusigAggregatedKey(_ context.Context, keys []string) (string, error) { +func getMusigAggregatedKey(_ context.Context, keys []string) (nostr.PubKey, error) { knownSigners := make([]*btcec.PublicKey, len(keys)) for i, spk := range keys { bpk, err := hex.DecodeString(spk) if err != nil { - return "", fmt.Errorf("'%s' is invalid hex: %w", spk, err) + return nostr.ZeroPK, fmt.Errorf("'%s' is invalid hex: %w", spk, err) } if len(bpk) == 32 { - return "", fmt.Errorf("'%s' is missing the leading parity byte", spk) + return nostr.ZeroPK, fmt.Errorf("'%s' is missing the leading parity byte", spk) } pk, err := btcec.ParsePubKey(bpk) if err != nil { - return "", fmt.Errorf("'%s' is not a valid pubkey: %w", spk, err) + return nostr.ZeroPK, fmt.Errorf("'%s' is not a valid pubkey: %w", spk, err) } knownSigners[i] = pk } aggpk, _, _, err := musig2.AggregateKeys(knownSigners, true) if err != nil { - return "", fmt.Errorf("aggregation failed: %w", err) + return nostr.ZeroPK, fmt.Errorf("aggregation failed: %w", err) } - return hex.EncodeToString(aggpk.FinalKey.SerializeCompressed()[1:]), nil + return nostr.PubKey(aggpk.FinalKey.SerializeCompressed()[1:]), nil } func performMusig( _ context.Context, - sec string, + sec nostr.SecretKey, evt *nostr.Event, numSigners int, keys []string, @@ -50,11 +50,7 @@ func performMusig( partialSigs []string, ) (signed bool, err error) { // preprocess data received - secb, err := hex.DecodeString(sec) - if err != nil { - return false, err - } - seck, pubk := btcec.PrivKeyFromBytes(secb) + seck, pubk := btcec.PrivKeyFromBytes(sec[:]) knownSigners := make([]*btcec.PublicKey, 0, numSigners) includesUs := false @@ -146,7 +142,7 @@ func performMusig( if comb, err := mctx.CombinedKey(); err != nil { return false, fmt.Errorf("failed to combine keys (after %d signers): %w", len(knownSigners), err) } else { - evt.PubKey = hex.EncodeToString(comb.SerializeCompressed()[1:]) + evt.PubKey = nostr.PubKey(comb.SerializeCompressed()[1:]) evt.ID = evt.GetID() log("combined key: %x\n\n", comb.SerializeCompressed()) } @@ -200,11 +196,7 @@ func performMusig( // signing phase // we always have to sign, so let's do this - id := evt.GetID() - hash, _ := hex.DecodeString(id) - var msg32 [32]byte - copy(msg32[:], hash) - partialSig, err := session.Sign(msg32) // this will already include our sig in the bundle + partialSig, err := session.Sign(evt.GetID()) // this will already include our sig in the bundle if err != nil { return false, fmt.Errorf("failed to produce partial signature: %w", err) } @@ -225,7 +217,7 @@ func performMusig( } // we have the signature - evt.Sig = hex.EncodeToString(session.FinalSig().Serialize()) + evt.Sig = [64]byte(session.FinalSig().Serialize()) return true, nil } @@ -258,7 +250,7 @@ func eventToCliArgs(evt *nostr.Event) string { b.Grow(100) b.WriteString("-k ") - b.WriteString(strconv.Itoa(evt.Kind)) + b.WriteString(strconv.Itoa(int(evt.Kind))) b.WriteString(" -ts ") b.WriteString(strconv.FormatInt(int64(evt.CreatedAt), 10)) @@ -269,7 +261,7 @@ func eventToCliArgs(evt *nostr.Event) string { for _, tag := range evt.Tags { b.WriteString(" -t '") - b.WriteString(tag.Key()) + b.WriteString(tag[0]) if len(tag) > 1 { b.WriteString("=") b.WriteString(tag[1]) diff --git a/nostrfs/asyncfile.go b/nostrfs/asyncfile.go index c322f64..14d5b16 100644 --- a/nostrfs/asyncfile.go +++ b/nostrfs/asyncfile.go @@ -7,7 +7,7 @@ import ( "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" - "github.com/nbd-wtf/go-nostr" + "fiatjaf.com/nostr" ) type AsyncFile struct { diff --git a/nostrfs/entitydir.go b/nostrfs/entitydir.go index ed8bd76..2b8a9c8 100644 --- a/nostrfs/entitydir.go +++ b/nostrfs/entitydir.go @@ -15,14 +15,15 @@ import ( "unsafe" "fiatjaf.com/lib/debouncer" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/nip19" + "fiatjaf.com/nostr/nip27" + "fiatjaf.com/nostr/nip73" + "fiatjaf.com/nostr/nip92" + sdk "fiatjaf.com/nostr/sdk" "github.com/fatih/color" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" - "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/nip19" - "github.com/nbd-wtf/go-nostr/nip27" - "github.com/nbd-wtf/go-nostr/nip92" - sdk "github.com/nbd-wtf/go-nostr/sdk" ) type EntityDir struct { @@ -95,11 +96,10 @@ func (e *EntityDir) Setattr(_ context.Context, _ fs.FileHandle, in *fuse.SetAttr func (e *EntityDir) OnAdd(_ context.Context) { log := e.root.ctx.Value("log").(func(msg string, args ...any)) - npub, _ := nip19.EncodePublicKey(e.event.PubKey) e.AddChild("@author", e.NewPersistentInode( e.root.ctx, &fs.MemSymlink{ - Data: []byte(e.root.wd + "/" + npub), + Data: []byte(e.root.wd + "/" + nip19.EncodeNpub(e.event.PubKey)), }, fs.StableAttr{Mode: syscall.S_IFLNK}, ), true) @@ -180,8 +180,12 @@ func (e *EntityDir) OnAdd(_ context.Context) { var refsdir *fs.Inode i := 0 - for ref := range nip27.ParseReferences(*e.event) { + for ref := range nip27.Parse(e.event.Content) { + if _, isExternal := ref.Pointer.(nip73.ExternalPointer); isExternal { + continue + } i++ + if refsdir == nil { refsdir = e.NewPersistentInode(e.root.ctx, &fs.Inode{}, fs.StableAttr{Mode: syscall.S_IFDIR}) e.root.AddChild("references", refsdir, true) @@ -320,7 +324,11 @@ func (e *EntityDir) handleWrite() { } // add "p" tags from people mentioned and "q" tags from events mentioned - for ref := range nip27.ParseReferences(evt) { + for ref := range nip27.Parse(evt.Content) { + if _, isExternal := ref.Pointer.(nip73.ExternalPointer); isExternal { + continue + } + tag := ref.Pointer.AsTag() key := tag[0] val := tag[1] @@ -339,7 +347,7 @@ func (e *EntityDir) handleWrite() { } logverbose("%s\n", evt) - relays := e.root.sys.FetchWriteRelays(e.root.ctx, e.root.rootPubKey, 8) + relays := e.root.sys.FetchWriteRelays(e.root.ctx, e.root.rootPubKey) if len(relays) == 0 { relays = e.root.sys.FetchOutboxRelays(e.root.ctx, e.root.rootPubKey, 6) } diff --git a/nostrfs/eventdir.go b/nostrfs/eventdir.go index ac99196..ced7a71 100644 --- a/nostrfs/eventdir.go +++ b/nostrfs/eventdir.go @@ -3,6 +3,7 @@ package nostrfs import ( "bytes" "context" + "encoding/binary" "encoding/json" "fmt" "io" @@ -11,16 +12,16 @@ import ( "syscall" "time" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/nip10" + "fiatjaf.com/nostr/nip19" + "fiatjaf.com/nostr/nip22" + "fiatjaf.com/nostr/nip27" + "fiatjaf.com/nostr/nip73" + "fiatjaf.com/nostr/nip92" + sdk "fiatjaf.com/nostr/sdk" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" - "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/nip10" - "github.com/nbd-wtf/go-nostr/nip19" - "github.com/nbd-wtf/go-nostr/nip22" - "github.com/nbd-wtf/go-nostr/nip27" - "github.com/nbd-wtf/go-nostr/nip73" - "github.com/nbd-wtf/go-nostr/nip92" - sdk "github.com/nbd-wtf/go-nostr/sdk" ) type EventDir struct { @@ -58,14 +59,13 @@ func (r *NostrRoot) CreateEventDir( h := parent.EmbeddedInode().NewPersistentInode( r.ctx, &EventDir{ctx: r.ctx, wd: r.wd, evt: event}, - fs.StableAttr{Mode: syscall.S_IFDIR, Ino: hexToUint64(event.ID)}, + fs.StableAttr{Mode: syscall.S_IFDIR, Ino: binary.BigEndian.Uint64(event.ID[8:16])}, ) - npub, _ := nip19.EncodePublicKey(event.PubKey) h.AddChild("@author", h.NewPersistentInode( r.ctx, &fs.MemSymlink{ - Data: []byte(r.wd + "/" + npub), + Data: []byte(r.wd + "/" + nip19.EncodeNpub(event.PubKey)), }, fs.StableAttr{Mode: syscall.S_IFLNK}, ), true) @@ -88,7 +88,7 @@ func (r *NostrRoot) CreateEventDir( h.AddChild("id", h.NewPersistentInode( r.ctx, &fs.MemRegularFile{ - Data: []byte(event.ID), + Data: []byte(event.ID.Hex()), Attr: fuse.Attr{ Mode: 0444, Ctime: uint64(event.CreatedAt), @@ -115,8 +115,12 @@ func (r *NostrRoot) CreateEventDir( var refsdir *fs.Inode i := 0 - for ref := range nip27.ParseReferences(*event) { + for ref := range nip27.Parse(event.Content) { + if _, isExternal := ref.Pointer.(nip73.ExternalPointer); isExternal { + continue + } i++ + if refsdir == nil { refsdir = h.NewPersistentInode(r.ctx, &fs.Inode{}, fs.StableAttr{Mode: syscall.S_IFDIR}) h.AddChild("references", refsdir, true) diff --git a/nostrfs/helpers.go b/nostrfs/helpers.go index e067e6b..79562b2 100644 --- a/nostrfs/helpers.go +++ b/nostrfs/helpers.go @@ -1,8 +1,10 @@ package nostrfs -import "strconv" +import ( + "fiatjaf.com/nostr" +) -func kindToExtension(kind int) string { +func kindToExtension(kind nostr.Kind) string { switch kind { case 30023: return "md" @@ -12,8 +14,3 @@ func kindToExtension(kind int) string { return "txt" } } - -func hexToUint64(hexStr string) uint64 { - v, _ := strconv.ParseUint(hexStr[16:32], 16, 64) - return v -} diff --git a/nostrfs/npubdir.go b/nostrfs/npubdir.go index d34a226..afce05c 100644 --- a/nostrfs/npubdir.go +++ b/nostrfs/npubdir.go @@ -3,6 +3,7 @@ package nostrfs import ( "bytes" "context" + "encoding/binary" "encoding/json" "io" "net/http" @@ -10,12 +11,12 @@ import ( "syscall" "time" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/nip19" "github.com/fatih/color" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" "github.com/liamg/magic" - "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/nip19" ) type NpubDir struct { @@ -36,7 +37,7 @@ func (r *NostrRoot) CreateNpubDir( return parent.EmbeddedInode().NewPersistentInode( r.ctx, npubdir, - fs.StableAttr{Mode: syscall.S_IFDIR, Ino: hexToUint64(pointer.PublicKey)}, + fs.StableAttr{Mode: syscall.S_IFDIR, Ino: binary.BigEndian.Uint64(pointer.PublicKey[8:16])}, ) } @@ -49,7 +50,7 @@ func (h *NpubDir) OnAdd(_ context.Context) { h.AddChild("pubkey", h.NewPersistentInode( h.root.ctx, - &fs.MemRegularFile{Data: []byte(h.pointer.PublicKey + "\n"), Attr: fuse.Attr{Mode: 0444}}, + &fs.MemRegularFile{Data: []byte(h.pointer.PublicKey.Hex() + "\n"), Attr: fuse.Attr{Mode: 0444}}, fs.StableAttr{}, ), true) @@ -116,8 +117,8 @@ func (h *NpubDir) OnAdd(_ context.Context) { &ViewDir{ root: h.root, filter: nostr.Filter{ - Kinds: []int{1}, - Authors: []string{h.pointer.PublicKey}, + Kinds: []nostr.Kind{1}, + Authors: []nostr.PubKey{h.pointer.PublicKey}, }, paginate: true, relays: relays, @@ -138,8 +139,8 @@ func (h *NpubDir) OnAdd(_ context.Context) { &ViewDir{ root: h.root, filter: nostr.Filter{ - Kinds: []int{1111}, - Authors: []string{h.pointer.PublicKey}, + Kinds: []nostr.Kind{1111}, + Authors: []nostr.PubKey{h.pointer.PublicKey}, }, paginate: true, relays: relays, @@ -159,8 +160,8 @@ func (h *NpubDir) OnAdd(_ context.Context) { &ViewDir{ root: h.root, filter: nostr.Filter{ - Kinds: []int{20}, - Authors: []string{h.pointer.PublicKey}, + Kinds: []nostr.Kind{20}, + Authors: []nostr.PubKey{h.pointer.PublicKey}, }, paginate: true, relays: relays, @@ -180,8 +181,8 @@ func (h *NpubDir) OnAdd(_ context.Context) { &ViewDir{ root: h.root, filter: nostr.Filter{ - Kinds: []int{21, 22}, - Authors: []string{h.pointer.PublicKey}, + Kinds: []nostr.Kind{21, 22}, + Authors: []nostr.PubKey{h.pointer.PublicKey}, }, paginate: false, relays: relays, @@ -201,8 +202,8 @@ func (h *NpubDir) OnAdd(_ context.Context) { &ViewDir{ root: h.root, filter: nostr.Filter{ - Kinds: []int{9802}, - Authors: []string{h.pointer.PublicKey}, + Kinds: []nostr.Kind{9802}, + Authors: []nostr.PubKey{h.pointer.PublicKey}, }, paginate: false, relays: relays, @@ -222,8 +223,8 @@ func (h *NpubDir) OnAdd(_ context.Context) { &ViewDir{ root: h.root, filter: nostr.Filter{ - Kinds: []int{30023}, - Authors: []string{h.pointer.PublicKey}, + Kinds: []nostr.Kind{30023}, + Authors: []nostr.PubKey{h.pointer.PublicKey}, }, paginate: false, relays: relays, @@ -244,8 +245,8 @@ func (h *NpubDir) OnAdd(_ context.Context) { &ViewDir{ root: h.root, filter: nostr.Filter{ - Kinds: []int{30818}, - Authors: []string{h.pointer.PublicKey}, + Kinds: []nostr.Kind{30818}, + Authors: []nostr.PubKey{h.pointer.PublicKey}, }, paginate: false, relays: relays, diff --git a/nostrfs/root.go b/nostrfs/root.go index d821bc8..6c9fc2f 100644 --- a/nostrfs/root.go +++ b/nostrfs/root.go @@ -6,12 +6,12 @@ import ( "syscall" "time" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/nip05" + "fiatjaf.com/nostr/nip19" + "fiatjaf.com/nostr/sdk" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" - "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/sdk" ) type Options struct { @@ -25,7 +25,7 @@ type NostrRoot struct { ctx context.Context wd string sys *sdk.System - rootPubKey string + rootPubKey nostr.PubKey signer nostr.Signer opts Options @@ -54,7 +54,7 @@ func NewNostrRoot(ctx context.Context, sys *sdk.System, user nostr.User, mountpo } func (r *NostrRoot) OnAdd(_ context.Context) { - if r.rootPubKey == "" { + if r.rootPubKey == nostr.ZeroPK { return } @@ -65,16 +65,15 @@ func (r *NostrRoot) OnAdd(_ context.Context) { fl := r.sys.FetchFollowList(r.ctx, r.rootPubKey) for _, f := range fl.Items { pointer := nostr.ProfilePointer{PublicKey: f.Pubkey, Relays: []string{f.Relay}} - npub, _ := nip19.EncodePublicKey(f.Pubkey) r.AddChild( - npub, + nip19.EncodeNpub(f.Pubkey), r.CreateNpubDir(r, pointer, nil), true, ) } // add ourselves - npub, _ := nip19.EncodePublicKey(r.rootPubKey) + npub := nip19.EncodeNpub(r.rootPubKey) if r.GetChild(npub) == nil { pointer := nostr.ProfilePointer{PublicKey: r.rootPubKey} diff --git a/nostrfs/viewdir.go b/nostrfs/viewdir.go index 1dbbd90..4f992fb 100644 --- a/nostrfs/viewdir.go +++ b/nostrfs/viewdir.go @@ -8,11 +8,12 @@ import ( "syscall" "fiatjaf.com/lib/debouncer" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/nip27" + "fiatjaf.com/nostr/nip73" "github.com/fatih/color" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" - "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/nip27" ) type ViewDir struct { @@ -141,13 +142,17 @@ func (n *ViewDir) publishNote() { } // our write relays - relays := n.root.sys.FetchWriteRelays(n.root.ctx, n.root.rootPubKey, 8) + relays := n.root.sys.FetchWriteRelays(n.root.ctx, n.root.rootPubKey) if len(relays) == 0 { relays = n.root.sys.FetchOutboxRelays(n.root.ctx, n.root.rootPubKey, 6) } // add "p" tags from people mentioned and "q" tags from events mentioned - for ref := range nip27.ParseReferences(evt) { + for ref := range nip27.Parse(evt.Content) { + if _, isExternal := ref.Pointer.(nip73.ExternalPointer); isExternal { + continue + } + tag := ref.Pointer.AsTag() key := tag[0] val := tag[1] @@ -159,7 +164,12 @@ func (n *ViewDir) publishNote() { // add their "read" relays if key == "p" { - for _, r := range n.root.sys.FetchInboxRelays(n.root.ctx, val, 4) { + pk, err := nostr.PubKeyFromHex(val) + if err != nil { + continue + } + + for _, r := range n.root.sys.FetchInboxRelays(n.root.ctx, pk, 4) { if !slices.Contains(relays, r) { relays = append(relays, r) } @@ -196,8 +206,8 @@ func (n *ViewDir) publishNote() { if success { n.RmChild("new") - n.AddChild(evt.ID, n.root.CreateEventDir(n, &evt), true) - log("event published as %s and updated locally.\n", color.BlueString(evt.ID)) + n.AddChild(evt.ID.Hex(), n.root.CreateEventDir(n, &evt), true) + log("event published as %s and updated locally.\n", color.BlueString(evt.ID.Hex())) } } @@ -241,23 +251,24 @@ func (n *ViewDir) Opendir(ctx context.Context) syscall.Errno { } if n.replaceable { - for rkey, evt := range n.root.sys.Pool.FetchManyReplaceable(n.root.ctx, n.relays, n.filter, - nostr.WithLabel("nakfs"), - ).Range { + for rkey, evt := range n.root.sys.Pool.FetchManyReplaceable(n.root.ctx, n.relays, n.filter, nostr.SubscriptionOptions{ + Label: "nakfs", + }).Range { name := rkey.D if name == "" { name = "_" } if n.GetChild(name) == nil { - n.AddChild(name, n.root.CreateEntityDir(n, evt), true) + n.AddChild(name, n.root.CreateEntityDir(n, &evt), true) } } } else { for ie := range n.root.sys.Pool.FetchMany(n.root.ctx, n.relays, n.filter, - nostr.WithLabel("nakfs"), - ) { - if n.GetChild(ie.Event.ID) == nil { - n.AddChild(ie.Event.ID, n.root.CreateEventDir(n, ie.Event), true) + nostr.SubscriptionOptions{ + Label: "nakfs", + }) { + if n.GetChild(ie.Event.ID.Hex()) == nil { + n.AddChild(ie.Event.ID.Hex(), n.root.CreateEventDir(n, &ie.Event), true) } } } diff --git a/outbox.go b/outbox.go index 284f148..3c3ae3e 100644 --- a/outbox.go +++ b/outbox.go @@ -6,8 +6,8 @@ import ( "os" "path/filepath" + "fiatjaf.com/nostr" "github.com/urfave/cli/v3" - "github.com/nbd-wtf/go-nostr" ) var outbox = &cli.Command{ @@ -52,12 +52,12 @@ var outbox = &cli.Command{ return fmt.Errorf("expected exactly one argument (pubkey)") } - pubkey := c.Args().First() - if !nostr.IsValidPublicKey(pubkey) { - return fmt.Errorf("invalid public key: %s", pubkey) + pk, err := nostr.PubKeyFromHex(c.Args().First()) + if err != nil { + return fmt.Errorf("invalid public key '%s': %w", c.Args().First(), err) } - for _, relay := range sys.FetchOutboxRelays(ctx, pubkey, 6) { + for _, relay := range sys.FetchOutboxRelays(ctx, pk, 6) { stdout(relay) } diff --git a/relay.go b/relay.go index b05f580..450ca33 100644 --- a/relay.go +++ b/relay.go @@ -10,9 +10,9 @@ import ( "io" "net/http" - "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/nip11" - "github.com/nbd-wtf/go-nostr/nip86" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/nip11" + "fiatjaf.com/nostr/nip86" "github.com/urfave/cli/v3" ) diff --git a/req.go b/req.go index 3221fdd..c55c1fa 100644 --- a/req.go +++ b/req.go @@ -6,10 +6,11 @@ import ( "os" "strings" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/nip42" + "fiatjaf.com/nostr/nip77" "github.com/fatih/color" "github.com/mailru/easyjson" - "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/nip77" "github.com/urfave/cli/v3" ) @@ -88,16 +89,20 @@ example: c, relayUrls, forcePreAuthSigner, - nostr.WithAuthHandler(func(ctx context.Context, authEvent nostr.RelayEvent) error { - return authSigner(ctx, c, func(s string, args ...any) { - if strings.HasPrefix(s, "authenticating as") { - cleanUrl, _ := strings.CutPrefix(authEvent.Relay.URL, "wss://") - s = "authenticating to " + color.CyanString(cleanUrl) + " as" + s[len("authenticating as"):] - } - log(s+"\n", args...) - }, authEvent) - }), - ) + nostr.PoolOptions{ + AuthHandler: func(ctx context.Context, authEvent *nostr.Event) error { + return authSigner(ctx, c, func(s string, args ...any) { + if strings.HasPrefix(s, "authenticating as") { + cleanUrl, _ := strings.CutPrefix( + nip42.GetRelayURLFromAuthEvent(*authEvent), + "wss://", + ) + s = "authenticating to " + color.CyanString(cleanUrl) + " as" + s[len("authenticating as"):] + } + log(s+"\n", args...) + }, authEvent) + }, + }) // stop here already if all connections failed if len(relays) == 0 { @@ -132,7 +137,7 @@ example: if len(relayUrls) > 0 { if c.Bool("ids-only") { - seen := make(map[string]struct{}, max(500, filter.Limit)) + seen := make(map[nostr.ID]struct{}, max(500, filter.Limit)) for _, url := range relayUrls { ch, err := nip77.FetchIDsOnly(ctx, url, filter) if err != nil { @@ -155,7 +160,7 @@ example: fn = sys.Pool.SubscribeMany } - for ie := range fn(ctx, relayUrls, filter) { + for ie := range fn(ctx, relayUrls, filter, nostr.SubscriptionOptions{}) { stdout(ie.Event) } } @@ -165,7 +170,7 @@ example: if c.Bool("bare") { result = filter.String() } else { - j, _ := json.Marshal(nostr.ReqEnvelope{SubscriptionID: "nak", Filters: nostr.Filters{filter}}) + j, _ := json.Marshal(nostr.ReqEnvelope{SubscriptionID: "nak", Filter: filter}) result = string(j) } @@ -179,13 +184,13 @@ example: } var reqFilterFlags = []cli.Flag{ - &cli.StringSliceFlag{ + &PubKeySliceFlag{ Name: "author", Aliases: []string{"a"}, Usage: "only accept events from these authors (pubkey as hex)", Category: CATEGORY_FILTER_ATTRIBUTES, }, - &cli.StringSliceFlag{ + &IDSliceFlag{ Name: "id", Aliases: []string{"i"}, Usage: "only accept events with these ids (hex)", @@ -244,14 +249,14 @@ var reqFilterFlags = []cli.Flag{ } func applyFlagsToFilter(c *cli.Command, filter *nostr.Filter) error { - if authors := c.StringSlice("author"); len(authors) > 0 { + if authors := getPubKeySlice(c, "author"); len(authors) > 0 { filter.Authors = append(filter.Authors, authors...) } - if ids := c.StringSlice("id"); len(ids) > 0 { + if ids := getIDSlice(c, "id"); len(ids) > 0 { filter.IDs = append(filter.IDs, ids...) } for _, kind64 := range c.IntSlice("kind") { - filter.Kinds = append(filter.Kinds, int(kind64)) + filter.Kinds = append(filter.Kinds, nostr.Kind(kind64)) } if search := c.String("search"); search != "" { filter.Search = search diff --git a/serve.go b/serve.go index 28489dc..ee6ed51 100644 --- a/serve.go +++ b/serve.go @@ -8,11 +8,11 @@ import ( "os" "time" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/eventstore/slicestore" + "fiatjaf.com/nostr/khatru" "github.com/bep/debounce" "github.com/fatih/color" - "github.com/fiatjaf/eventstore/slicestore" - "github.com/fiatjaf/khatru" - "github.com/nbd-wtf/go-nostr" "github.com/urfave/cli/v3" ) @@ -38,7 +38,7 @@ var serve = &cli.Command{ }, }, Action: func(ctx context.Context, c *cli.Command) error { - db := slicestore.SliceStore{MaxLimit: math.MaxInt} + db := &slicestore.SliceStore{MaxLimit: math.MaxInt} var scanner *bufio.Scanner if path := c.String("events"); path != "" { @@ -59,7 +59,7 @@ var serve = &cli.Command{ if err := json.Unmarshal(scanner.Bytes(), &evt); err != nil { return fmt.Errorf("invalid event received at line %d: %s (`%s`)", i, err, scanner.Text()) } - db.SaveEvent(ctx, &evt) + db.SaveEvent(evt) i++ } } @@ -71,10 +71,7 @@ var serve = &cli.Command{ rl.Info.Software = "https://github.com/fiatjaf/nak" rl.Info.Version = version - rl.QueryEvents = append(rl.QueryEvents, db.QueryEvents) - rl.CountEvents = append(rl.CountEvents, db.CountEvents) - rl.DeleteEvent = append(rl.DeleteEvent, db.DeleteEvent) - rl.StoreEvent = append(rl.StoreEvent, db.SaveEvent) + rl.UseEventstore(db) started := make(chan bool) exited := make(chan error) @@ -90,28 +87,29 @@ var serve = &cli.Command{ var printStatus func() // relay logging - rl.RejectFilter = append(rl.RejectFilter, func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) { + rl.OnRequest = func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) { log(" got %s %v\n", color.HiYellowString("request"), colors.italic(filter)) printStatus() return false, "" - }) - rl.RejectCountFilter = append(rl.RejectCountFilter, func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) { + } + + rl.OnCount = func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) { log(" got %s %v\n", color.HiCyanString("count request"), colors.italic(filter)) printStatus() return false, "" - }) - rl.RejectEvent = append(rl.RejectEvent, func(ctx context.Context, event *nostr.Event) (reject bool, msg string) { + } + + rl.OnEvent = func(ctx context.Context, event nostr.Event) (reject bool, msg string) { log(" got %s %v\n", color.BlueString("event"), colors.italic(event)) printStatus() return false, "" - }) + } d := debounce.New(time.Second * 2) printStatus = func() { d(func() { totalEvents := 0 - ch, _ := db.QueryEvents(ctx, nostr.Filter{}) - for range ch { + for range db.QueryEvents(nostr.Filter{}) { totalEvents++ } subs := rl.GetListeningFilters() diff --git a/verify.go b/verify.go index bccd27e..c74eab8 100644 --- a/verify.go +++ b/verify.go @@ -3,8 +3,8 @@ package main import ( "context" + "fiatjaf.com/nostr" "github.com/urfave/cli/v3" - "github.com/nbd-wtf/go-nostr" ) var verify = &cli.Command{ @@ -30,8 +30,8 @@ it outputs nothing if the verification is successful.`, continue } - if ok, err := evt.CheckSignature(); !ok { - ctx = lineProcessingError(ctx, "invalid signature: %v", err) + if !evt.VerifySignature() { + ctx = lineProcessingError(ctx, "invalid signature") continue } } diff --git a/wallet.go b/wallet.go index 088aaa5..615998c 100644 --- a/wallet.go +++ b/wallet.go @@ -6,10 +6,10 @@ import ( "strconv" "strings" - "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/nip60" - "github.com/nbd-wtf/go-nostr/nip61" - "github.com/nbd-wtf/go-nostr/sdk" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/nip60" + "fiatjaf.com/nostr/nip61" + "fiatjaf.com/nostr/sdk" "github.com/urfave/cli/v3" ) @@ -30,7 +30,7 @@ func prepareWallet(ctx context.Context, c *cli.Command) (*nip60.Wallet, func(), return nil, nil, fmt.Errorf("error loading walle") } - w.Processed = func(evt *nostr.Event, err error) { + w.Processed = func(evt nostr.Event, err error) { if err == nil { logverbose("processed event %s\n", evt) } else { @@ -321,11 +321,16 @@ var wallet = &cli.Command{ return err } - amount := c.Uint("amount") + amount, err := strconv.ParseInt(c.Args().First(), 10, 64) + if err != nil { + return fmt.Errorf("invalid amount '%s': %w", c.Args().First(), err) + } + target := c.String("target") + var pm sdk.ProfileMetadata var evt *nostr.Event - var eventId string + var eventId nostr.ID if strings.HasPrefix(target, "nevent1") { evt, _, err = sys.FetchSpecificEventFromInput(ctx, target, sdk.FetchSpecificEventParameters{ @@ -335,12 +340,12 @@ var wallet = &cli.Command{ return err } eventId = evt.ID - target = evt.PubKey - } - - pm, err := sys.FetchProfileFromInput(ctx, target) - if err != nil { - return err + pm = sys.FetchProfileMetadata(ctx, evt.PubKey) + } else { + pm, err = sys.FetchProfileFromInput(ctx, target) + if err != nil { + return err + } } log("sending %d sat to '%s' (%s)", amount, pm.ShortName(), pm.Npub()) @@ -361,7 +366,7 @@ var wallet = &cli.Command{ sys.FetchInboxRelays, sys.FetchOutboxRelays(ctx, pm.PubKey, 3), eventId, - amount, + uint64(amount), c.String("message"), ) if err != nil { @@ -426,14 +431,14 @@ var wallet = &cli.Command{ kr, _, _ := gatherKeyerFromArguments(ctx, c) pk, _ := kr.GetPublicKey(ctx) - relays := sys.FetchWriteRelays(ctx, pk, 6) + relays := sys.FetchWriteRelays(ctx, pk) info := nip61.Info{} ie := sys.Pool.QuerySingle(ctx, relays, nostr.Filter{ - Kinds: []int{10019}, - Authors: []string{pk}, + Kinds: []nostr.Kind{10019}, + Authors: []nostr.PubKey{pk}, Limit: 1, - }) + }, nostr.SubscriptionOptions{}) if ie != nil { info.ParseEvent(ie.Event) }