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 }