diff --git a/accounts/account_manager.go b/accounts/account_manager.go
index eb2672a7d..17b128e9e 100644
--- a/accounts/account_manager.go
+++ b/accounts/account_manager.go
@@ -36,6 +36,7 @@ import (
"crypto/ecdsa"
crand "crypto/rand"
"errors"
+ "fmt"
"os"
"sync"
"time"
@@ -158,6 +159,20 @@ func (am *Manager) NewAccount(auth string) (Account, error) {
return Account{Address: key.Address}, nil
}
+func (am *Manager) AddressByIndex(index int) (addr string, err error) {
+ var addrs []common.Address
+ addrs, err = am.keyStore.GetKeyAddresses()
+ if err != nil {
+ return
+ }
+ if index < 0 || index >= len(addrs) {
+ err = fmt.Errorf("index out of range: %d (should be 0-%d)", index, len(addrs)-1)
+ } else {
+ addr = addrs[index].Hex()
+ }
+ return
+}
+
func (am *Manager) Accounts() ([]Account, error) {
addresses, err := am.keyStore.GetKeyAddresses()
if os.IsNotExist(err) {
@@ -204,6 +219,19 @@ func (am *Manager) Import(path string, keyAuth string) (Account, error) {
return Account{Address: key.Address}, nil
}
+func (am *Manager) Update(addr common.Address, authFrom, authTo string) (err error) {
+ var key *crypto.Key
+ key, err = am.keyStore.GetKey(addr, authFrom)
+
+ if err == nil {
+ err = am.keyStore.StoreKey(key, authTo)
+ if err == nil {
+ am.keyStore.Cleanup(addr)
+ }
+ }
+ return
+}
+
func (am *Manager) ImportPreSaleKey(keyJSON []byte, password string) (acc Account, err error) {
var key *crypto.Key
key, err = crypto.ImportPreSaleKey(am.keyStore, keyJSON, password)
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index 673a08d45..ffd26a7c2 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -189,6 +189,33 @@ Note, this is meant to be used for testing only, it is a bad idea to save your
password to file or expose in any other way.
`,
},
+ {
+ Action: accountUpdate,
+ Name: "update",
+ Usage: "update an existing account",
+ Description: `
+
+ ethereum account update
+
+Update an existing account.
+
+The account is saved in the newest version in encrypted format, you are prompted
+for a passphrase to unlock the account and another to save the updated file.
+
+This same command can therefore be used to migrate an account of a deprecated
+format to the newest format or change the password for an account.
+
+For non-interactive use the passphrase can be specified with the --password flag:
+
+ ethereum --password account new
+
+Since only one password can be given, only format update can be performed,
+changing your password is only possible interactively.
+
+Note that account update has the a side effect that the order of your accounts
+changes.
+ `,
+ },
{
Action: accountImport,
Name: "import",
@@ -433,19 +460,30 @@ func execJSFiles(ctx *cli.Context) {
ethereum.WaitForShutdown()
}
-func unlockAccount(ctx *cli.Context, am *accounts.Manager, account string, i int) {
+func unlockAccount(ctx *cli.Context, am *accounts.Manager, addr string, i int) (addrHex, auth string) {
var err error
// Load startup keys. XXX we are going to need a different format
- if !((len(account) == 40) || (len(account) == 42)) { // with or without 0x
- utils.Fatalf("Invalid account address '%s'", account)
+ if !((len(addr) == 40) || (len(addr) == 42)) { // with or without 0x
+ var index int
+ index, err = strconv.Atoi(addr)
+ if err != nil {
+ utils.Fatalf("Invalid account address '%s'", addr)
+ }
+
+ addrHex, err = am.AddressByIndex(index)
+ if err != nil {
+ utils.Fatalf("%v", err)
+ }
+ } else {
+ addrHex = addr
}
// Attempt to unlock the account 3 times
attempts := 3
for tries := 0; tries < attempts; tries++ {
- msg := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", account, tries+1, attempts)
- passphrase := getPassPhrase(ctx, msg, false, i)
- err = am.Unlock(common.HexToAddress(account), passphrase)
+ msg := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", addr, tries+1, attempts)
+ auth = getPassPhrase(ctx, msg, false, i)
+ err = am.Unlock(common.HexToAddress(addrHex), auth)
if err == nil {
break
}
@@ -453,7 +491,8 @@ func unlockAccount(ctx *cli.Context, am *accounts.Manager, account string, i int
if err != nil {
utils.Fatalf("Unlock account failed '%v'", err)
}
- fmt.Printf("Account '%s' unlocked.\n", account)
+ fmt.Printf("Account '%s' unlocked.\n", addr)
+ return
}
func blockRecovery(ctx *cli.Context) {
@@ -578,6 +617,21 @@ func accountCreate(ctx *cli.Context) {
fmt.Printf("Address: %x\n", acct)
}
+func accountUpdate(ctx *cli.Context) {
+ am := utils.MakeAccountManager(ctx)
+ arg := ctx.Args().First()
+ if len(arg) == 0 {
+ utils.Fatalf("account address or index must be given as argument")
+ }
+
+ addr, authFrom := unlockAccount(ctx, am, arg, 0)
+ authTo := getPassPhrase(ctx, "Please give a new password. Do not forget this password.", true, 0)
+ err := am.Update(common.HexToAddress(addr), authFrom, authTo)
+ if err != nil {
+ utils.Fatalf("Could not update the account: %v", err)
+ }
+}
+
func importWallet(ctx *cli.Context) {
keyfile := ctx.Args().First()
if len(keyfile) == 0 {
diff --git a/crypto/key_store_passphrase.go b/crypto/key_store_passphrase.go
index d26e3407f..47909bc76 100644
--- a/crypto/key_store_passphrase.go
+++ b/crypto/key_store_passphrase.go
@@ -72,16 +72,19 @@ func (ks keyStorePassphrase) GenerateNewKey(rand io.Reader, auth string) (key *K
}
func (ks keyStorePassphrase) GetKey(keyAddr common.Address, auth string) (key *Key, err error) {
- keyBytes, keyId, err := decryptKeyFromFile(ks, keyAddr, auth)
- if err != nil {
- return nil, err
+ keyBytes, keyId, err := decryptKeyFromFile(ks.keysDirPath, keyAddr, auth)
+ if err == nil {
+ key = &Key{
+ Id: uuid.UUID(keyId),
+ Address: keyAddr,
+ PrivateKey: ToECDSA(keyBytes),
+ }
}
- key = &Key{
- Id: uuid.UUID(keyId),
- Address: keyAddr,
- PrivateKey: ToECDSA(keyBytes),
- }
- return key, err
+ return
+}
+
+func (ks keyStorePassphrase) Cleanup(keyAddr common.Address) (err error) {
+ return cleanup(ks.keysDirPath, keyAddr)
}
func (ks keyStorePassphrase) GetKeyAddresses() (addresses []common.Address, err error) {
@@ -142,7 +145,7 @@ func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) {
func (ks keyStorePassphrase) DeleteKey(keyAddr common.Address, auth string) (err error) {
// only delete if correct passphrase is given
- _, _, err = decryptKeyFromFile(ks, keyAddr, auth)
+ _, _, err = decryptKeyFromFile(ks.keysDirPath, keyAddr, auth)
if err != nil {
return err
}
@@ -150,25 +153,25 @@ func (ks keyStorePassphrase) DeleteKey(keyAddr common.Address, auth string) (err
return deleteKey(ks.keysDirPath, keyAddr)
}
-func decryptKeyFromFile(ks keyStorePassphrase, keyAddr common.Address, auth string) (keyBytes []byte, keyId []byte, err error) {
+func decryptKeyFromFile(keysDirPath string, keyAddr common.Address, auth string) (keyBytes []byte, keyId []byte, err error) {
+ fmt.Printf("%v\n", keyAddr.Hex())
m := make(map[string]interface{})
- err = getKey(ks.keysDirPath, keyAddr, &m)
+ err = getKey(keysDirPath, keyAddr, &m)
if err != nil {
- fmt.Printf("get key error: %v\n", err)
return
}
v := reflect.ValueOf(m["version"])
if v.Kind() == reflect.String && v.String() == "1" {
k := new(encryptedKeyJSONV1)
- getKey(ks.keysDirPath, keyAddr, &k)
+ err = getKey(keysDirPath, keyAddr, &k)
if err != nil {
return
}
return decryptKeyV1(k, auth)
} else {
k := new(encryptedKeyJSONV3)
- getKey(ks.keysDirPath, keyAddr, &k)
+ err = getKey(keysDirPath, keyAddr, &k)
if err != nil {
return
}
diff --git a/crypto/key_store_plain.go b/crypto/key_store_plain.go
index d785fdf68..c13c5e7a4 100644
--- a/crypto/key_store_plain.go
+++ b/crypto/key_store_plain.go
@@ -43,6 +43,7 @@ type KeyStore interface {
GetKeyAddresses() ([]common.Address, error) // get all addresses
StoreKey(*Key, string) error // store key optionally using auth string
DeleteKey(common.Address, string) error // delete key by addr and auth string
+ Cleanup(keyAddr common.Address) (err error)
}
type keyStorePlain struct {
@@ -86,6 +87,10 @@ func (ks keyStorePlain) GetKeyAddresses() (addresses []common.Address, err error
return getKeyAddresses(ks.keysDirPath)
}
+func (ks keyStorePlain) Cleanup(keyAddr common.Address) (err error) {
+ return cleanup(ks.keysDirPath, keyAddr)
+}
+
func (ks keyStorePlain) StoreKey(key *Key, auth string) (err error) {
keyJSON, err := json.Marshal(key)
if err != nil {
@@ -100,10 +105,14 @@ func (ks keyStorePlain) DeleteKey(keyAddr common.Address, auth string) (err erro
}
func deleteKey(keysDirPath string, keyAddr common.Address) (err error) {
- var keyFilePath string
- keyFilePath, err = getKeyFilePath(keysDirPath, keyAddr)
+ var path string
+ path, err = getKeyFilePath(keysDirPath, keyAddr)
if err == nil {
- err = os.Remove(keyFilePath)
+ addrHex := hex.EncodeToString(keyAddr[:])
+ if path == filepath.Join(keysDirPath, addrHex, addrHex) {
+ path = filepath.Join(keysDirPath, addrHex)
+ }
+ err = os.RemoveAll(path)
}
return
}
@@ -122,6 +131,36 @@ func getKeyFilePath(keysDirPath string, keyAddr common.Address) (keyFilePath str
return
}
+func cleanup(keysDirPath string, keyAddr common.Address) (err error) {
+ fileInfos, err := ioutil.ReadDir(keysDirPath)
+ if err != nil {
+ return
+ }
+ var paths []string
+ account := hex.EncodeToString(keyAddr[:])
+ for _, fileInfo := range fileInfos {
+ path := filepath.Join(keysDirPath, fileInfo.Name())
+ if len(path) >= 40 {
+ addr := path[len(path)-40 : len(path)]
+ if addr == account {
+ if path == filepath.Join(keysDirPath, addr, addr) {
+ path = filepath.Join(keysDirPath, addr)
+ }
+ paths = append(paths, path)
+ }
+ }
+ }
+ if len(paths) > 1 {
+ for i := 0; err == nil && i < len(paths)-1; i++ {
+ err = os.RemoveAll(paths[i])
+ if err != nil {
+ break
+ }
+ }
+ }
+ return
+}
+
func getKeyFile(keysDirPath string, keyAddr common.Address) (fileContent []byte, err error) {
var keyFilePath string
keyFilePath, err = getKeyFilePath(keysDirPath, keyAddr)