From c4ea921876b0535022882c568b5cc6b0269db7d4 Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 23 Mar 2015 13:00:06 +0000 Subject: import/export accounts - cli: add passwordfile flag - cli: change unlock flag only takes account - cli: with unlock you are prompted for password or use passfile with password flag - cli: unlockAccount used in normal client start (run) and accountExport - cli: getPassword used in accountCreate and accountImport - accounts: Manager.Import, Manager.Export - crypto: SaveECDSA (to complement LoadECDSA) to save to file - crypto: NewKeyFromECDSA added (used in accountImport and New = generated constructor) --- cmd/ethereum/main.go | 174 ++++++++++++++++++++++++++++++++++++++++++--------- cmd/utils/flags.go | 8 ++- 2 files changed, 152 insertions(+), 30 deletions(-) (limited to 'cmd') diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 2f417aacb..276480195 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -26,11 +26,11 @@ import ( "os" "runtime" "strconv" - "strings" "time" "github.com/codegangsta/cli" "github.com/ethereum/ethash" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" @@ -83,11 +83,62 @@ The output of this command is supposed to be machine-readable. Action: accountList, Name: "list", Usage: "print account addresses", + Description: ` + +`, }, { Action: accountCreate, Name: "new", Usage: "create a new account", + Description: ` + + ethereum account new + +Creates a new accountThe account is saved in encrypted format, you are prompted for a passphrase. +You must remember this passphrase to unlock your account in future. +For non-interactive use the passphrase can be specified with the --password flag: + + ethereum --password account new + + `, + }, + { + Action: accountImport, + Name: "import", + Usage: "import a private key into a new account", + Description: ` + + ethereum account import + +Imports a private key from and creates a new account with the address derived from the key. +The keyfile is assumed to contain an unencrypted private key in canonical EC format. + +The account is saved in encrypted format, you are prompted for a passphrase. +You must remember this passphrase to unlock your account in future. +For non-interactive use the passphrase can be specified with the --password flag: + + ethereum --password account import + + `, + }, + { + Action: accountExport, + Name: "export", + Usage: "export an account into key file", + Description: ` + + ethereum account export
+ +Exports the given account's private key into keyfile using the canonical EC format. +The account needs to be unlocked, if it is not the user is prompted for a passphrase to unlock it. +For non-interactive use, the password can be specified with the --unlock flag: + + ethereum --unlock account export
+ +Note: +Since you can directly copy your encrypted accounts to another ethereum instance, this import/export mechanism is not needed when you transfer an account between nodes. + `, }, }, }, @@ -130,6 +181,7 @@ The Ethereum JavaScript VM exposes a node admin interface as well as the DAPP Ja } app.Flags = []cli.Flag{ utils.UnlockedAccountFlag, + utils.PasswordFileFlag, utils.BootnodesFlag, utils.DataDirFlag, utils.JSpathFlag, @@ -218,23 +270,43 @@ func execJSFiles(ctx *cli.Context) { ethereum.WaitForShutdown() } -func startEth(ctx *cli.Context, eth *eth.Ethereum) { - utils.StartEthereum(eth) +func unlockAccount(ctx *cli.Context, am *accounts.Manager, account string) (passphrase string) { + if !ctx.GlobalBool(utils.UnencryptedKeysFlag.Name) { + var err error + // Load startup keys. XXX we are going to need a different format + // Attempt to unlock the account + passfile := ctx.GlobalString(utils.PasswordFileFlag.Name) + if len(passfile) == 0 { + fmt.Println("Please enter a passphrase now.") + auth, err := readPassword("Passphrase: ", true) + if err != nil { + utils.Fatalf("%v", err) + } - // Load startup keys. XXX we are going to need a different format - account := ctx.GlobalString(utils.UnlockedAccountFlag.Name) - if len(account) > 0 { - split := strings.Split(account, ":") - if len(split) != 2 { - utils.Fatalf("Illegal 'unlock' format (address:password)") + passphrase = auth + + } else { + if passphrase, err = common.ReadAllFile(passfile); err != nil { + utils.Fatalf("Unable to read password file '%s': %v", passfile, err) + } } - am := eth.AccountManager() - // Attempt to unlock the account - err := am.Unlock(common.FromHex(split[0]), split[1]) + + err = am.Unlock(common.FromHex(account), passphrase) if err != nil { utils.Fatalf("Unlock account failed '%v'", err) } } + return +} + +func startEth(ctx *cli.Context, eth *eth.Ethereum) { + utils.StartEthereum(eth) + am := eth.AccountManager() + + account := ctx.GlobalString(utils.UnlockedAccountFlag.Name) + if len(account) > 0 { + unlockAccount(ctx, am, account) + } // Start auxiliary services if enabled. if ctx.GlobalBool(utils.RPCEnabledFlag.Name) { utils.StartRPC(eth, ctx) @@ -255,30 +327,74 @@ func accountList(ctx *cli.Context) { } } -func accountCreate(ctx *cli.Context) { - am := utils.GetAccountManager(ctx) - passphrase := "" +func getPassPhrase(ctx *cli.Context) (passphrase string) { if !ctx.GlobalBool(utils.UnencryptedKeysFlag.Name) { - fmt.Println("The new account will be encrypted with a passphrase.") - fmt.Println("Please enter a passphrase now.") - auth, err := readPassword("Passphrase: ", true) - if err != nil { - utils.Fatalf("%v", err) - } - confirm, err := readPassword("Repeat Passphrase: ", false) - if err != nil { - utils.Fatalf("%v", err) - } - if auth != confirm { - utils.Fatalf("Passphrases did not match.") + passfile := ctx.GlobalString(utils.PasswordFileFlag.Name) + if len(passfile) == 0 { + fmt.Println("The new account will be encrypted with a passphrase.") + fmt.Println("Please enter a passphrase now.") + auth, err := readPassword("Passphrase: ", true) + if err != nil { + utils.Fatalf("%v", err) + } + confirm, err := readPassword("Repeat Passphrase: ", false) + if err != nil { + utils.Fatalf("%v", err) + } + if auth != confirm { + utils.Fatalf("Passphrases did not match.") + } + passphrase = auth + + } else { + var err error + if passphrase, err = common.ReadAllFile(passfile); err != nil { + utils.Fatalf("Unable to read password file '%s': %v", passfile, err) + } } - passphrase = auth } + return +} + +func accountCreate(ctx *cli.Context) { + am := utils.GetAccountManager(ctx) + passphrase := getPassPhrase(ctx) acct, err := am.NewAccount(passphrase) if err != nil { utils.Fatalf("Could not create the account: %v", err) } - fmt.Printf("Address: %x\n", acct.Address) + fmt.Printf("Address: %x\n", acct) +} + +func accountImport(ctx *cli.Context) { + keyfile := ctx.Args().First() + if len(keyfile) == 0 { + utils.Fatalf("keyfile must be given as argument") + } + am := utils.GetAccountManager(ctx) + passphrase := getPassPhrase(ctx) + acct, err := am.Import(keyfile, passphrase) + if err != nil { + utils.Fatalf("Could not create the account: %v", err) + } + fmt.Printf("Address: %x\n", acct) +} + +func accountExport(ctx *cli.Context) { + account := ctx.Args().First() + if len(account) == 0 { + utils.Fatalf("account address must be given as first argument") + } + keyfile := ctx.Args().Get(1) + if len(keyfile) == 0 { + utils.Fatalf("keyfile must be given as second argument") + } + am := utils.GetAccountManager(ctx) + auth := unlockAccount(ctx, am, account) + err := am.Export(keyfile, common.FromHex(account), auth) + if err != nil { + utils.Fatalf("Account export failed: %v", err) + } } func importchain(ctx *cli.Context) { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 94b043d73..f94ec3a69 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -104,7 +104,13 @@ var ( } UnlockedAccountFlag = cli.StringFlag{ Name: "unlock", - Usage: "Unlock a given account untill this programs exits (address:password)", + Usage: "unlock the account given until this program exits (prompts for password).", + Value: "", + } + PasswordFileFlag = cli.StringFlag{ + Name: "password", + Usage: "Password used when saving a new account and unlocking an existing account. If you create a new account make sure you remember this password.", + Value: "", } // logging and debug settings -- cgit