mirror of
https://github.com/fiatjaf/nak.git
synced 2024-10-30 00:59:07 -04:00
use keyer.Keyer in most places instead of raw bunkers and plaintext keys, simplifies the code a little at the cost of some abstraction but I think it's strictly good this time.
This commit is contained in:
parent
2b5f3355bc
commit
dae7eba8ca
|
@ -1,7 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/nbd-wtf/go-nostr/sdk"
|
|
||||||
)
|
|
||||||
|
|
||||||
var sys = sdk.NewSystem()
|
|
64
event.go
64
event.go
|
@ -37,29 +37,7 @@ example:
|
||||||
echo '{"id":"a889df6a387419ff204305f4c2d296ee328c3cd4f8b62f205648a541b4554dfb","pubkey":"c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5","created_at":1698623783,"kind":1,"tags":[],"content":"hello from the nostr army knife","sig":"84876e1ee3e726da84e5d195eb79358b2b3eaa4d9bd38456fde3e8a2af3f1cd4cda23f23fda454869975b3688797d4c66e12f4c51c1b43c6d2997c5e61865661"}' | nak event wss://offchain.pub
|
echo '{"id":"a889df6a387419ff204305f4c2d296ee328c3cd4f8b62f205648a541b4554dfb","pubkey":"c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5","created_at":1698623783,"kind":1,"tags":[],"content":"hello from the nostr army knife","sig":"84876e1ee3e726da84e5d195eb79358b2b3eaa4d9bd38456fde3e8a2af3f1cd4cda23f23fda454869975b3688797d4c66e12f4c51c1b43c6d2997c5e61865661"}' | nak event wss://offchain.pub
|
||||||
echo '{"tags": [["t", "spam"]]}' | nak event -c 'this is spam'`,
|
echo '{"tags": [["t", "spam"]]}' | nak event -c 'this is spam'`,
|
||||||
DisableSliceFlagSeparator: true,
|
DisableSliceFlagSeparator: true,
|
||||||
Flags: []cli.Flag{
|
Flags: append(defaultKeyFlags,
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "sec",
|
|
||||||
Usage: "secret key to sign the event, as nsec, ncryptsec or hex",
|
|
||||||
DefaultText: "the key '1'",
|
|
||||||
Category: CATEGORY_SIGNER,
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "prompt-sec",
|
|
||||||
Usage: "prompt the user to paste a hex or nsec with which to sign the event",
|
|
||||||
Category: CATEGORY_SIGNER,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "connect",
|
|
||||||
Usage: "sign event using NIP-46, expects a bunker://... URL",
|
|
||||||
Category: CATEGORY_SIGNER,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "connect-as",
|
|
||||||
Usage: "private key to when communicating with the bunker given on --connect",
|
|
||||||
DefaultText: "a random key",
|
|
||||||
Category: CATEGORY_SIGNER,
|
|
||||||
},
|
|
||||||
// ~ these args are only for the convoluted musig2 signing process
|
// ~ these args are only for the convoluted musig2 signing process
|
||||||
// they will be generally copy-shared-pasted across some manual coordination method between participants
|
// they will be generally copy-shared-pasted across some manual coordination method between participants
|
||||||
&cli.UintFlag{
|
&cli.UintFlag{
|
||||||
|
@ -151,7 +129,7 @@ example:
|
||||||
Value: nostr.Now(),
|
Value: nostr.Now(),
|
||||||
Category: CATEGORY_EVENT_FIELDS,
|
Category: CATEGORY_EVENT_FIELDS,
|
||||||
},
|
},
|
||||||
},
|
),
|
||||||
ArgsUsage: "[relay...]",
|
ArgsUsage: "[relay...]",
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
// try to connect to the relays here
|
// try to connect to the relays here
|
||||||
|
@ -170,10 +148,11 @@ example:
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
|
kr, err := gatherKeyerFromArguments(ctx, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
sec, _, _ := gatherSecretKeyOrBunkerFromArguments(ctx, c)
|
||||||
|
|
||||||
doAuth := c.Bool("auth")
|
doAuth := c.Bool("auth")
|
||||||
|
|
||||||
|
@ -250,12 +229,7 @@ example:
|
||||||
|
|
||||||
if difficulty := c.Uint("pow"); difficulty > 0 {
|
if difficulty := c.Uint("pow"); difficulty > 0 {
|
||||||
// before doing pow we need the pubkey
|
// before doing pow we need the pubkey
|
||||||
if bunker != nil {
|
if numSigners := c.Uint("musig"); numSigners > 1 {
|
||||||
evt.PubKey, err = bunker.GetPublicKey(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't pow: failed to get public key from bunker: %w", err)
|
|
||||||
}
|
|
||||||
} else if numSigners := c.Uint("musig"); numSigners > 1 && sec != "" {
|
|
||||||
pubkeys := c.StringSlice("musig-pubkey")
|
pubkeys := c.StringSlice("musig-pubkey")
|
||||||
if int(numSigners) != len(pubkeys) {
|
if int(numSigners) != len(pubkeys) {
|
||||||
return fmt.Errorf("when doing a pow with musig we must know all signer pubkeys upfront")
|
return fmt.Errorf("when doing a pow with musig we must know all signer pubkeys upfront")
|
||||||
|
@ -265,7 +239,7 @@ example:
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
evt.PubKey, _ = nostr.GetPublicKey(sec)
|
evt.PubKey = kr.GetPublicKey(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to generate work with this difficulty -- runs forever
|
// try to generate work with this difficulty -- runs forever
|
||||||
|
@ -276,11 +250,7 @@ example:
|
||||||
}
|
}
|
||||||
|
|
||||||
if evt.Sig == "" || mustRehashAndResign {
|
if evt.Sig == "" || mustRehashAndResign {
|
||||||
if bunker != nil {
|
if numSigners := c.Uint("musig"); numSigners > 1 && sec != "" {
|
||||||
if err := bunker.SignEvent(ctx, &evt); err != nil {
|
|
||||||
return fmt.Errorf("failed to sign with bunker: %w", err)
|
|
||||||
}
|
|
||||||
} else if numSigners := c.Uint("musig"); numSigners > 1 && sec != "" {
|
|
||||||
pubkeys := c.StringSlice("musig-pubkey")
|
pubkeys := c.StringSlice("musig-pubkey")
|
||||||
secNonce := c.String("musig-nonce-secret")
|
secNonce := c.String("musig-nonce-secret")
|
||||||
pubNonces := c.StringSlice("musig-nonce")
|
pubNonces := c.StringSlice("musig-nonce")
|
||||||
|
@ -295,7 +265,7 @@ example:
|
||||||
// instructions for what to do should have been printed by the performMusig() function
|
// instructions for what to do should have been printed by the performMusig() function
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
} else if err := evt.Sign(sec); err != nil {
|
} else if err := kr.SignEvent(ctx, &evt); err != nil {
|
||||||
return fmt.Errorf("error signing with provided key: %w", err)
|
return fmt.Errorf("error signing with provided key: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -332,21 +302,9 @@ example:
|
||||||
// error publishing
|
// error publishing
|
||||||
if strings.HasPrefix(err.Error(), "msg: auth-required:") && (sec != "" || bunker != nil) && doAuth {
|
if strings.HasPrefix(err.Error(), "msg: auth-required:") && (sec != "" || bunker != nil) && doAuth {
|
||||||
// if the relay is requesting auth and we can auth, let's do it
|
// if the relay is requesting auth and we can auth, let's do it
|
||||||
var pk string
|
log("performing auth as %s... ", kr.GetPublicKey(ctx))
|
||||||
if bunker != nil {
|
if err := relay.Auth(ctx, func(authEvent *nostr.Event) error {
|
||||||
pk, err = bunker.GetPublicKey(ctx)
|
return kr.SignEvent(ctx, authEvent)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get public key from bunker: %w", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pk, _ = nostr.GetPublicKey(sec)
|
|
||||||
}
|
|
||||||
log("performing auth as %s... ", pk)
|
|
||||||
if err := relay.Auth(ctx, func(evt *nostr.Event) error {
|
|
||||||
if bunker != nil {
|
|
||||||
return bunker.SignEvent(ctx, evt)
|
|
||||||
}
|
|
||||||
return evt.Sign(sec)
|
|
||||||
}); err == nil {
|
}); err == nil {
|
||||||
// try to publish again, but this time don't try to auth again
|
// try to publish again, but this time don't try to auth again
|
||||||
doAuth = false
|
doAuth = false
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -13,7 +13,7 @@ require (
|
||||||
github.com/fiatjaf/khatru v0.7.5
|
github.com/fiatjaf/khatru v0.7.5
|
||||||
github.com/mailru/easyjson v0.7.7
|
github.com/mailru/easyjson v0.7.7
|
||||||
github.com/markusmobius/go-dateparser v1.2.3
|
github.com/markusmobius/go-dateparser v1.2.3
|
||||||
github.com/nbd-wtf/go-nostr v0.36.2
|
github.com/nbd-wtf/go-nostr v0.36.3
|
||||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
|
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -113,8 +113,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/nbd-wtf/go-nostr v0.36.2 h1:c79JA5FOsNeVFdPUqP9dAA5xRw1qYcwaweKU/U8YyhE=
|
github.com/nbd-wtf/go-nostr v0.36.3 h1:50fNFO8vQNMEIZ+6qUq0M5hlqEtA13WrtrKcz10eg9k=
|
||||||
github.com/nbd-wtf/go-nostr v0.36.2/go.mod h1:TGKGj00BmJRXvRe0LlpDN3KKbELhhPXgBwUEhzu3Oq0=
|
github.com/nbd-wtf/go-nostr v0.36.3/go.mod h1:TGKGj00BmJRXvRe0LlpDN3KKbELhhPXgBwUEhzu3Oq0=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
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.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
|
111
helpers.go
111
helpers.go
|
@ -3,7 +3,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -11,15 +10,14 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/chzyer/readline"
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/fiatjaf/cli/v3"
|
"github.com/fiatjaf/cli/v3"
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/nbd-wtf/go-nostr/nip19"
|
"github.com/nbd-wtf/go-nostr/sdk"
|
||||||
"github.com/nbd-wtf/go-nostr/nip46"
|
|
||||||
"github.com/nbd-wtf/go-nostr/nip49"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var sys = sdk.NewSystem()
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LINE_PROCESSING_ERROR = iota
|
LINE_PROCESSING_ERROR = iota
|
||||||
)
|
)
|
||||||
|
@ -179,109 +177,6 @@ func exitIfLineProcessingError(ctx context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func gatherSecretKeyOrBunkerFromArguments(ctx context.Context, c *cli.Command) (string, *nip46.BunkerClient, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if bunkerURL := c.String("connect"); bunkerURL != "" {
|
|
||||||
clientKey := c.String("connect-as")
|
|
||||||
if clientKey != "" {
|
|
||||||
clientKey = strings.Repeat("0", 64-len(clientKey)) + clientKey
|
|
||||||
} else {
|
|
||||||
clientKey = nostr.GeneratePrivateKey()
|
|
||||||
}
|
|
||||||
bunker, err := nip46.ConnectBunker(ctx, clientKey, bunkerURL, nil, func(s string) {
|
|
||||||
fmt.Fprintf(color.Error, color.CyanString("[nip46]: open the following URL: %s"), s)
|
|
||||||
})
|
|
||||||
return "", bunker, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// take private from flags, environment variable or default to 1
|
|
||||||
sec := c.String("sec")
|
|
||||||
if sec == "" {
|
|
||||||
if key, ok := os.LookupEnv("NOSTR_SECRET_KEY"); ok {
|
|
||||||
sec = key
|
|
||||||
} else {
|
|
||||||
sec = "0000000000000000000000000000000000000000000000000000000000000001"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(sec, "ncryptsec1") {
|
|
||||||
sec, err = promptDecrypt(sec)
|
|
||||||
if err != nil {
|
|
||||||
return "", 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok := nostr.IsValid32ByteHex(sec); !ok {
|
|
||||||
return "", nil, fmt.Errorf("invalid secret key")
|
|
||||||
}
|
|
||||||
|
|
||||||
return sec, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func promptDecrypt(ncryptsec string) (string, error) {
|
|
||||||
for i := 1; i < 4; i++ {
|
|
||||||
var attemptStr string
|
|
||||||
if i > 1 {
|
|
||||||
attemptStr = fmt.Sprintf(" [%d/3]", i)
|
|
||||||
}
|
|
||||||
password, err := askPassword("type the password to decrypt your secret key"+attemptStr+": ", nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
sec, err := nip49.Decrypt(ncryptsec, password)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return sec, nil
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("couldn't decrypt private key")
|
|
||||||
}
|
|
||||||
|
|
||||||
func askPassword(msg string, shouldAskAgain func(answer string) bool) (string, error) {
|
|
||||||
config := &readline.Config{
|
|
||||||
Stdout: color.Error,
|
|
||||||
Prompt: color.YellowString(msg),
|
|
||||||
InterruptPrompt: "^C",
|
|
||||||
DisableAutoSaveHistory: true,
|
|
||||||
EnableMask: true,
|
|
||||||
MaskRune: '*',
|
|
||||||
}
|
|
||||||
|
|
||||||
rl, err := readline.NewEx(config)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
answer, err := rl.Readline()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
answer = strings.TrimSpace(answer)
|
|
||||||
if shouldAskAgain != nil && shouldAskAgain(answer) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return answer, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
|
||||||
func randString(n int) string {
|
func randString(n int) string {
|
||||||
|
|
160
helpers_key.go
Normal file
160
helpers_key.go
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/chzyer/readline"
|
||||||
|
"github.com/fatih/color"
|
||||||
|
"github.com/fiatjaf/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{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "sec",
|
||||||
|
Usage: "secret key to sign the event, as nsec, ncryptsec or hex, or a bunker URL",
|
||||||
|
DefaultText: "the key '1'",
|
||||||
|
Aliases: []string{"connect"},
|
||||||
|
Category: CATEGORY_SIGNER,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "prompt-sec",
|
||||||
|
Usage: "prompt the user to paste a hex or nsec with which to sign the event",
|
||||||
|
Category: CATEGORY_SIGNER,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "connect-as",
|
||||||
|
Usage: "private key to when communicating with the bunker given on --connect",
|
||||||
|
DefaultText: "a random key",
|
||||||
|
Category: CATEGORY_SIGNER,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func gatherKeyerFromArguments(ctx context.Context, c *cli.Command) (keyer.Keyer, error) {
|
||||||
|
key, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var kr keyer.Keyer
|
||||||
|
if bunker != nil {
|
||||||
|
kr = keyer.NewBunkerSignerFromBunkerClient(bunker)
|
||||||
|
} else {
|
||||||
|
kr = keyer.NewPlainKeySigner(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return kr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func gatherSecretKeyOrBunkerFromArguments(ctx context.Context, c *cli.Command) (string, *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
|
||||||
|
} else {
|
||||||
|
clientKey = nostr.GeneratePrivateKey()
|
||||||
|
}
|
||||||
|
bunker, err := nip46.ConnectBunker(ctx, clientKey, bunkerURL, nil, func(s string) {
|
||||||
|
fmt.Fprintf(color.Error, color.CyanString("[nip46]: open the following URL: %s"), s)
|
||||||
|
})
|
||||||
|
return "", bunker, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// take private from flags, environment variable or default to 1
|
||||||
|
if sec == "" {
|
||||||
|
if key, ok := os.LookupEnv("NOSTR_SECRET_KEY"); ok {
|
||||||
|
sec = key
|
||||||
|
} else {
|
||||||
|
sec = "0000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(sec, "ncryptsec1") {
|
||||||
|
sec, err = promptDecrypt(sec)
|
||||||
|
if err != nil {
|
||||||
|
return "", 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok := nostr.IsValid32ByteHex(sec); !ok {
|
||||||
|
return "", nil, fmt.Errorf("invalid secret key")
|
||||||
|
}
|
||||||
|
|
||||||
|
return sec, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func promptDecrypt(ncryptsec string) (string, error) {
|
||||||
|
for i := 1; i < 4; i++ {
|
||||||
|
var attemptStr string
|
||||||
|
if i > 1 {
|
||||||
|
attemptStr = fmt.Sprintf(" [%d/3]", i)
|
||||||
|
}
|
||||||
|
password, err := askPassword("type the password to decrypt your secret key"+attemptStr+": ", nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
sec, err := nip49.Decrypt(ncryptsec, password)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return sec, nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("couldn't decrypt private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
func askPassword(msg string, shouldAskAgain func(answer string) bool) (string, error) {
|
||||||
|
config := &readline.Config{
|
||||||
|
Stdout: color.Error,
|
||||||
|
Prompt: color.YellowString(msg),
|
||||||
|
InterruptPrompt: "^C",
|
||||||
|
DisableAutoSaveHistory: true,
|
||||||
|
EnableMask: true,
|
||||||
|
MaskRune: '*',
|
||||||
|
}
|
||||||
|
|
||||||
|
rl, err := readline.NewEx(config)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
answer, err := rl.Readline()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
answer = strings.TrimSpace(answer)
|
||||||
|
if shouldAskAgain != nil && shouldAskAgain(answer) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return answer, err
|
||||||
|
}
|
||||||
|
}
|
35
relay.go
35
relay.go
|
@ -74,26 +74,7 @@ var relay = &cli.Command{
|
||||||
flags[i] = declareFlag(argName)
|
flags[i] = declareFlag(argName)
|
||||||
}
|
}
|
||||||
|
|
||||||
flags = append(flags,
|
flags = append(flags, defaultKeyFlags...)
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "sec",
|
|
||||||
Usage: "secret key to sign the event, as nsec, ncryptsec or hex",
|
|
||||||
DefaultText: "the key '1'",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "prompt-sec",
|
|
||||||
Usage: "prompt the user to paste a hex or nsec with which to sign the event",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "connect",
|
|
||||||
Usage: "sign event using NIP-46, expects a bunker://... URL",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "connect-as",
|
|
||||||
Usage: "private key to when communicating with the bunker given on --connect",
|
|
||||||
DefaultText: "a random key",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
cmd := &cli.Command{
|
cmd := &cli.Command{
|
||||||
Name: def.method,
|
Name: def.method,
|
||||||
|
@ -114,7 +95,7 @@ var relay = &cli.Command{
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
|
kr, err := gatherKeyerFromArguments(ctx, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -131,7 +112,7 @@ var relay = &cli.Command{
|
||||||
|
|
||||||
// Authorization
|
// Authorization
|
||||||
payloadHash := sha256.Sum256(reqj)
|
payloadHash := sha256.Sum256(reqj)
|
||||||
authEvent := nostr.Event{
|
tokenEvent := nostr.Event{
|
||||||
Kind: 27235,
|
Kind: 27235,
|
||||||
CreatedAt: nostr.Now(),
|
CreatedAt: nostr.Now(),
|
||||||
Tags: nostr.Tags{
|
Tags: nostr.Tags{
|
||||||
|
@ -140,14 +121,10 @@ var relay = &cli.Command{
|
||||||
{"payload", hex.EncodeToString(payloadHash[:])},
|
{"payload", hex.EncodeToString(payloadHash[:])},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if bunker != nil {
|
if err := kr.SignEvent(ctx, &tokenEvent); err != nil {
|
||||||
if err := bunker.SignEvent(ctx, &authEvent); err != nil {
|
return fmt.Errorf("failed to sign token event: %w", err)
|
||||||
return fmt.Errorf("failed to sign with bunker: %w", err)
|
|
||||||
}
|
|
||||||
} else if err := authEvent.Sign(sec); err != nil {
|
|
||||||
return fmt.Errorf("error signing with provided key: %w", err)
|
|
||||||
}
|
}
|
||||||
evtj, _ := json.Marshal(authEvent)
|
evtj, _ := json.Marshal(tokenEvent)
|
||||||
req.Header.Set("Authorization", "Nostr "+base64.StdEncoding.EncodeToString(evtj))
|
req.Header.Set("Authorization", "Nostr "+base64.StdEncoding.EncodeToString(evtj))
|
||||||
|
|
||||||
// Content-Type
|
// Content-Type
|
||||||
|
|
134
req.go
134
req.go
|
@ -31,93 +31,67 @@ it can also take a filter from stdin, optionally modify it with flags and send i
|
||||||
example:
|
example:
|
||||||
echo '{"kinds": [1], "#t": ["test"]}' | nak req -l 5 -k 4549 --tag t=spam wss://nostr-pub.wellorder.net`,
|
echo '{"kinds": [1], "#t": ["test"]}' | nak req -l 5 -k 4549 --tag t=spam wss://nostr-pub.wellorder.net`,
|
||||||
DisableSliceFlagSeparator: true,
|
DisableSliceFlagSeparator: true,
|
||||||
Flags: append(reqFilterFlags,
|
Flags: append(defaultKeyFlags,
|
||||||
&cli.BoolFlag{
|
append(reqFilterFlags,
|
||||||
Name: "stream",
|
&cli.BoolFlag{
|
||||||
Usage: "keep the subscription open, printing all events as they are returned",
|
Name: "stream",
|
||||||
DefaultText: "false, will close on EOSE",
|
Usage: "keep the subscription open, printing all events as they are returned",
|
||||||
},
|
DefaultText: "false, will close on EOSE",
|
||||||
&cli.BoolFlag{
|
},
|
||||||
Name: "paginate",
|
&cli.BoolFlag{
|
||||||
Usage: "make multiple REQs to the relay decreasing the value of 'until' until 'limit' or 'since' conditions are met",
|
Name: "paginate",
|
||||||
DefaultText: "false",
|
Usage: "make multiple REQs to the relay decreasing the value of 'until' until 'limit' or 'since' conditions are met",
|
||||||
},
|
DefaultText: "false",
|
||||||
&cli.DurationFlag{
|
},
|
||||||
Name: "paginate-interval",
|
&cli.DurationFlag{
|
||||||
Usage: "time between queries when using --paginate",
|
Name: "paginate-interval",
|
||||||
},
|
Usage: "time between queries when using --paginate",
|
||||||
&cli.UintFlag{
|
},
|
||||||
Name: "paginate-global-limit",
|
&cli.UintFlag{
|
||||||
Usage: "global limit at which --paginate should stop",
|
Name: "paginate-global-limit",
|
||||||
DefaultText: "uses the value given by --limit/-l or infinite",
|
Usage: "global limit at which --paginate should stop",
|
||||||
},
|
DefaultText: "uses the value given by --limit/-l or infinite",
|
||||||
&cli.BoolFlag{
|
},
|
||||||
Name: "bare",
|
&cli.BoolFlag{
|
||||||
Usage: "when printing the filter, print just the filter, not enveloped in a [\"REQ\", ...] array",
|
Name: "bare",
|
||||||
},
|
Usage: "when printing the filter, print just the filter, not enveloped in a [\"REQ\", ...] array",
|
||||||
&cli.BoolFlag{
|
},
|
||||||
Name: "auth",
|
&cli.BoolFlag{
|
||||||
Usage: "always perform NIP-42 \"AUTH\" when facing an \"auth-required: \" rejection and try again",
|
Name: "auth",
|
||||||
},
|
Usage: "always perform NIP-42 \"AUTH\" when facing an \"auth-required: \" rejection and try again",
|
||||||
&cli.BoolFlag{
|
},
|
||||||
Name: "force-pre-auth",
|
&cli.BoolFlag{
|
||||||
Aliases: []string{"fpa"},
|
Name: "force-pre-auth",
|
||||||
Usage: "after connecting, for a NIP-42 \"AUTH\" message to be received, act on it and only then send the \"REQ\"",
|
Aliases: []string{"fpa"},
|
||||||
Category: CATEGORY_SIGNER,
|
Usage: "after connecting, for a NIP-42 \"AUTH\" message to be received, act on it and only then send the \"REQ\"",
|
||||||
},
|
Category: CATEGORY_SIGNER,
|
||||||
&cli.StringFlag{
|
},
|
||||||
Name: "sec",
|
)...,
|
||||||
Usage: "secret key to sign the AUTH challenge, as hex or nsec",
|
|
||||||
DefaultText: "the key '1'",
|
|
||||||
Category: CATEGORY_SIGNER,
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "prompt-sec",
|
|
||||||
Usage: "prompt the user to paste a hex or nsec with which to sign the AUTH challenge",
|
|
||||||
Category: CATEGORY_SIGNER,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "connect",
|
|
||||||
Usage: "sign AUTH using NIP-46, expects a bunker://... URL",
|
|
||||||
Category: CATEGORY_SIGNER,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "connect-as",
|
|
||||||
Usage: "private key to when communicating with the bunker given on --connect",
|
|
||||||
DefaultText: "a random key",
|
|
||||||
Category: CATEGORY_SIGNER,
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
ArgsUsage: "[relay...]",
|
ArgsUsage: "[relay...]",
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
relayUrls := c.Args().Slice()
|
relayUrls := c.Args().Slice()
|
||||||
if len(relayUrls) > 0 {
|
if len(relayUrls) > 0 {
|
||||||
relays := connectToAllRelays(ctx, relayUrls, c.Bool("force-pre-auth"), nostr.WithAuthHandler(func(evt *nostr.Event) error {
|
relays := connectToAllRelays(ctx,
|
||||||
if !c.Bool("auth") && !c.Bool("force-pre-auth") {
|
relayUrls,
|
||||||
return fmt.Errorf("auth not authorized")
|
c.Bool("force-pre-auth"),
|
||||||
}
|
nostr.WithAuthHandler(
|
||||||
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
|
func(authEvent *nostr.Event) error {
|
||||||
if err != nil {
|
if !c.Bool("auth") && !c.Bool("force-pre-auth") {
|
||||||
return err
|
return fmt.Errorf("auth not authorized")
|
||||||
}
|
}
|
||||||
|
kr, err := gatherKeyerFromArguments(ctx, c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var pk string
|
pk := kr.GetPublicKey(ctx)
|
||||||
if bunker != nil {
|
log("performing auth as %s... ", pk)
|
||||||
pk, err = bunker.GetPublicKey(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get public key from bunker: %w", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pk, _ = nostr.GetPublicKey(sec)
|
|
||||||
}
|
|
||||||
log("performing auth as %s... ", pk)
|
|
||||||
|
|
||||||
if bunker != nil {
|
return kr.SignEvent(ctx, authEvent)
|
||||||
return bunker.SignEvent(ctx, evt)
|
},
|
||||||
} else {
|
),
|
||||||
return evt.Sign(sec)
|
)
|
||||||
}
|
|
||||||
}))
|
|
||||||
if len(relays) == 0 {
|
if len(relays) == 0 {
|
||||||
log("failed to connect to any of the given relays.\n")
|
log("failed to connect to any of the given relays.\n")
|
||||||
os.Exit(3)
|
os.Exit(3)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user