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)
This commit is contained in:
zelig 2015-03-23 13:00:06 +00:00
parent 658204bafc
commit c4ea921876
5 changed files with 190 additions and 39 deletions

View File

@ -208,3 +208,23 @@ func zeroKey(k *ecdsa.PrivateKey) {
b[i] = 0 b[i] = 0
} }
} }
func (am *Manager) Export(path string, addr []byte, keyAuth string) error {
key, err := am.keyStore.GetKey(addr, keyAuth)
if err != nil {
return err
}
return crypto.SaveECDSA(path, key.PrivateKey)
}
func (am *Manager) Import(path string, keyAuth string) (Account, error) {
privateKeyECDSA, err := crypto.LoadECDSA(path)
if err != nil {
return Account{}, err
}
key := crypto.NewKeyFromECDSA(privateKeyECDSA)
if err = am.keyStore.StoreKey(key, keyAuth); err != nil {
return Account{}, err
}
return Account{Address: key.Address}, nil
}

View File

@ -26,11 +26,11 @@ import (
"os" "os"
"runtime" "runtime"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/ethereum/ethash" "github.com/ethereum/ethash"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
@ -83,11 +83,62 @@ The output of this command is supposed to be machine-readable.
Action: accountList, Action: accountList,
Name: "list", Name: "list",
Usage: "print account addresses", Usage: "print account addresses",
Description: `
`,
}, },
{ {
Action: accountCreate, Action: accountCreate,
Name: "new", Name: "new",
Usage: "create a new account", 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 <passwordfile> account new
`,
},
{
Action: accountImport,
Name: "import",
Usage: "import a private key into a new account",
Description: `
ethereum account import <keyfile>
Imports a private key from <keyfile> 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 <passwordfile> account import <keyfile>
`,
},
{
Action: accountExport,
Name: "export",
Usage: "export an account into key file",
Description: `
ethereum account export <address> <keyfile>
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 <passwrdfile> account export <address> <keyfile>
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{ app.Flags = []cli.Flag{
utils.UnlockedAccountFlag, utils.UnlockedAccountFlag,
utils.PasswordFileFlag,
utils.BootnodesFlag, utils.BootnodesFlag,
utils.DataDirFlag, utils.DataDirFlag,
utils.JSpathFlag, utils.JSpathFlag,
@ -218,23 +270,43 @@ func execJSFiles(ctx *cli.Context) {
ethereum.WaitForShutdown() ethereum.WaitForShutdown()
} }
func startEth(ctx *cli.Context, eth *eth.Ethereum) { func unlockAccount(ctx *cli.Context, am *accounts.Manager, account string) (passphrase string) {
utils.StartEthereum(eth) if !ctx.GlobalBool(utils.UnencryptedKeysFlag.Name) {
var err error
// Load startup keys. XXX we are going to need a different format // 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)")
}
am := eth.AccountManager()
// Attempt to unlock the account // Attempt to unlock the account
err := am.Unlock(common.FromHex(split[0]), split[1]) 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)
}
passphrase = auth
} else {
if passphrase, err = common.ReadAllFile(passfile); err != nil {
utils.Fatalf("Unable to read password file '%s': %v", passfile, err)
}
}
err = am.Unlock(common.FromHex(account), passphrase)
if err != nil { if err != nil {
utils.Fatalf("Unlock account failed '%v'", err) 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. // Start auxiliary services if enabled.
if ctx.GlobalBool(utils.RPCEnabledFlag.Name) { if ctx.GlobalBool(utils.RPCEnabledFlag.Name) {
utils.StartRPC(eth, ctx) utils.StartRPC(eth, ctx)
@ -255,30 +327,74 @@ func accountList(ctx *cli.Context) {
} }
} }
func getPassPhrase(ctx *cli.Context) (passphrase string) {
if !ctx.GlobalBool(utils.UnencryptedKeysFlag.Name) {
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)
}
}
}
return
}
func accountCreate(ctx *cli.Context) { func accountCreate(ctx *cli.Context) {
am := utils.GetAccountManager(ctx) am := utils.GetAccountManager(ctx)
passphrase := "" passphrase := getPassPhrase(ctx)
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.")
}
passphrase = auth
}
acct, err := am.NewAccount(passphrase) acct, err := am.NewAccount(passphrase)
if err != nil { if err != nil {
utils.Fatalf("Could not create the account: %v", err) 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) { func importchain(ctx *cli.Context) {

View File

@ -104,7 +104,13 @@ var (
} }
UnlockedAccountFlag = cli.StringFlag{ UnlockedAccountFlag = cli.StringFlag{
Name: "unlock", 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 // logging and debug settings

View File

@ -139,6 +139,11 @@ func LoadECDSA(file string) (*ecdsa.PrivateKey, error) {
return ToECDSA(buf), nil return ToECDSA(buf), nil
} }
// SaveECDSA saves a secp256k1 private key from the given file.
func SaveECDSA(file string, key *ecdsa.PrivateKey) error {
return common.WriteFile(file, FromECDSA(key))
}
func GenerateKey() (*ecdsa.PrivateKey, error) { func GenerateKey() (*ecdsa.PrivateKey, error) {
return ecdsa.GenerateKey(S256(), rand.Reader) return ecdsa.GenerateKey(S256(), rand.Reader)
} }

View File

@ -85,6 +85,16 @@ func (k *Key) UnmarshalJSON(j []byte) (err error) {
return err return err
} }
func NewKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key {
id := uuid.NewRandom()
key := &Key{
Id: id,
Address: PubkeyToAddress(privateKeyECDSA.PublicKey),
PrivateKey: privateKeyECDSA,
}
return key
}
func NewKey(rand io.Reader) *Key { func NewKey(rand io.Reader) *Key {
randBytes := make([]byte, 64) randBytes := make([]byte, 64)
_, err := rand.Read(randBytes) _, err := rand.Read(randBytes)
@ -97,11 +107,5 @@ func NewKey(rand io.Reader) *Key {
panic("key generation: ecdsa.GenerateKey failed: " + err.Error()) panic("key generation: ecdsa.GenerateKey failed: " + err.Error())
} }
id := uuid.NewRandom() return NewKeyFromECDSA(privateKeyECDSA)
key := &Key{
Id: id,
Address: PubkeyToAddress(privateKeyECDSA.PublicKey),
PrivateKey: privateKeyECDSA,
}
return key
} }