Compare commits

...

6 Commits

Author SHA1 Message Date
fiatjaf
b7a7e0504f --connect to use nip46 as a client to sign event and auth messages. 2024-02-06 00:58:26 -03:00
mattn
01e1f52a70 fix panic (#12)
* c.Args().Len() - 1 might be negative value

* fix getStdinLinesOrFirstArgument

* fix getStdinLinesOrFirstArgument
2024-02-03 10:03:32 -03:00
fiatjaf
0b9e861f90 fix: print prompt to stderr. 2024-02-02 14:13:11 -03:00
fiatjaf
bda18e035a fix reading hex secret key from input. 2024-02-02 14:11:58 -03:00
fiatjaf
0d46d48881 fix naddr. 2024-01-29 15:37:21 -03:00
fiatjaf
6f24112b5e support ncryptsec in all operations that require a private key and have a nice password prompt. 2024-01-29 10:56:58 -03:00
11 changed files with 188 additions and 55 deletions

View File

@@ -84,5 +84,4 @@ nak req -l 100 -k 1 -a 2edbcea694d164629854a52583458fd6d965b161e3c48b57d3aff0194
## Contributing to this repository ## Contributing to this repository
Use NIP-34 to send your patches to `naddr1qqpkucttqy28wumn8ghj7un9d3shjtnyv9kh2uewd9hsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvsq3vamnwvaz7tmjv4kxz7fwwpexjmtpdshxuet5qgsg04q5ypr6f4n65mv7e5hs05z50hy7vvgua8uc8szwtp262cfwn6srqsqqqauedy5x7y`. Use NIP-34 to send your patches to `naddr1qqpkucttqy28wumn8ghj7un9d3shjtnwdaehgu3wvfnsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvsq3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7q3q80cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsxpqqqpmej2wctpn`.

View File

@@ -56,7 +56,7 @@ var bunker = &cli.Command{
} }
// gather the secret key // gather the secret key
sec, err := gatherSecretKeyFromArguments(c) sec, _, err := gatherSecretKeyOrBunkerFromArguments(c)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -33,7 +33,7 @@ var decode = &cli.Command{
}, },
ArgsUsage: "<npub | nprofile | nip05 | nevent | naddr | nsec>", ArgsUsage: "<npub | nprofile | nip05 | nevent | naddr | nsec>",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
for input := range getStdinLinesOrFirstArgument(c) { for input := range getStdinLinesOrFirstArgument(c.Args().First()) {
if strings.HasPrefix(input, "nostr:") { if strings.HasPrefix(input, "nostr:") {
input = input[6:] input = input[6:]
} }

View File

@@ -29,7 +29,7 @@ var encode = &cli.Command{
Name: "npub", Name: "npub",
Usage: "encode a hex public key into bech32 'npub' format", Usage: "encode a hex public key into bech32 'npub' format",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
for target := range getStdinLinesOrFirstArgument(c) { for target := range getStdinLinesOrFirstArgument(c.Args().First()) {
if ok := nostr.IsValidPublicKey(target); !ok { if ok := nostr.IsValidPublicKey(target); !ok {
lineProcessingError(c, "invalid public key: %s", target) lineProcessingError(c, "invalid public key: %s", target)
continue continue
@@ -50,7 +50,7 @@ var encode = &cli.Command{
Name: "nsec", Name: "nsec",
Usage: "encode a hex private key into bech32 'nsec' format", Usage: "encode a hex private key into bech32 'nsec' format",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
for target := range getStdinLinesOrFirstArgument(c) { for target := range getStdinLinesOrFirstArgument(c.Args().First()) {
if ok := nostr.IsValid32ByteHex(target); !ok { if ok := nostr.IsValid32ByteHex(target); !ok {
lineProcessingError(c, "invalid private key: %s", target) lineProcessingError(c, "invalid private key: %s", target)
continue continue
@@ -78,7 +78,7 @@ var encode = &cli.Command{
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
for target := range getStdinLinesOrFirstArgument(c) { for target := range getStdinLinesOrFirstArgument(c.Args().First()) {
if ok := nostr.IsValid32ByteHex(target); !ok { if ok := nostr.IsValid32ByteHex(target); !ok {
lineProcessingError(c, "invalid public key: %s", target) lineProcessingError(c, "invalid public key: %s", target)
continue continue
@@ -115,7 +115,7 @@ var encode = &cli.Command{
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
for target := range getStdinLinesOrFirstArgument(c) { for target := range getStdinLinesOrFirstArgument(c.Args().First()) {
if ok := nostr.IsValid32ByteHex(target); !ok { if ok := nostr.IsValid32ByteHex(target); !ok {
lineProcessingError(c, "invalid event id: %s", target) lineProcessingError(c, "invalid event id: %s", target)
continue continue
@@ -212,7 +212,7 @@ var encode = &cli.Command{
Name: "note", Name: "note",
Usage: "generate note1 event codes (not recommended)", Usage: "generate note1 event codes (not recommended)",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
for target := range getStdinLinesOrFirstArgument(c) { for target := range getStdinLinesOrFirstArgument(c.Args().First()) {
if ok := nostr.IsValid32ByteHex(target); !ok { if ok := nostr.IsValid32ByteHex(target); !ok {
lineProcessingError(c, "invalid event id: %s", target) lineProcessingError(c, "invalid event id: %s", target)
continue continue

View File

@@ -44,6 +44,10 @@ example:
Name: "prompt-sec", Name: "prompt-sec",
Usage: "prompt the user to paste a hex or nsec with which to sign the event", 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.BoolFlag{ &cli.BoolFlag{
Name: "envelope", Name: "envelope",
Usage: "print the event enveloped in a [\"EVENT\", ...] message ready to be sent to a relay", Usage: "print the event enveloped in a [\"EVENT\", ...] message ready to be sent to a relay",
@@ -124,8 +128,7 @@ example:
} }
}() }()
// gather the secret key sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(c)
sec, err := gatherSecretKeyFromArguments(c)
if err != nil { if err != nil {
return err return err
} }
@@ -215,7 +218,11 @@ example:
} }
if evt.Sig == "" || mustRehashAndResign { if evt.Sig == "" || mustRehashAndResign {
if err := evt.Sign(sec); err != nil { if bunker != nil {
if err := bunker.SignEvent(c.Context, &evt); err != nil {
return fmt.Errorf("failed to sign with bunker: %w", err)
}
} else if err := evt.Sign(sec); err != nil {
return fmt.Errorf("error signing with provided key: %w", err) return fmt.Errorf("error signing with provided key: %w", err)
} }
} }
@@ -252,11 +259,24 @@ example:
} }
// error publishing // error publishing
if strings.HasPrefix(err.Error(), "msg: auth-required:") && sec != "" && 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
pk, _ := nostr.GetPublicKey(sec) var pk string
if bunker != nil {
pk, err = bunker.GetPublicKey(c.Context)
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) log("performing auth as %s... ", pk)
if err := relay.Auth(c.Context, func(evt *nostr.Event) error { return evt.Sign(sec) }); err == nil { if err := relay.Auth(c.Context, func(evt *nostr.Event) error {
if bunker != nil {
return bunker.SignEvent(c.Context, evt)
}
return evt.Sign(sec)
}); 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
goto publish goto publish

View File

@@ -31,7 +31,7 @@ var fetch = &cli.Command{
}) })
}() }()
for code := range getStdinLinesOrFirstArgument(c) { for code := range getStdinLinesOrFirstArgument(c.Args().First()) {
filter := nostr.Filter{} filter := nostr.Filter{}
prefix, value, err := nip19.Decode(code) prefix, value, err := nip19.Decode(code)

8
go.mod
View File

@@ -5,10 +5,11 @@ go 1.21
toolchain go1.21.0 toolchain go1.21.0
require ( require (
github.com/bgentry/speakeasy v0.1.0 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
github.com/fatih/color v1.16.0
github.com/mailru/easyjson v0.7.7 github.com/mailru/easyjson v0.7.7
github.com/manifoldco/promptui v0.9.0 github.com/manifoldco/promptui v0.9.0
github.com/nbd-wtf/go-nostr v0.28.2 github.com/nbd-wtf/go-nostr v0.28.4
github.com/nbd-wtf/nostr-sdk v0.0.5 github.com/nbd-wtf/nostr-sdk v0.0.5
github.com/urfave/cli/v2 v2.25.7 github.com/urfave/cli/v2 v2.25.7
golang.org/x/exp v0.0.0-20231006140011-7918f672742d golang.org/x/exp v0.0.0-20231006140011-7918f672742d
@@ -18,7 +19,6 @@ require (
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
github.com/btcsuite/btcd/btcutil v1.1.3 // indirect github.com/btcsuite/btcd/btcutil v1.1.3 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
@@ -27,6 +27,8 @@ require (
github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.3.1 // indirect github.com/gobwas/ws v1.3.1 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/puzpuzpuz/xsync/v3 v3.0.2 // indirect github.com/puzpuzpuz/xsync/v3 v3.0.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/tidwall/gjson v1.17.0 // indirect github.com/tidwall/gjson v1.17.0 // indirect

14
go.sum
View File

@@ -1,6 +1,4 @@
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
@@ -44,6 +42,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeC
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
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.2.16 h1:NR64mnyUT5nJR8Sj2AwJTd1Hqs5kKJcCFO21ggUkvWg= github.com/fiatjaf/eventstore v0.2.16 h1:NR64mnyUT5nJR8Sj2AwJTd1Hqs5kKJcCFO21ggUkvWg=
github.com/fiatjaf/eventstore v0.2.16/go.mod h1:rUc1KhVufVmC+HUOiuPweGAcvG6lEOQCkRCn2Xn5VRA= github.com/fiatjaf/eventstore v0.2.16/go.mod h1:rUc1KhVufVmC+HUOiuPweGAcvG6lEOQCkRCn2Xn5VRA=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -76,8 +76,13 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/nbd-wtf/go-nostr v0.28.2 h1:KhpGcs6KMLBqYExzKoqt7vP5Re2f8Kpy9SavYZa2PTI= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/nbd-wtf/go-nostr v0.28.2/go.mod h1:l9NRRaHPN+QwkqrjNKhnfYjQ0+nKP1xZrVxePPGUs+A= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
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/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/nbd-wtf/go-nostr v0.28.4 h1:chGBpdCQvM9aInf/vVDishY8GHapgqFc/RLl2WDnHQM=
github.com/nbd-wtf/go-nostr v0.28.4/go.mod h1:l9NRRaHPN+QwkqrjNKhnfYjQ0+nKP1xZrVxePPGUs+A=
github.com/nbd-wtf/nostr-sdk v0.0.5 h1:rec+FcDizDVO0W25PX0lgYMXvP7zNNOgI3Fu9UCm4BY= github.com/nbd-wtf/nostr-sdk v0.0.5 h1:rec+FcDizDVO0W25PX0lgYMXvP7zNNOgI3Fu9UCm4BY=
github.com/nbd-wtf/nostr-sdk v0.0.5/go.mod h1:iJJsikesCGLNFZ9dLqhLPDzdt924EagUmdQxT3w2Lmk= github.com/nbd-wtf/nostr-sdk v0.0.5/go.mod h1:iJJsikesCGLNFZ9dLqhLPDzdt924EagUmdQxT3w2Lmk=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
@@ -133,6 +138,7 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

View File

@@ -3,14 +3,18 @@ package main
import ( import (
"bufio" "bufio"
"context" "context"
"encoding/hex"
"fmt" "fmt"
"net/url" "net/url"
"os" "os"
"strings" "strings"
"github.com/bgentry/speakeasy" "github.com/chzyer/readline"
"github.com/fatih/color"
"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/nip19"
"github.com/nbd-wtf/go-nostr/nip46"
"github.com/nbd-wtf/go-nostr/nip49"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@@ -44,12 +48,11 @@ func getStdinLinesOrBlank() chan string {
} }
} }
func getStdinLinesOrFirstArgument(c *cli.Context) chan string { func getStdinLinesOrFirstArgument(arg string) chan string {
// try the first argument // try the first argument
target := c.Args().First() if arg != "" {
if target != "" {
single := make(chan string, 1) single := make(chan string, 1)
single <- target single <- arg
close(single) close(single)
return single return single
} }
@@ -127,32 +130,99 @@ func exitIfLineProcessingError(c *cli.Context) {
} }
} }
func gatherSecretKeyFromArguments(c *cli.Context) (string, error) { func gatherSecretKeyOrBunkerFromArguments(c *cli.Context) (string, *nip46.BunkerClient, error) {
var err error
if bunkerURL := c.String("connect"); bunkerURL != "" {
clientKey := nostr.GeneratePrivateKey()
bunker, err := nip46.ConnectBunker(c.Context, clientKey, bunkerURL, nil)
return "", bunker, err
}
sec := c.String("sec") sec := c.String("sec")
if c.Bool("prompt-sec") { if c.Bool("prompt-sec") {
if isPiped() { if isPiped() {
return "", fmt.Errorf("can't prompt for a secret key when processing data from a pipe, try again without --prompt-sec") return "", nil, 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 = askPassword("type your secret key as ncryptsec, nsec or hex: ", nil)
sec, err = speakeasy.FAsk(os.Stderr, "type your secret key as nsec or hex: ")
if err != nil { if err != nil {
return "", fmt.Errorf("failed to get secret key: %w", err) return "", nil, fmt.Errorf("failed to get secret key: %w", err)
} }
} }
if strings.HasPrefix(sec, "nsec1") { if strings.HasPrefix(sec, "ncryptsec1") {
_, hex, err := nip19.Decode(sec) sec, err = promptDecrypt(sec)
if err != nil { if err != nil {
return "", fmt.Errorf("invalid nsec: %w", err) return "", nil, fmt.Errorf("failed to decrypt: %w", err)
} }
sec = hex.(string) } else if bsec, err := hex.DecodeString(strings.Repeat("0", 64-len(sec)) + sec); err == nil {
} sec = hex.EncodeToString(bsec)
if len(sec) > 64 { } else if prefix, hexvalue, err := nip19.Decode(sec); err != nil {
return "", fmt.Errorf("invalid secret key: too large") return "", nil, fmt.Errorf("invalid nsec: %w", err)
} } else if prefix == "nsec" {
sec = strings.Repeat("0", 64-len(sec)) + sec // left-pad sec = hexvalue.(string)
if ok := nostr.IsValid32ByteHex(sec); !ok {
return "", fmt.Errorf("invalid secret key")
} }
return sec, nil if ok := nostr.IsValid32ByteHex(sec); !ok {
return "", nil, fmt.Errorf("invalid secret key")
}
return sec, nil, nil
}
func promptDecrypt(ncryptsec1 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(ncryptsec1, password)
if err != nil {
continue
}
return sec, nil
}
return "", fmt.Errorf("couldn't decrypt private key")
}
func ask(msg string, defaultValue string, shouldAskAgain func(answer string) bool) (string, error) {
return _ask(&readline.Config{
Stdout: os.Stderr,
Prompt: color.YellowString(msg),
InterruptPrompt: "^C",
DisableAutoSaveHistory: true,
}, msg, defaultValue, shouldAskAgain)
}
func askPassword(msg string, shouldAskAgain func(answer string) bool) (string, error) {
config := &readline.Config{
Stdout: os.Stderr,
Prompt: color.YellowString(msg),
InterruptPrompt: "^C",
DisableAutoSaveHistory: true,
EnableMask: true,
MaskRune: '*',
}
return _ask(config, msg, "", shouldAskAgain)
}
func _ask(config *readline.Config, msg string, defaultValue string, shouldAskAgain func(answer string) bool) (string, error) {
rl, err := readline.NewEx(config)
if err != nil {
return "", err
}
rl.WriteStdin([]byte(defaultValue))
for {
answer, err := rl.Readline()
if err != nil {
return "", err
}
answer = strings.TrimSpace(strings.ToLower(answer))
if shouldAskAgain != nil && shouldAskAgain(answer) {
continue
}
return answer, err
}
} }

32
key.go
View File

@@ -39,7 +39,7 @@ var public = &cli.Command{
Description: ``, Description: ``,
ArgsUsage: "[secret]", ArgsUsage: "[secret]",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
for sec := range getSecretKeyFromStdinLinesOrFirstArgument(c) { for sec := range getSecretKeyFromStdinLinesOrFirstArgument(c, c.Args().First()) {
pubkey, err := nostr.GetPublicKey(sec) pubkey, err := nostr.GetPublicKey(sec)
if err != nil { if err != nil {
lineProcessingError(c, "failed to derive public key: %s", err) lineProcessingError(c, "failed to derive public key: %s", err)
@@ -65,11 +65,20 @@ var encrypt = &cli.Command{
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
password := c.Args().Get(c.Args().Len() - 1) var content string
var password string
switch c.Args().Len() {
case 1:
content = ""
password = c.Args().Get(0)
case 2:
content = c.Args().Get(0)
password = c.Args().Get(1)
}
if password == "" { if password == "" {
return fmt.Errorf("no password given") return fmt.Errorf("no password given")
} }
for sec := range getSecretKeyFromStdinLinesOrFirstArgument(c) { for sec := range getSecretKeyFromStdinLinesOrFirstArgument(c, content) {
ncryptsec, err := nip49.Encrypt(sec, password, uint8(c.Int("logn")), 0x02) ncryptsec, err := nip49.Encrypt(sec, password, uint8(c.Int("logn")), 0x02)
if err != nil { if err != nil {
lineProcessingError(c, "failed to encrypt: %s", err) lineProcessingError(c, "failed to encrypt: %s", err)
@@ -87,11 +96,20 @@ var decrypt = &cli.Command{
Description: `uses the NIP-49 standard.`, Description: `uses the NIP-49 standard.`,
ArgsUsage: "<ncryptsec-code> <password>", ArgsUsage: "<ncryptsec-code> <password>",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
password := c.Args().Get(c.Args().Len() - 1) var content string
var password string
switch c.Args().Len() {
case 1:
content = ""
password = c.Args().Get(0)
case 2:
content = c.Args().Get(0)
password = c.Args().Get(1)
}
if password == "" { if password == "" {
return fmt.Errorf("no password given") return fmt.Errorf("no password given")
} }
for ncryptsec := range getStdinLinesOrFirstArgument(c) { for ncryptsec := range getStdinLinesOrFirstArgument(content) {
sec, err := nip49.Decrypt(ncryptsec, password) sec, err := nip49.Decrypt(ncryptsec, password)
if err != nil { if err != nil {
lineProcessingError(c, "failed to decrypt: %s", err) lineProcessingError(c, "failed to decrypt: %s", err)
@@ -104,10 +122,10 @@ var decrypt = &cli.Command{
}, },
} }
func getSecretKeyFromStdinLinesOrFirstArgument(c *cli.Context) chan string { func getSecretKeyFromStdinLinesOrFirstArgument(c *cli.Context, content string) chan string {
ch := make(chan string) ch := make(chan string)
go func() { go func() {
for sec := range getStdinLinesOrFirstArgument(c) { for sec := range getStdinLinesOrFirstArgument(content) {
if sec == "" { if sec == "" {
continue continue
} }

22
req.go
View File

@@ -113,6 +113,10 @@ example:
Name: "prompt-sec", Name: "prompt-sec",
Usage: "prompt the user to paste a hex or nsec with which to sign the AUTH challenge", Usage: "prompt the user to paste a hex or nsec with which to sign the AUTH challenge",
}, },
&cli.StringFlag{
Name: "connect",
Usage: "sign AUTH using NIP-46, expects a bunker://... URL",
},
}, },
ArgsUsage: "[relay...]", ArgsUsage: "[relay...]",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
@@ -124,13 +128,27 @@ example:
if !c.Bool("auth") { if !c.Bool("auth") {
return fmt.Errorf("auth not authorized") return fmt.Errorf("auth not authorized")
} }
sec, err := gatherSecretKeyFromArguments(c) sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(c)
if err != nil { if err != nil {
return err return err
} }
pk, _ := nostr.GetPublicKey(sec)
var pk string
if bunker != nil {
pk, err = bunker.GetPublicKey(c.Context)
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...\n", pk) log("performing auth as %s...\n", pk)
if bunker != nil {
return bunker.SignEvent(c.Context, evt)
} else {
return evt.Sign(sec) 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")