github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/crypto/keys/client/add.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"flag"
     7  	"fmt"
     8  	"regexp"
     9  
    10  	"github.com/gnolang/gno/tm2/pkg/commands"
    11  	"github.com/gnolang/gno/tm2/pkg/crypto"
    12  	"github.com/gnolang/gno/tm2/pkg/crypto/bip39"
    13  	"github.com/gnolang/gno/tm2/pkg/crypto/hd"
    14  	"github.com/gnolang/gno/tm2/pkg/crypto/keys"
    15  	"github.com/gnolang/gno/tm2/pkg/crypto/secp256k1"
    16  )
    17  
    18  var (
    19  	errInvalidMnemonic       = errors.New("invalid bip39 mnemonic")
    20  	errInvalidDerivationPath = errors.New("invalid derivation path")
    21  )
    22  
    23  var reDerivationPath = regexp.MustCompile(`^44'\/118'\/\d+'\/0\/\d+$`)
    24  
    25  type AddCfg struct {
    26  	RootCfg *BaseCfg
    27  
    28  	Recover  bool
    29  	NoBackup bool
    30  	Account  uint64
    31  	Index    uint64
    32  
    33  	DerivationPath commands.StringArr
    34  }
    35  
    36  func NewAddCmd(rootCfg *BaseCfg, io commands.IO) *commands.Command {
    37  	cfg := &AddCfg{
    38  		RootCfg: rootCfg,
    39  	}
    40  
    41  	cmd := commands.NewCommand(
    42  		commands.Metadata{
    43  			Name:       "add",
    44  			ShortUsage: "add [flags] <key-name>",
    45  			ShortHelp:  "adds key to the keybase",
    46  		},
    47  		cfg,
    48  		func(_ context.Context, args []string) error {
    49  			return execAdd(cfg, args, io)
    50  		},
    51  	)
    52  
    53  	cmd.AddSubCommands(
    54  		NewAddMultisigCmd(cfg, io),
    55  		NewAddLedgerCmd(cfg, io),
    56  		NewAddBech32Cmd(cfg, io),
    57  	)
    58  
    59  	return cmd
    60  }
    61  
    62  func (c *AddCfg) RegisterFlags(fs *flag.FlagSet) {
    63  	fs.BoolVar(
    64  		&c.Recover,
    65  		"recover",
    66  		false,
    67  		"provide seed phrase to recover existing key instead of creating",
    68  	)
    69  
    70  	fs.BoolVar(
    71  		&c.NoBackup,
    72  		"nobackup",
    73  		false,
    74  		"don't print out seed phrase (if others are watching the terminal)",
    75  	)
    76  
    77  	fs.Uint64Var(
    78  		&c.Account,
    79  		"account",
    80  		0,
    81  		"account number for HD derivation",
    82  	)
    83  
    84  	fs.Uint64Var(
    85  		&c.Index,
    86  		"index",
    87  		0,
    88  		"address index number for HD derivation",
    89  	)
    90  
    91  	fs.Var(
    92  		&c.DerivationPath,
    93  		"derivation-path",
    94  		"derivation path for deriving the address",
    95  	)
    96  }
    97  
    98  func execAdd(cfg *AddCfg, args []string, io commands.IO) error {
    99  	// Check if the key name is provided
   100  	if len(args) != 1 {
   101  		return flag.ErrHelp
   102  	}
   103  
   104  	// Validate the derivation paths are correct
   105  	for _, path := range cfg.DerivationPath {
   106  		// Make sure the path is valid
   107  		if _, err := hd.NewParamsFromPath(path); err != nil {
   108  			return fmt.Errorf(
   109  				"%w, %w",
   110  				errInvalidDerivationPath,
   111  				err,
   112  			)
   113  		}
   114  
   115  		// Make sure the path conforms to the Gno derivation path
   116  		if !reDerivationPath.MatchString(path) {
   117  			return errInvalidDerivationPath
   118  		}
   119  	}
   120  
   121  	name := args[0]
   122  
   123  	// Read the keybase from the home directory
   124  	kb, err := keys.NewKeyBaseFromDir(cfg.RootCfg.Home)
   125  	if err != nil {
   126  		return fmt.Errorf("unable to read keybase, %w", err)
   127  	}
   128  
   129  	// Check if the key exists
   130  	exists, err := kb.HasByName(name)
   131  	if err != nil {
   132  		return fmt.Errorf("unable to fetch key, %w", err)
   133  	}
   134  
   135  	// Get overwrite confirmation, if any
   136  	if exists {
   137  		overwrite, err := io.GetConfirmation(fmt.Sprintf("Override the existing name %s", name))
   138  		if err != nil {
   139  			return fmt.Errorf("unable to get confirmation, %w", err)
   140  		}
   141  
   142  		if !overwrite {
   143  			return errOverwriteAborted
   144  		}
   145  	}
   146  
   147  	// Ask for a password when generating a local key
   148  	encryptPassword, err := io.GetCheckPassword(
   149  		[2]string{
   150  			"Enter a passphrase to encrypt your key to disk:",
   151  			"Repeat the passphrase:",
   152  		},
   153  		cfg.RootCfg.InsecurePasswordStdin,
   154  	)
   155  	if err != nil {
   156  		return fmt.Errorf("unable to parse provided password, %w", err)
   157  	}
   158  
   159  	// Get bip39 mnemonic
   160  	mnemonic, err := GenerateMnemonic(mnemonicEntropySize)
   161  	if err != nil {
   162  		return fmt.Errorf("unable to generate mnemonic, %w", err)
   163  	}
   164  
   165  	if cfg.Recover {
   166  		bip39Message := "Enter your bip39 mnemonic"
   167  		mnemonic, err = io.GetString(bip39Message)
   168  		if err != nil {
   169  			return fmt.Errorf("unable to parse mnemonic, %w", err)
   170  		}
   171  
   172  		// Make sure it's valid
   173  		if !bip39.IsMnemonicValid(mnemonic) {
   174  			return errInvalidMnemonic
   175  		}
   176  	}
   177  
   178  	// Save the account
   179  	info, err := kb.CreateAccount(
   180  		name,
   181  		mnemonic,
   182  		"",
   183  		encryptPassword,
   184  		uint32(cfg.Account),
   185  		uint32(cfg.Index),
   186  	)
   187  	if err != nil {
   188  		return fmt.Errorf("unable to save account to keybase, %w", err)
   189  	}
   190  
   191  	// Print the derived address info
   192  	printDerive(mnemonic, cfg.DerivationPath, io)
   193  
   194  	// Recover key from seed passphrase
   195  	if cfg.Recover {
   196  		printCreate(info, false, "", io)
   197  
   198  		return nil
   199  	}
   200  
   201  	// Print the key create info
   202  	printCreate(info, !cfg.NoBackup, mnemonic, io)
   203  
   204  	return nil
   205  }
   206  
   207  func printCreate(info keys.Info, showMnemonic bool, mnemonic string, io commands.IO) {
   208  	io.Println("")
   209  	printNewInfo(info, io)
   210  
   211  	// print mnemonic unless requested not to.
   212  	if showMnemonic {
   213  		io.Printfln(`
   214  **IMPORTANT** write this mnemonic phrase in a safe place.
   215  It is the only way to recover your account if you ever forget your password.
   216  %v
   217  `, mnemonic)
   218  	}
   219  }
   220  
   221  func printNewInfo(info keys.Info, io commands.IO) {
   222  	keyname := info.GetName()
   223  	keytype := info.GetType()
   224  	keypub := info.GetPubKey()
   225  	keyaddr := info.GetAddress()
   226  	keypath, _ := info.GetPath()
   227  
   228  	io.Printfln("* %s (%s) - addr: %v pub: %v, path: %v",
   229  		keyname, keytype, keyaddr, keypub, keypath)
   230  }
   231  
   232  // printDerive prints the derived accounts, if any
   233  func printDerive(
   234  	mnemonic string,
   235  	paths []string,
   236  	io commands.IO,
   237  ) {
   238  	if len(paths) == 0 {
   239  		// No accounts to print
   240  		return
   241  	}
   242  
   243  	// Generate the accounts
   244  	accounts := generateAccounts(
   245  		mnemonic,
   246  		paths,
   247  	)
   248  
   249  	io.Printf("[Derived Accounts]\n\n")
   250  
   251  	// Print them out
   252  	for index, path := range paths {
   253  		io.Printfln(
   254  			"%d. %s: %s",
   255  			index,
   256  			path,
   257  			accounts[index].String(),
   258  		)
   259  	}
   260  }
   261  
   262  // generateAccounts the accounts using the provided mnemonics
   263  func generateAccounts(mnemonic string, paths []string) []crypto.Address {
   264  	addresses := make([]crypto.Address, len(paths))
   265  
   266  	// Generate the seed
   267  	seed := bip39.NewSeed(mnemonic, "")
   268  
   269  	for index, path := range paths {
   270  		key := generateKeyFromSeed(seed, path)
   271  		address := key.PubKey().Address()
   272  
   273  		addresses[index] = address
   274  	}
   275  
   276  	return addresses
   277  }
   278  
   279  // generateKeyFromSeed generates a private key from
   280  // the provided seed and path
   281  func generateKeyFromSeed(seed []byte, path string) crypto.PrivKey {
   282  	masterPriv, ch := hd.ComputeMastersFromSeed(seed)
   283  	derivedPriv, _ := hd.DerivePrivateKeyForPath(masterPriv, ch, path)
   284  
   285  	return secp256k1.PrivKeySecp256k1(derivedPriv)
   286  }