diff --git a/cmd/createenr.go b/cmd/createenr.go index 683e24c1d..a35464936 100644 --- a/cmd/createenr.go +++ b/cmd/createenr.go @@ -23,6 +23,7 @@ import ( "github.com/spf13/cobra" "github.com/obolnetwork/charon/app/errors" + "github.com/obolnetwork/charon/app/z" "github.com/obolnetwork/charon/p2p" ) @@ -48,7 +49,13 @@ func newCreateEnrCmd(runFunc func(io.Writer, p2p.Config, string) error) *cobra.C } // runCreateEnrCmd stores a new charon-enr-private-key to disk and prints the ENR for the provided config. +// It returns an error if the key already exists. func runCreateEnrCmd(w io.Writer, config p2p.Config, dataDir string) error { + _, err := p2p.LoadPrivKey(dataDir) + if err == nil { + return errors.New("charon-enr-private-key already exists", z.Str("enr_path", p2p.KeyPath(dataDir))) + } + key, err := p2p.NewSavedPrivKey(dataDir) if err != nil { return err diff --git a/cmd/enr.go b/cmd/enr.go index 8ff46dd70..c7816f53b 100644 --- a/cmd/enr.go +++ b/cmd/enr.go @@ -16,41 +16,47 @@ package cmd import ( + "crypto/ecdsa" "fmt" "io" "io/fs" + "strings" + "github.com/ethereum/go-ethereum/crypto" "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/obolnetwork/charon/app/errors" "github.com/obolnetwork/charon/app/z" "github.com/obolnetwork/charon/p2p" ) -func newEnrCmd(runFunc func(io.Writer, p2p.Config, string) error) *cobra.Command { +func newEnrCmd(runFunc func(io.Writer, p2p.Config, string, bool) error) *cobra.Command { var ( config p2p.Config dataDir string + verbose bool ) cmd := &cobra.Command{ Use: "enr", - Short: "Print this client's Ethereum Node Record", - Long: `Return information on this node's Ethereum Node Record (ENR)`, + Short: "Prints a new ENR for this node", + Long: `Prints a newly generated Ethereum Node Record (ENR) from this node's charon-enr-private-key`, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return runFunc(cmd.OutOrStdout(), config, dataDir) + return runFunc(cmd.OutOrStdout(), config, dataDir, verbose) }, } bindDataDirFlag(cmd.Flags(), &dataDir) bindP2PFlags(cmd.Flags(), &config) + bindEnrFlags(cmd.Flags(), &verbose) return cmd } // runNewENR loads the p2pkey from disk and prints the ENR for the provided config. -func runNewENR(w io.Writer, config p2p.Config, dataDir string) error { +func runNewENR(w io.Writer, config p2p.Config, dataDir string, verbose bool) error { key, err := p2p.LoadPrivKey(dataDir) if errors.Is(err, fs.ErrNotExist) { return errors.New("private key not found. If this is your first time running this client, create one with `charon create enr`.", z.Str("enr_path", p2p.KeyPath(dataDir))) //nolint:revive @@ -64,7 +70,44 @@ func runNewENR(w io.Writer, config p2p.Config, dataDir string) error { } defer db.Close() - _, _ = fmt.Fprintln(w, localEnode.Node().String()) + newEnr := localEnode.Node().String() + _, _ = fmt.Fprintln(w, newEnr) + + if !verbose { + return nil + } + + r, err := p2p.DecodeENR(newEnr) + if err != nil { + return err + } + + writeExpandedEnr(w, r.Signature(), r.Seq(), pubkeyHex(key.PublicKey)) return nil } + +// writeExpandedEnr writes the expanded form of ENR to the terminal. +func writeExpandedEnr(w io.Writer, sig []byte, seq uint64, pubkey string) { + var sb strings.Builder + _, _ = sb.WriteString("\n") + _, _ = sb.WriteString("***************** Decoded ENR (see https://enr-viewer.com/ for additional fields) **********************\n") + _, _ = sb.WriteString(fmt.Sprintf("secp256k1 pubkey: %#x\n", pubkey)) + _, _ = sb.WriteString(fmt.Sprintf("signature: %#x\n", sig)) + _, _ = sb.WriteString(fmt.Sprintf("seq: %d\n", seq)) + _, _ = sb.WriteString("********************************************************************************************************\n") + _, _ = sb.WriteString("\n") + + _, _ = w.Write([]byte(sb.String())) +} + +// pubkeyHex returns compressed public key bytes. +func pubkeyHex(pubkey ecdsa.PublicKey) string { + b := crypto.CompressPubkey(&pubkey) + + return fmt.Sprintf("%#x", b) +} + +func bindEnrFlags(flags *pflag.FlagSet, verbose *bool) { + flags.BoolVar(verbose, "verbose", false, "Prints the expanded form of ENR.") +} diff --git a/cmd/enr_internal_test.go b/cmd/enr_internal_test.go index edd45ac61..7dfc8fd61 100644 --- a/cmd/enr_internal_test.go +++ b/cmd/enr_internal_test.go @@ -31,7 +31,7 @@ func TestRunNewEnr(t *testing.T) { temp, err := os.MkdirTemp("", "") require.NoError(t, err) - got := runNewENR(io.Discard, p2p.Config{}, temp) + got := runNewENR(io.Discard, p2p.Config{}, temp, false) expected := errors.New("private key not found. If this is your first time running this client, create one with `charon create enr`.", z.Str("enr_path", p2p.KeyPath(temp))) require.Equal(t, expected.Error(), got.Error()) }