diff --git a/go.mod b/go.mod index 95baf94..4949687 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/bgentry/speakeasy v0.1.0 github.com/mailru/easyjson v0.7.7 github.com/manifoldco/promptui v0.9.0 - github.com/nbd-wtf/go-nostr v0.28.1 + github.com/nbd-wtf/go-nostr v0.28.2 github.com/nbd-wtf/nostr-sdk v0.0.5 github.com/urfave/cli/v2 v2.25.7 golang.org/x/exp v0.0.0-20231006140011-7918f672742d @@ -33,5 +33,6 @@ require ( github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + golang.org/x/crypto v0.7.0 // indirect golang.org/x/sys v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index 328fc7a..3b65c90 100644 --- a/go.sum +++ b/go.sum @@ -76,8 +76,8 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 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/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= -github.com/nbd-wtf/go-nostr v0.28.1 h1:XQi/lBsigBXHRm7IDBJE7SR9citCh9srgf8sA5iVW3A= -github.com/nbd-wtf/go-nostr v0.28.1/go.mod h1:OQ8sNLFJnsj17BdqZiLSmjJBIFTfDqckEYC3utS4qoY= +github.com/nbd-wtf/go-nostr v0.28.2 h1:KhpGcs6KMLBqYExzKoqt7vP5Re2f8Kpy9SavYZa2PTI= +github.com/nbd-wtf/go-nostr v0.28.2/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/go.mod h1:iJJsikesCGLNFZ9dLqhLPDzdt924EagUmdQxT3w2Lmk= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -111,6 +111,8 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsr 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.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/key.go b/key.go new file mode 100644 index 0000000..d51ee0f --- /dev/null +++ b/key.go @@ -0,0 +1,131 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/nbd-wtf/go-nostr" + "github.com/nbd-wtf/go-nostr/nip19" + "github.com/nbd-wtf/go-nostr/nip49" + "github.com/urfave/cli/v2" +) + +var key = &cli.Command{ + Name: "key", + Usage: "operations on secret keys: generate, derive, encrypt, decrypt.", + Description: ``, + Subcommands: []*cli.Command{ + generate, + public, + encrypt, + decrypt, + }, +} + +var generate = &cli.Command{ + Name: "generate", + Usage: "generates a secret key", + Description: ``, + Action: func(c *cli.Context) error { + sec := nostr.GeneratePrivateKey() + stdout(sec) + return nil + }, +} + +var public = &cli.Command{ + Name: "public", + Usage: "computes a public key from a secret key", + Description: ``, + ArgsUsage: "[secret]", + Action: func(c *cli.Context) error { + for sec := range getSecretKeyFromStdinLinesOrFirstArgument(c) { + pubkey, err := nostr.GetPublicKey(sec) + if err != nil { + lineProcessingError(c, "failed to derive public key: %s", err) + continue + } + stdout(pubkey) + } + return nil + }, +} + +var encrypt = &cli.Command{ + Name: "encrypt", + Usage: "encrypts a secret key and prints an ncryptsec code", + Description: `uses the NIP-49 standard.`, + ArgsUsage: " ", + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "logn", + Usage: "the bigger the number the harder it will be to bruteforce the password", + Value: 16, + DefaultText: "16", + }, + }, + Action: func(c *cli.Context) error { + password := c.Args().Get(c.Args().Len() - 1) + if password == "" { + return fmt.Errorf("no password given") + } + for sec := range getSecretKeyFromStdinLinesOrFirstArgument(c) { + ncryptsec, err := nip49.Encrypt(sec, password, uint8(c.Int("logn")), 0x02) + if err != nil { + lineProcessingError(c, "failed to encrypt: %s", err) + continue + } + stdout(ncryptsec) + } + return nil + }, +} + +var decrypt = &cli.Command{ + Name: "decrypt", + Usage: "takes an ncrypsec and a password and decrypts it into an nsec", + Description: `uses the NIP-49 standard.`, + ArgsUsage: " ", + Action: func(c *cli.Context) error { + password := c.Args().Get(c.Args().Len() - 1) + if password == "" { + return fmt.Errorf("no password given") + } + for ncryptsec := range getStdinLinesOrFirstArgument(c) { + sec, err := nip49.Decrypt(ncryptsec, password) + if err != nil { + lineProcessingError(c, "failed to decrypt: %s", err) + continue + } + nsec, _ := nip19.EncodePrivateKey(sec) + stdout(nsec) + } + return nil + }, +} + +func getSecretKeyFromStdinLinesOrFirstArgument(c *cli.Context) chan string { + ch := make(chan string) + go func() { + for sec := range getStdinLinesOrFirstArgument(c) { + if sec == "" { + continue + } + if strings.HasPrefix(sec, "nsec1") { + _, data, err := nip19.Decode(sec) + if err != nil { + lineProcessingError(c, "invalid nsec code: %s", err) + continue + } + sec = data.(string) + } + if !nostr.IsValid32ByteHex(sec) { + lineProcessingError(c, "invalid hex secret key") + continue + } + ch <- sec + } + close(ch) + }() + return ch +} diff --git a/main.go b/main.go index c0175fa..1d0c194 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,7 @@ var app = &cli.App{ event, decode, encode, + key, verify, relay, bunker,