From bc7cd0939c9d3edaf696d31b045cdaa671c2cd9d Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Sat, 2 Dec 2023 12:18:55 -0300 Subject: [PATCH] nsecbunker work-in-progress. --- event.go | 31 ++----------- go.mod | 2 +- go.sum | 4 +- helpers.go | 35 ++++++++++++++ main.go | 1 + nsecbunker.go | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 167 insertions(+), 30 deletions(-) create mode 100644 nsecbunker.go diff --git a/event.go b/event.go index 8aa0255..8daaaa3 100644 --- a/event.go +++ b/event.go @@ -9,10 +9,8 @@ import ( "strings" "time" - "github.com/bgentry/speakeasy" "github.com/mailru/easyjson" "github.com/nbd-wtf/go-nostr" - "github.com/nbd-wtf/go-nostr/nip19" "github.com/nbd-wtf/go-nostr/nson" "github.com/urfave/cli/v2" "golang.org/x/exp/slices" @@ -106,31 +104,10 @@ example: } } - // gather the secret key first - sec := c.String("sec") - if c.Bool("prompt-sec") { - if isPiped() { - return fmt.Errorf("can't prompt for a secret key when processing data from a pipe, try again without --prompt-sec") - } - var err error - sec, err = speakeasy.FAsk(os.Stderr, "type your secret key as nsec or hex: ") - if err != nil { - return fmt.Errorf("failed to get secret key: %w", err) - } - } - if strings.HasPrefix(sec, "nsec1") { - _, hex, err := nip19.Decode(sec) - if err != nil { - return fmt.Errorf("invalid nsec: %w", err) - } - sec = hex.(string) - } - if len(sec) > 64 { - return fmt.Errorf("invalid secret key: too large") - } - sec = strings.Repeat("0", 64-len(sec)) + sec // left-pad - if err := validate32BytesHex(sec); err != nil { - return fmt.Errorf("invalid secret key") + // gather the secret key + sec, err := gatherSecretKeyFromArguments(c) + if err != nil { + return err } // then process input and generate events diff --git a/go.mod b/go.mod index 663aab5..8fe7917 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.21.0 require ( github.com/bgentry/speakeasy v0.1.0 github.com/mailru/easyjson v0.7.7 - github.com/nbd-wtf/go-nostr v0.26.1 + github.com/nbd-wtf/go-nostr v0.26.2 github.com/nbd-wtf/nostr-sdk v0.0.2 github.com/urfave/cli/v2 v2.25.3 golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 diff --git a/go.sum b/go.sum index 5e936c1..7caf913 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlT github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/nbd-wtf/go-nostr v0.26.1 h1:aqvXYdPycETozrRPp2roSNALNk2XMxRkBrHu3jXmhxA= -github.com/nbd-wtf/go-nostr v0.26.1/go.mod h1:bkffJI+x914sPQWum9ZRUn66D7NpDnAoWo1yICvj3/0= +github.com/nbd-wtf/go-nostr v0.26.2 h1:VI47qW7jqLrofB9DbDqH9lQH38eYgLYI7lBPYywIS78= +github.com/nbd-wtf/go-nostr v0.26.2/go.mod h1:bkffJI+x914sPQWum9ZRUn66D7NpDnAoWo1yICvj3/0= github.com/nbd-wtf/nostr-sdk v0.0.2 h1:mZIeti+DOF0D1179q+NLL/h0LVMMOPRQAYpOuUrn5Zk= github.com/nbd-wtf/nostr-sdk v0.0.2/go.mod h1:KQZOtzcrXBlVhpZYG1tw83ADIONNMMPjUU3ZAH5U2RY= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= diff --git a/helpers.go b/helpers.go index 94fce32..47fda35 100644 --- a/helpers.go +++ b/helpers.go @@ -9,12 +9,17 @@ import ( "os" "strings" + "github.com/bgentry/speakeasy" "github.com/nbd-wtf/go-nostr" + "github.com/nbd-wtf/go-nostr/nip19" "github.com/urfave/cli/v2" ) const ( LINE_PROCESSING_ERROR = iota + + BOLD_ON = "\033[1m" + BOLD_OFF = "\033[21m" ) var log = func(msg string, args ...any) { @@ -129,3 +134,33 @@ func exitIfLineProcessingError(c *cli.Context) { os.Exit(123) } } + +func gatherSecretKeyFromArguments(c *cli.Context) (string, error) { + sec := c.String("sec") + if c.Bool("prompt-sec") { + if isPiped() { + return "", fmt.Errorf("can't prompt for a secret key when processing data from a pipe, try again without --prompt-sec") + } + var err error + sec, err = speakeasy.FAsk(os.Stderr, "type your secret key as nsec or hex: ") + if err != nil { + return "", fmt.Errorf("failed to get secret key: %w", err) + } + } + if strings.HasPrefix(sec, "nsec1") { + _, hex, err := nip19.Decode(sec) + if err != nil { + return "", fmt.Errorf("invalid nsec: %w", err) + } + sec = hex.(string) + } + if len(sec) > 64 { + return "", fmt.Errorf("invalid secret key: too large") + } + sec = strings.Repeat("0", 64-len(sec)) + sec // left-pad + if err := validate32BytesHex(sec); err != nil { + return "", fmt.Errorf("invalid secret key") + } + + return sec, nil +} diff --git a/main.go b/main.go index e701dea..556b957 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,7 @@ var app = &cli.App{ encode, verify, relay, + nsecbunker, }, Flags: []cli.Flag{ &cli.BoolFlag{ diff --git a/nsecbunker.go b/nsecbunker.go new file mode 100644 index 0000000..393f565 --- /dev/null +++ b/nsecbunker.go @@ -0,0 +1,124 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/url" + "os" + + "github.com/bgentry/speakeasy" + "github.com/nbd-wtf/go-nostr" + "github.com/nbd-wtf/go-nostr/nip19" + "github.com/nbd-wtf/go-nostr/nip46" + "github.com/urfave/cli/v2" +) + +var nsecbunker = &cli.Command{ + Name: "nsecbunker", + Usage: "starts a NIP-46 signer daemon with the given --sec key", + ArgsUsage: "[relay...]", + Description: ``, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "sec", + Usage: "secret key to sign the event, as hex or nsec", + DefaultText: "the key '1'", + Value: "0000000000000000000000000000000000000000000000000000000000000001", + }, + &cli.BoolFlag{ + Name: "prompt-sec", + Usage: "prompt the user to paste a hex or nsec with which to sign the event", + }, + &cli.BoolFlag{ + Name: "yes", + Aliases: []string{"y"}, + Usage: "always respond to any NIP-46 requests from anyone", + }, + }, + Action: func(c *cli.Context) error { + // try to connect to the relays here + qs := url.Values{} + relayURLs := make([]string, 0, c.Args().Len()) + if relayUrls := c.Args().Slice(); len(relayUrls) > 0 { + _, relays := connectToAllRelays(c.Context, relayUrls) + if len(relays) == 0 { + log("failed to connect to any of the given relays.\n") + os.Exit(3) + } + for _, relay := range relays { + relayURLs = append(relayURLs, relay.URL) + qs.Add("relay", relay.URL) + } + } + if len(relayURLs) == 0 { + return fmt.Errorf("not connected to any relays: please specify at least one") + } + + // gather the secret key + sec, err := gatherSecretKeyFromArguments(c) + if err != nil { + return err + } + pubkey, err := nostr.GetPublicKey(sec) + if err != nil { + return err + } + npub, _ := nip19.EncodePublicKey(pubkey) + code := fmt.Sprintf("%s#secret?%s", npub, qs.Encode()) + log("listening at %s%v%s:\n %spubkey:%s %s\n %snpub:%s %s\n %sconnection code:%s %s\n\n", + BOLD_ON, relayURLs, BOLD_OFF, + BOLD_ON, BOLD_OFF, pubkey, + BOLD_ON, BOLD_OFF, npub, + BOLD_ON, BOLD_OFF, code, + ) + + alwaysYes := c.Bool("yes") + + // subscribe to relays + pool := nostr.NewSimplePool(c.Context) + events := pool.SubMany(c.Context, relayURLs, nostr.Filters{ + { + Kinds: []int{24133}, + Tags: nostr.TagMap{"p": []string{pubkey}}, + }, + }) + + signer := nip46.NewSigner(sec) + for ie := range events { + req, resp, eventResponse, harmless, err := signer.HandleRequest(ie.Event) + if err != nil { + log("< failed to handle request from %s: %w", ie.Event.PubKey, err) + continue + } + + jreq, _ := json.MarshalIndent(req, " ", " ") + log("- got request from '%s': %s\n", ie.Event.PubKey, string(jreq)) + jresp, _ := json.MarshalIndent(resp, " ", " ") + log("~ responding with %s\n", string(jresp)) + + if alwaysYes || harmless || askUserIfWeCanRespond() { + _, err := ie.Relay.Publish(c.Context, eventResponse) + if err == nil { + log("* sent response!\n") + } else { + log("* failed to send response: %s\n", err) + } + } + } + + return nil + }, +} + +func askUserIfWeCanRespond() bool { + answer, err := speakeasy.FAsk(os.Stderr, + fmt.Sprintf("proceed? y/n")) + if err != nil { + return false + } + if answer == "y" || answer == "yes" { + return true + } + + return askUserIfWeCanRespond() +}