aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--accounts/account_manager.go20
-rw-r--r--cmd/ethereum/main.go174
-rw-r--r--cmd/utils/flags.go8
-rw-r--r--crypto/crypto.go5
-rw-r--r--crypto/key.go18
5 files changed, 188 insertions, 37 deletions
diff --git a/accounts/account_manager.go b/accounts/account_manager.go
index 646dc8376..670d4337f 100644
--- a/accounts/account_manager.go
+++ b/accounts/account_manager.go
@@ -208,3 +208,23 @@ func zeroKey(k *ecdsa.PrivateKey) {
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
+}
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 <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{
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
diff --git a/crypto/crypto.go b/crypto/crypto.go
index c3d47b629..2d26dd25e 100644
--- a/crypto/crypto.go
+++ b/crypto/crypto.go
@@ -139,6 +139,11 @@ func LoadECDSA(file string) (*ecdsa.PrivateKey, error) {
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) {
return ecdsa.GenerateKey(S256(), rand.Reader)
}
diff --git a/crypto/key.go b/crypto/key.go
index 9dbf37467..0b84bfec1 100644
--- a/crypto/key.go
+++ b/crypto/key.go
@@ -85,6 +85,16 @@ func (k *Key) UnmarshalJSON(j []byte) (err error) {
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 {
randBytes := make([]byte, 64)
_, err := rand.Read(randBytes)
@@ -97,11 +107,5 @@ func NewKey(rand io.Reader) *Key {
panic("key generation: ecdsa.GenerateKey failed: " + err.Error())
}
- id := uuid.NewRandom()
- key := &Key{
- Id: id,
- Address: PubkeyToAddress(privateKeyECDSA.PublicKey),
- PrivateKey: privateKeyECDSA,
- }
- return key
+ return NewKeyFromECDSA(privateKeyECDSA)
}