Compare commits

..

14 Commits

Author SHA1 Message Date
fiatjaf
ea53eca74f update go-nostr for nip44-on-nip46 fixes. 2024-10-27 09:56:49 -03:00
fiatjaf
38ed370c59 slightly improve verify error message. 2024-10-11 17:59:08 -03:00
fiatjaf
5b04bc4859 nak key public --with-parity 2024-10-08 09:08:50 -03:00
fiatjaf
2988c71ccb nak/b and nak/s user-agents. 2024-09-26 22:17:31 -03:00
fiatjaf
d7c0ff2bb7 update go-nostr keyer interface and make req --auth work again. 2024-09-22 19:21:41 -03:00
fiatjaf
43fe41df5d use log() function instead of fmt.Fprintf(os.Stderr) in some places. 2024-09-22 19:04:21 -03:00
fiatjaf
3215726417 use stdout() function instead of fmt.Println() in some places. 2024-09-21 12:02:09 -03:00
fiatjaf
a4886dc445 nak encrypt and nak decrypt: nip44 with option to do nip04.
closes https://github.com/fiatjaf/nak/issues/36
2024-09-17 11:33:02 -03:00
fiatjaf
dae7eba8ca 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. 2024-09-17 11:33:02 -03:00
fiatjaf
2b5f3355bc use a single global sdk.System and its Pool. 2024-09-17 11:33:02 -03:00
fiatjaf
bd5ca27661 github.ref->github.ref_name as version variable. 2024-09-15 09:04:52 -03:00
fiatjaf
9d02301b2d support --version using -X 2024-09-15 08:57:53 -03:00
arkinox
9bbc87b27a specify how ; can separate multiple tag values 2024-09-10 19:13:25 -03:00
fiatjaf
88a07a3504 update go-nostr and nostr-sdk to fix bad nevent/naddr parsing bug. 2024-09-05 14:43:34 -03:00
17 changed files with 489 additions and 368 deletions

View File

@@ -40,6 +40,7 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }}
ldflags: -X main.version=${{ github.ref_name }}
overwrite: true
md5sum: false
sha256sum: false

View File

@@ -50,7 +50,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, relayUrls, false)
relays := connectToAllRelays(ctx, relayUrls, false)
if len(relays) == 0 {
log("failed to connect to any of the given relays.\n")
os.Exit(3)
@@ -143,9 +143,8 @@ var bunker = &cli.Command{
printBunkerInfo()
// subscribe to relays
pool := nostr.NewSimplePool(ctx)
now := nostr.Now()
events := pool.SubMany(ctx, relayURLs, nostr.Filters{
events := sys.Pool.SubMany(ctx, relayURLs, nostr.Filters{
{
Kinds: []int{nostr.KindNostrConnect},
Tags: nostr.TagMap{"p": []string{pubkey}},
@@ -184,7 +183,7 @@ var bunker = &cli.Command{
cancelPreviousBunkerInfoPrint() // this prevents us from printing a million bunker info blocks
// handle the NIP-46 request event
req, resp, eventResponse, err := signer.HandleRequest(ie.Event)
req, resp, eventResponse, err := signer.HandleRequest(ctx, ie.Event)
if err != nil {
log("< failed to handle request from %s: %s\n", ie.Event.PubKey, err.Error())
continue
@@ -198,7 +197,7 @@ var bunker = &cli.Command{
handlerWg.Add(len(relayURLs))
for _, relayURL := range relayURLs {
go func(relayURL string) {
if relay, _ := pool.EnsureRelay(relayURL); relay != nil {
if relay, _ := sys.Pool.EnsureRelay(relayURL); relay != nil {
err := relay.Publish(ctx, eventResponse)
printLock.Lock()
if err == nil {
@@ -223,7 +222,7 @@ var bunker = &cli.Command{
select {
case <-ctx.Done():
case <-time.After(time.Minute * 5):
fmt.Fprintf(os.Stderr, "\n")
log("\n")
printBunkerInfo()
}
}()

View File

@@ -9,7 +9,7 @@ import (
"github.com/fiatjaf/cli/v3"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
sdk "github.com/nbd-wtf/nostr-sdk"
"github.com/nbd-wtf/go-nostr/sdk"
)
var decode = &cli.Command{
@@ -59,8 +59,11 @@ var decode = &cli.Command{
} else if pp := sdk.InputToProfile(ctx, input); pp != nil {
decodeResult = DecodeResult{ProfilePointer: pp}
} else if prefix, value, err := nip19.Decode(input); err == nil && prefix == "naddr" {
ep := value.(nostr.EntityPointer)
decodeResult = DecodeResult{EntityPointer: &ep}
if ep, ok := value.(nostr.EntityPointer); ok {
decodeResult = DecodeResult{EntityPointer: &ep}
} else {
ctx = lineProcessingError(ctx, "couldn't decode naddr: %s", err)
}
} else if prefix, value, err := nip19.Decode(input); err == nil && prefix == "nsec" {
decodeResult.PrivateKey.PrivateKey = value.(string)
decodeResult.PrivateKey.PublicKey, _ = nostr.GetPublicKey(value.(string))

140
encrypt_decrypt.go Normal file
View File

@@ -0,0 +1,140 @@
package main
import (
"context"
"fmt"
"github.com/fiatjaf/cli/v3"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip04"
)
var encrypt = &cli.Command{
Name: "encrypt",
Usage: "encrypts a string with nip44 (or nip04 if specified using a flag) and returns the resulting ciphertext as base64",
ArgsUsage: "[plaintext string]",
DisableSliceFlagSeparator: true,
Flags: append(
defaultKeyFlags,
&cli.StringFlag{
Name: "recipient-pubkey",
Aliases: []string{"p", "tgt", "target", "pubkey"},
Required: true,
},
&cli.BoolFlag{
Name: "nip04",
Usage: "use nip04 encryption instead of nip44",
},
),
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)
}
plaintext := c.Args().First()
if c.Bool("nip04") {
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
if err != nil {
return err
}
if bunker != nil {
ciphertext, err := bunker.NIP04Encrypt(ctx, target, plaintext)
if err != nil {
return err
}
stdout(ciphertext)
} else {
ss, err := nip04.ComputeSharedSecret(target, sec)
if err != nil {
return fmt.Errorf("failed to compute nip04 shared secret: %w", err)
}
ciphertext, err := nip04.Encrypt(plaintext, ss)
if err != nil {
return fmt.Errorf("failed to encrypt as nip04: %w", err)
}
stdout(ciphertext)
}
} else {
kr, err := gatherKeyerFromArguments(ctx, c)
if err != nil {
return err
}
res, err := kr.Encrypt(ctx, plaintext, target)
if err != nil {
return fmt.Errorf("failed to encrypt: %w", err)
}
stdout(res)
}
return nil
},
}
var decrypt = &cli.Command{
Name: "decrypt",
Usage: "decrypts a base64 nip44 ciphertext (or nip04 if specified using a flag) and returns the resulting plaintext",
ArgsUsage: "[ciphertext base64]",
DisableSliceFlagSeparator: true,
Flags: append(
defaultKeyFlags,
&cli.StringFlag{
Name: "sender-pubkey",
Aliases: []string{"p", "src", "source", "pubkey"},
Required: true,
},
&cli.BoolFlag{
Name: "nip04",
Usage: "use nip04 encryption instead of nip44",
},
),
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)
}
ciphertext := c.Args().First()
if c.Bool("nip04") {
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
if err != nil {
return err
}
if bunker != nil {
plaintext, err := bunker.NIP04Decrypt(ctx, source, ciphertext)
if err != nil {
return err
}
stdout(plaintext)
} else {
ss, err := nip04.ComputeSharedSecret(source, sec)
if err != nil {
return fmt.Errorf("failed to compute nip04 shared secret: %w", err)
}
plaintext, err := nip04.Decrypt(ciphertext, ss)
if err != nil {
return fmt.Errorf("failed to encrypt as nip04: %w", err)
}
stdout(plaintext)
}
} else {
kr, err := gatherKeyerFromArguments(ctx, c)
if err != nil {
return err
}
res, err := kr.Decrypt(ctx, ciphertext, source)
if err != nil {
return fmt.Errorf("failed to encrypt: %w", err)
}
stdout(res)
}
return nil
},
}

View File

@@ -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 '{"tags": [["t", "spam"]]}' | nak event -c 'this is spam'`,
DisableSliceFlagSeparator: true,
Flags: []cli.Flag{
&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,
},
Flags: append(defaultKeyFlags,
// ~ these args are only for the convoluted musig2 signing process
// they will be generally copy-shared-pasted across some manual coordination method between participants
&cli.UintFlag{
@@ -125,7 +103,7 @@ example:
&cli.StringSliceFlag{
Name: "tag",
Aliases: []string{"t"},
Usage: "sets a tag field on the event, takes a value like -t e=<id>",
Usage: "sets a tag field on the event, takes a value like -t e=<id> or -t sometag=\"value one;value two;value three\"",
Category: CATEGORY_EVENT_FIELDS,
},
&cli.StringSliceFlag{
@@ -151,13 +129,13 @@ example:
Value: nostr.Now(),
Category: CATEGORY_EVENT_FIELDS,
},
},
),
ArgsUsage: "[relay...]",
Action: func(ctx context.Context, c *cli.Command) error {
// try to connect to the relays here
var relays []*nostr.Relay
if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
_, relays = connectToAllRelays(ctx, relayUrls, false)
relays = connectToAllRelays(ctx, relayUrls, false)
if len(relays) == 0 {
log("failed to connect to any of the given relays.\n")
os.Exit(3)
@@ -170,10 +148,11 @@ example:
}
}()
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
kr, err := gatherKeyerFromArguments(ctx, c)
if err != nil {
return err
}
sec, _, _ := gatherSecretKeyOrBunkerFromArguments(ctx, c)
doAuth := c.Bool("auth")
@@ -250,12 +229,7 @@ example:
if difficulty := c.Uint("pow"); difficulty > 0 {
// before doing pow we need the pubkey
if bunker != nil {
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 != "" {
if numSigners := c.Uint("musig"); numSigners > 1 {
pubkeys := c.StringSlice("musig-pubkey")
if int(numSigners) != len(pubkeys) {
return fmt.Errorf("when doing a pow with musig we must know all signer pubkeys upfront")
@@ -265,7 +239,7 @@ example:
return err
}
} else {
evt.PubKey, _ = nostr.GetPublicKey(sec)
evt.PubKey, _ = kr.GetPublicKey(ctx)
}
// try to generate work with this difficulty -- runs forever
@@ -276,11 +250,7 @@ example:
}
if evt.Sig == "" || mustRehashAndResign {
if bunker != nil {
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 != "" {
if numSigners := c.Uint("musig"); numSigners > 1 && sec != "" {
pubkeys := c.StringSlice("musig-pubkey")
secNonce := c.String("musig-nonce-secret")
pubNonces := c.StringSlice("musig-nonce")
@@ -295,7 +265,7 @@ example:
// instructions for what to do should have been printed by the performMusig() function
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)
}
}
@@ -332,21 +302,10 @@ example:
// error publishing
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
var pk string
if bunker != nil {
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)
}
pk, _ := kr.GetPublicKey(ctx)
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)
if err := relay.Auth(ctx, func(authEvent *nostr.Event) error {
return kr.SignEvent(ctx, authEvent)
}); err == nil {
// try to publish again, but this time don't try to auth again
doAuth = false

View File

@@ -8,7 +8,6 @@ import (
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip05"
"github.com/nbd-wtf/go-nostr/nip19"
sdk "github.com/nbd-wtf/nostr-sdk"
)
var fetch = &cli.Command{
@@ -27,8 +26,6 @@ var fetch = &cli.Command{
),
ArgsUsage: "[nip05_or_nip19_code]",
Action: func(ctx context.Context, c *cli.Command) error {
sys := sdk.NewSystem()
defer func() {
sys.Pool.Relays.Range(func(_ string, relay *nostr.Relay) bool {
relay.Close()

32
go.mod
View File

@@ -1,41 +1,37 @@
module github.com/fiatjaf/nak
go 1.22
toolchain go1.22.4
go 1.23.1
require (
github.com/bep/debounce v1.2.1
github.com/btcsuite/btcd/btcec/v2 v2.3.3
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.3.0
github.com/fatih/color v1.16.0
github.com/fiatjaf/cli/v3 v3.0.0-20240723181502-e7dd498b16ae
github.com/fiatjaf/eventstore v0.7.1
github.com/fiatjaf/eventstore v0.9.0
github.com/fiatjaf/khatru v0.7.5
github.com/mailru/easyjson v0.7.7
github.com/markusmobius/go-dateparser v1.2.3
github.com/nbd-wtf/go-nostr v0.34.10
github.com/nbd-wtf/nostr-sdk v0.5.0
golang.org/x/exp v0.0.0-20240707233637-46b078467d37
github.com/nbd-wtf/go-nostr v0.40.1
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
)
require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/btcsuite/btcd/btcutil v1.1.3 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chzyer/logex v1.1.10 // indirect
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect
github.com/dgraph-io/ristretto v1.0.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/elliotchance/pie/v2 v2.7.0 // indirect
github.com/fasthttp/websocket v1.5.7 // indirect
github.com/fiatjaf/generic-ristretto v0.0.1 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/golang/glog v1.1.2 // indirect
github.com/graph-gophers/dataloader/v7 v7.1.0 // indirect
github.com/hablullah/go-hijri v1.0.2 // indirect
github.com/hablullah/go-juliandays v1.0.0 // indirect
@@ -46,18 +42,18 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/puzpuzpuz/xsync/v3 v3.1.0 // indirect
github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect
github.com/rs/cors v1.7.0 // indirect
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
github.com/tetratelabs/wazero v1.2.1 // indirect
github.com/tidwall/gjson v1.17.1 // indirect
github.com/tetratelabs/wazero v1.8.0 // indirect
github.com/tidwall/gjson v1.17.3 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/wasilibs/go-re2 v1.3.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
)

60
go.sum
View File

@@ -8,8 +8,8 @@ github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tj
github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
github.com/btcsuite/btcd/btcec/v2 v2.3.3 h1:6+iXlDKE8RMtKsvK0gshlXIuPbyWM/h84Ensb7o3sC0=
github.com/btcsuite/btcd/btcec/v2 v2.3.3/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ=
@@ -27,8 +27,8 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
@@ -40,14 +40,16 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgraph-io/ristretto v1.0.0 h1:SYG07bONKMlFDUYu5pEu3DGAh8c2OFNzKm6G9J4Si84=
github.com/dgraph-io/ristretto v1.0.0/go.mod h1:jTi2FiYEhQ1NsMmA7DeBykizjOuY88NhKBkepyu1jPc=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/elliotchance/pie/v2 v2.7.0 h1:FqoIKg4uj0G/CrLGuMS9ejnFKa92lxE1dEgBD3pShXg=
@@ -58,10 +60,8 @@ 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/cli/v3 v3.0.0-20240723181502-e7dd498b16ae h1:0B/1dU3YECIbPoBIRTQ4c0scZCNz9TVHtQpiODGrTTo=
github.com/fiatjaf/cli/v3 v3.0.0-20240723181502-e7dd498b16ae/go.mod h1:aAWPO4bixZZxPtOnH6K3q4GbQ0jftUNDW9Oa861IRew=
github.com/fiatjaf/eventstore v0.7.1 h1:5f2yvEtYvsvMBNttysmXhSSum5M1qwvPzjEQ/BFue7Q=
github.com/fiatjaf/eventstore v0.7.1/go.mod h1:ek/yWbanKVG767fK51Q3+6Mvi5oEHYSsdPym40nZexw=
github.com/fiatjaf/generic-ristretto v0.0.1 h1:LUJSU87X/QWFsBXTwnH3moFe4N8AjUxT+Rfa0+bo6YM=
github.com/fiatjaf/generic-ristretto v0.0.1/go.mod h1:cvV6ANHDA/GrfzVrig7N7i6l8CWnkVZvtQ2/wk9DPVE=
github.com/fiatjaf/eventstore v0.9.0 h1:WsGDVAaRaVaV/J8PdrQDGfzChrL13q+lTO4C44rhu3E=
github.com/fiatjaf/eventstore v0.9.0/go.mod h1:JrAce5h0wi79+Sw4gsEq5kz0NtUxbVkOZ7lAo7ay6R8=
github.com/fiatjaf/khatru v0.7.5 h1:UFo+cdbqHDn1W4Q4h03y3vzh1BiU+6fLYK48oWU2K34=
github.com/fiatjaf/khatru v0.7.5/go.mod h1:WVqij7X9Vr9UAMIwafQbKVFKxc42Np37vyficwUr/nQ=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -72,8 +72,6 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -113,10 +111,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.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.34.10 h1:scJH45sFk5LOzHJNLw0EFTknCCKfKlo3tK+vdpTHz3Q=
github.com/nbd-wtf/go-nostr v0.34.10/go.mod h1:NZQkxl96ggbO8rvDpVjcsojJqKTPwqhP4i82O7K5DJs=
github.com/nbd-wtf/nostr-sdk v0.5.0 h1:zrMxcvMSxkw29RyfXEdF3XW5rUWLuT5Q9oBAhd5dyew=
github.com/nbd-wtf/nostr-sdk v0.5.0/go.mod h1:MJ7gYv3XiZKU6MHSM0N7oHqQAQhbvpgGQk4Q+XUdIUs=
github.com/nbd-wtf/go-nostr v0.40.1 h1:+ogxn+CeRwjQSMSU161fOxKWtVWTEz/p++X4O8VKhMw=
github.com/nbd-wtf/go-nostr v0.40.1/go.mod h1:FBa4FBJO7NuANvkeKSlrf0BIyxGufmrUbuelr6Q4Ick=
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=
@@ -130,8 +126,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4=
github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
@@ -141,10 +137,10 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/tetratelabs/wazero v1.2.1 h1:J4X2hrGzJvt+wqltuvcSjHQ7ujQxA9gb6PeMs4qlUWs=
github.com/tetratelabs/wazero v1.2.1/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g=
github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94=
github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
@@ -161,10 +157,10 @@ github.com/wasilibs/nottinygc v0.4.0/go.mod h1:oDcIotskuYNMpqMF23l7Z8uzD4TC0WXHK
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w=
golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -184,13 +180,13 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w
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.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -3,7 +3,6 @@ package main
import (
"bufio"
"context"
"encoding/hex"
"fmt"
"math/rand"
"net/url"
@@ -11,15 +10,20 @@ import (
"strings"
"time"
"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/nip19"
"github.com/nbd-wtf/go-nostr/nip46"
"github.com/nbd-wtf/go-nostr/nip49"
"github.com/nbd-wtf/go-nostr/sdk"
)
var sys = sdk.NewSystem()
func init() {
sys.Pool = nostr.NewSimplePool(context.Background(),
nostr.WithUserAgent("nak/b"),
)
}
const (
LINE_PROCESSING_ERROR = iota
)
@@ -120,13 +124,20 @@ func connectToAllRelays(
relayUrls []string,
forcePreAuth bool,
opts ...nostr.PoolOption,
) (*nostr.SimplePool, []*nostr.Relay) {
) []*nostr.Relay {
sys.Pool = nostr.NewSimplePool(context.Background(),
append(opts,
nostr.WithEventMiddleware(sys.TrackEventHints),
nostr.WithPenaltyBox(),
nostr.WithUserAgent("nak/s"),
)...,
)
relays := make([]*nostr.Relay, 0, len(relayUrls))
pool := nostr.NewSimplePool(ctx, opts...)
relayLoop:
for _, url := range relayUrls {
log("connecting to %s... ", url)
if relay, err := pool.EnsureRelay(url); err == nil {
if relay, err := sys.Pool.EnsureRelay(url); err == nil {
if forcePreAuth {
log("waiting for auth challenge... ")
signer := opts[0].(nostr.WithAuthHandler)
@@ -140,7 +151,7 @@ relayLoop:
if (*challengeTag)[1] == "" {
return fmt.Errorf("auth not received yet *****")
}
return signer(authEvent)
return signer(ctx, nostr.RelayEvent{Event: authEvent, Relay: relay})
}); err == nil {
// auth succeeded
break challengeWaitLoop
@@ -166,7 +177,7 @@ relayLoop:
log(err.Error() + "\n")
}
}
return pool, relays
return relays
}
func lineProcessingError(ctx context.Context, msg string, args ...any) context.Context {
@@ -180,109 +191,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"
func randString(n int) string {

160
helpers_key.go Normal file
View 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, err = keyer.NewPlainKeySigner(key)
}
return kr, err
}
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) {
log(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
key.go
View File

@@ -5,7 +5,6 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"os"
"strings"
"github.com/btcsuite/btcd/btcec/v2"
@@ -25,8 +24,8 @@ var key = &cli.Command{
Commands: []*cli.Command{
generate,
public,
encrypt,
decrypt,
encryptKey,
decryptKey,
combine,
},
}
@@ -49,20 +48,28 @@ var public = &cli.Command{
Description: ``,
ArgsUsage: "[secret]",
DisableSliceFlagSeparator: true,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "with-parity",
Usage: "output 33 bytes instead of 32, the first one being either '02' or '03', a prefix indicating whether this pubkey is even or odd.",
},
},
Action: func(ctx context.Context, c *cli.Command) error {
for sec := range getSecretKeysFromStdinLinesOrSlice(ctx, c, c.Args().Slice()) {
pubkey, err := nostr.GetPublicKey(sec)
if err != nil {
ctx = lineProcessingError(ctx, "failed to derive public key: %s", err)
continue
b, _ := hex.DecodeString(sec)
_, pk := btcec.PrivKeyFromBytes(b)
if c.Bool("with-parity") {
stdout(hex.EncodeToString(pk.SerializeCompressed()))
} else {
stdout(hex.EncodeToString(pk.SerializeCompressed()[1:]))
}
stdout(pubkey)
}
return nil
},
}
var encrypt = &cli.Command{
var encryptKey = &cli.Command{
Name: "encrypt",
Usage: "encrypts a secret key and prints an ncryptsec code",
Description: `uses the NIP-49 standard.`,
@@ -101,7 +108,7 @@ var encrypt = &cli.Command{
},
}
var decrypt = &cli.Command{
var decryptKey = &cli.Command{
Name: "decrypt",
Usage: "takes an ncrypsec and a password and decrypts it into an nsec",
Description: `uses the NIP-49 standard.`,
@@ -188,7 +195,7 @@ However, if the intent is to check if two existing Nostr pubkeys match a given c
for i, prefix := range []byte{0x02, 0x03} {
pubk, err := btcec.ParsePubKey(append([]byte{prefix}, keyb...))
if err != nil {
fmt.Fprintf(os.Stderr, "error parsing key %s: %s", keyhex, err)
log("error parsing key %s: %s", keyhex, err)
continue
}
group[i] = pubk
@@ -229,7 +236,7 @@ However, if the intent is to check if two existing Nostr pubkeys match a given c
agg, _, _, err := musig2.AggregateKeys(combining, true)
if err != nil {
fmt.Fprintf(os.Stderr, "error aggregating: %s", err)
log("error aggregating: %s", err)
return
}
@@ -252,13 +259,13 @@ However, if the intent is to check if two existing Nostr pubkeys match a given c
}
res, _ := json.MarshalIndent(result, "", " ")
fmt.Println(string(res))
stdout(string(res))
return nil
},
}
func getSecretKeysFromStdinLinesOrSlice(ctx context.Context, c *cli.Command, keys []string) chan string {
func getSecretKeysFromStdinLinesOrSlice(ctx context.Context, _ *cli.Command, keys []string) chan string {
ch := make(chan string)
go func() {
for sec := range getStdinLinesOrArgumentsFromSlice(keys) {

View File

@@ -7,6 +7,8 @@ import (
"github.com/fiatjaf/cli/v3"
)
var version string = "debug"
var app = &cli.Command{
Name: "nak",
Suggest: true,
@@ -26,7 +28,10 @@ var app = &cli.Command{
relay,
bunker,
serve,
encrypt,
decrypt,
},
Version: version,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "quiet",

View File

@@ -6,7 +6,6 @@ import (
"encoding/base64"
"encoding/hex"
"fmt"
"os"
"strconv"
"strings"
@@ -135,8 +134,8 @@ func performMusig(
if err != nil {
return false, err
}
fmt.Fprintf(os.Stderr, "the following code should be saved secretly until the next step an included with --musig-nonce-secret:\n")
fmt.Fprintf(os.Stderr, "%s\n\n", base64.StdEncoding.EncodeToString(nonce.SecNonce[:]))
log("the following code should be saved secretly until the next step an included with --musig-nonce-secret:\n")
log("%s\n\n", base64.StdEncoding.EncodeToString(nonce.SecNonce[:]))
knownNonces = append(knownNonces, nonce.PubNonce)
printPublicCommandForNextPeer(evt, numSigners, knownSigners, knownNonces, nil, false)
@@ -149,7 +148,7 @@ func performMusig(
} else {
evt.PubKey = hex.EncodeToString(comb.SerializeCompressed()[1:])
evt.ID = evt.GetID()
fmt.Fprintf(os.Stderr, "combined key: %x\n\n", comb.SerializeCompressed())
log("combined key: %x\n\n", comb.SerializeCompressed())
}
// we have all the signers, which means we must also have all the nonces
@@ -244,7 +243,7 @@ func printPublicCommandForNextPeer(
maybeNonceSecret = " --musig-nonce-secret '<insert-nonce-secret>'"
}
fmt.Fprintf(os.Stderr, "the next signer and they should call this on their side:\nnak event --sec <insert-secret-key> --musig %d %s%s%s%s%s\n",
log("the next signer and they should call this on their side:\nnak event --sec <insert-secret-key> --musig %d %s%s%s%s%s\n",
numSigners,
eventToCliArgs(evt),
signersToCliArgs(knownSigners),

View File

@@ -9,8 +9,11 @@ import (
"github.com/nbd-wtf/go-nostr"
)
func paginateWithPoolAndParams(pool *nostr.SimplePool, interval time.Duration, globalLimit uint64) func(ctx context.Context, urls []string, filters nostr.Filters) chan nostr.IncomingEvent {
return func(ctx context.Context, urls []string, filters nostr.Filters) chan nostr.IncomingEvent {
func paginateWithParams(
interval time.Duration,
globalLimit uint64,
) func(ctx context.Context, urls []string, filters nostr.Filters, opts ...nostr.SubscriptionOption) chan nostr.RelayEvent {
return func(ctx context.Context, urls []string, filters nostr.Filters, opts ...nostr.SubscriptionOption) chan nostr.RelayEvent {
// filters will always be just one
filter := filters[0]
@@ -26,7 +29,7 @@ func paginateWithPoolAndParams(pool *nostr.SimplePool, interval time.Duration, g
}
}
var globalCount uint64 = 0
globalCh := make(chan nostr.IncomingEvent)
globalCh := make(chan nostr.RelayEvent)
repeatedCache := make([]string, 0, 300)
nextRepeatedCache := make([]string, 0, 300)
@@ -39,7 +42,7 @@ func paginateWithPoolAndParams(pool *nostr.SimplePool, interval time.Duration, g
time.Sleep(interval)
keepGoing := false
for evt := range pool.SubManyEose(ctx, urls, nostr.Filters{filter}) {
for evt := range sys.Pool.SubManyEose(ctx, urls, nostr.Filters{filter}, opts...) {
if slices.Contains(repeatedCache, evt.ID) {
continue
}

View File

@@ -74,26 +74,7 @@ var relay = &cli.Command{
flags[i] = declareFlag(argName)
}
flags = append(flags,
&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",
},
)
flags = append(flags, defaultKeyFlags...)
cmd := &cli.Command{
Name: def.method,
@@ -114,7 +95,7 @@ var relay = &cli.Command{
return nil
}
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
kr, err := gatherKeyerFromArguments(ctx, c)
if err != nil {
return err
}
@@ -131,7 +112,7 @@ var relay = &cli.Command{
// Authorization
payloadHash := sha256.Sum256(reqj)
authEvent := nostr.Event{
tokenEvent := nostr.Event{
Kind: 27235,
CreatedAt: nostr.Now(),
Tags: nostr.Tags{
@@ -140,14 +121,10 @@ var relay = &cli.Command{
{"payload", hex.EncodeToString(payloadHash[:])},
},
}
if bunker != nil {
if err := bunker.SignEvent(ctx, &authEvent); err != nil {
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)
if err := kr.SignEvent(ctx, &tokenEvent); err != nil {
return fmt.Errorf("failed to sign token event: %w", err)
}
evtj, _ := json.Marshal(authEvent)
evtj, _ := json.Marshal(tokenEvent)
req.Header.Set("Authorization", "Nostr "+base64.StdEncoding.EncodeToString(evtj))
// Content-Type

143
req.go
View File

@@ -31,96 +31,67 @@ it can also take a filter from stdin, optionally modify it with flags and send i
example:
echo '{"kinds": [1], "#t": ["test"]}' | nak req -l 5 -k 4549 --tag t=spam wss://nostr-pub.wellorder.net`,
DisableSliceFlagSeparator: true,
Flags: append(reqFilterFlags,
&cli.BoolFlag{
Name: "stream",
Usage: "keep the subscription open, printing all events as they are returned",
DefaultText: "false, will close on EOSE",
},
&cli.BoolFlag{
Name: "paginate",
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",
Usage: "time between queries when using --paginate",
},
&cli.UintFlag{
Name: "paginate-global-limit",
Usage: "global limit at which --paginate should stop",
DefaultText: "uses the value given by --limit/-l or infinite",
},
&cli.BoolFlag{
Name: "bare",
Usage: "when printing the filter, print just the filter, not enveloped in a [\"REQ\", ...] array",
},
&cli.BoolFlag{
Name: "auth",
Usage: "always perform NIP-42 \"AUTH\" when facing an \"auth-required: \" rejection and try again",
},
&cli.BoolFlag{
Name: "force-pre-auth",
Aliases: []string{"fpa"},
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,
},
Flags: append(defaultKeyFlags,
append(reqFilterFlags,
&cli.BoolFlag{
Name: "stream",
Usage: "keep the subscription open, printing all events as they are returned",
DefaultText: "false, will close on EOSE",
},
&cli.BoolFlag{
Name: "paginate",
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",
Usage: "time between queries when using --paginate",
},
&cli.UintFlag{
Name: "paginate-global-limit",
Usage: "global limit at which --paginate should stop",
DefaultText: "uses the value given by --limit/-l or infinite",
},
&cli.BoolFlag{
Name: "bare",
Usage: "when printing the filter, print just the filter, not enveloped in a [\"REQ\", ...] array",
},
&cli.BoolFlag{
Name: "auth",
Usage: "always perform NIP-42 \"AUTH\" when facing an \"auth-required: \" rejection and try again",
},
&cli.BoolFlag{
Name: "force-pre-auth",
Aliases: []string{"fpa"},
Usage: "after connecting, for a NIP-42 \"AUTH\" message to be received, act on it and only then send the \"REQ\"",
Category: CATEGORY_SIGNER,
},
)...,
),
ArgsUsage: "[relay...]",
Action: func(ctx context.Context, c *cli.Command) error {
var pool *nostr.SimplePool
relayUrls := c.Args().Slice()
if len(relayUrls) > 0 {
var relays []*nostr.Relay
pool, relays = connectToAllRelays(ctx, relayUrls, c.Bool("force-pre-auth"), nostr.WithAuthHandler(func(evt *nostr.Event) error {
if !c.Bool("auth") && !c.Bool("force-pre-auth") {
return fmt.Errorf("auth not authorized")
}
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
if err != nil {
return err
}
relays := connectToAllRelays(ctx,
relayUrls,
c.Bool("force-pre-auth"),
nostr.WithAuthHandler(
func(ctx context.Context, authEvent nostr.RelayEvent) error {
if !c.Bool("auth") && !c.Bool("force-pre-auth") {
return fmt.Errorf("auth not authorized")
}
kr, err := gatherKeyerFromArguments(ctx, c)
if err != nil {
return err
}
var pk string
if bunker != nil {
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)
pk, _ := kr.GetPublicKey(ctx)
log("performing auth as %s... ", pk)
if bunker != nil {
return bunker.SignEvent(ctx, evt)
} else {
return evt.Sign(sec)
}
}))
return kr.SignEvent(ctx, authEvent.Event)
},
),
)
if len(relays) == 0 {
log("failed to connect to any of the given relays.\n")
os.Exit(3)
@@ -151,11 +122,11 @@ example:
}
if len(relayUrls) > 0 {
fn := pool.SubManyEose
fn := sys.Pool.SubManyEose
if c.Bool("paginate") {
fn = paginateWithPoolAndParams(pool, c.Duration("paginate-interval"), c.Uint("paginate-global-limit"))
fn = paginateWithParams(c.Duration("paginate-interval"), c.Uint("paginate-global-limit"))
} else if c.Bool("stream") {
fn = pool.SubMany
fn = sys.Pool.SubMany
}
for ie := range fn(ctx, relayUrls, nostr.Filters{filter}) {

View File

@@ -32,7 +32,7 @@ it outputs nothing if the verification is successful.`,
}
if ok, err := evt.CheckSignature(); !ok {
ctx = lineProcessingError(ctx, "invalid signature: %s", err)
ctx = lineProcessingError(ctx, "invalid signature: %v", err)
continue
}
}