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:
parent
658204bafc
commit
c4ea921876
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue