From c4ea921876b0535022882c568b5cc6b0269db7d4 Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 23 Mar 2015 13:00:06 +0000 Subject: [PATCH 01/12] 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) --- accounts/account_manager.go | 20 ++++ cmd/ethereum/main.go | 178 +++++++++++++++++++++++++++++------- cmd/utils/flags.go | 8 +- crypto/crypto.go | 5 + crypto/key.go | 18 ++-- 5 files changed, 190 insertions(+), 39 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 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) - - // 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() +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 - 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 { 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 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) { am := utils.GetAccountManager(ctx) - passphrase := "" - 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 - } + 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) } From 859f1f08ca48de99408c825eba8d6ed4bfea3235 Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 23 Mar 2015 21:34:05 +0000 Subject: [PATCH 02/12] blockpool: wrap intermittent status test in a loop --- blockpool/status_test.go | 83 +++++++++++++++++++++++----------------- blockpool/test/util.go | 12 ++++-- 2 files changed, 55 insertions(+), 40 deletions(-) diff --git a/blockpool/status_test.go b/blockpool/status_test.go index cbaa8bb55..a87b99d7c 100644 --- a/blockpool/status_test.go +++ b/blockpool/status_test.go @@ -1,7 +1,7 @@ package blockpool import ( - // "fmt" + "fmt" "testing" "time" @@ -45,17 +45,15 @@ func getStatusValues(s *Status) []int { func checkStatus(t *testing.T, bp *BlockPool, syncing bool, expected []int) (err error) { s := bp.Status() if s.Syncing != syncing { - t.Errorf("status for Syncing incorrect. expected %v, got %v", syncing, s.Syncing) + err = fmt.Errorf("status for Syncing incorrect. expected %v, got %v", syncing, s.Syncing) + return } got := getStatusValues(s) for i, v := range expected { - if i == 0 || i == 7 { - continue //hack - } err = test.CheckInt(statusFields[i], got[i], v, t) // fmt.Printf("%v: %v (%v)\n", statusFields[i], got[i], v) if err != nil { - return err + return } } return @@ -63,6 +61,25 @@ func checkStatus(t *testing.T, bp *BlockPool, syncing bool, expected []int) (err func TestBlockPoolStatus(t *testing.T) { test.LogInit() + var err error + n := 3 + for n > 0 { + n-- + err = testBlockPoolStatus(t) + if err != nil { + t.Log(err) + continue + } else { + return + } + } + if err != nil { + t.Errorf("no pass out of 3: %v", err) + } +} + +func testBlockPoolStatus(t *testing.T) (err error) { + _, blockPool, blockPoolTester := newTestBlockPool(t) blockPoolTester.blockChain[0] = nil blockPoolTester.initRefBlockChain(12) @@ -70,6 +87,7 @@ func TestBlockPoolStatus(t *testing.T) { delete(blockPoolTester.refBlockChain, 6) blockPool.Start() + defer blockPool.Stop() blockPoolTester.tds = make(map[int]int) blockPoolTester.tds[9] = 1 blockPoolTester.tds[11] = 3 @@ -79,73 +97,67 @@ func TestBlockPoolStatus(t *testing.T) { peer2 := blockPoolTester.newPeer("peer2", 2, 6) peer3 := blockPoolTester.newPeer("peer3", 3, 11) peer4 := blockPoolTester.newPeer("peer4", 1, 9) - // peer1 := blockPoolTester.newPeer("peer1", 1, 9) - // peer2 := blockPoolTester.newPeer("peer2", 2, 6) - // peer3 := blockPoolTester.newPeer("peer3", 3, 11) - // peer4 := blockPoolTester.newPeer("peer4", 1, 9) peer2.blocksRequestsMap = peer1.blocksRequestsMap var expected []int - var err error expected = []int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - err = checkStatus(t, blockPool, false, expected) + err = checkStatus(nil, blockPool, false, expected) if err != nil { return } peer1.AddPeer() expected = []int{0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0} - err = checkStatus(t, blockPool, true, expected) + err = checkStatus(nil, blockPool, true, expected) if err != nil { return } peer1.serveBlocks(8, 9) - expected = []int{0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0} - // err = checkStatus(t, blockPool, true, expected) + expected = []int{1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0} + err = checkStatus(nil, blockPool, true, expected) if err != nil { return } peer1.serveBlockHashes(9, 8, 7, 3, 2) expected = []int{6, 5, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0} - // expected = []int{5, 5, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0} - err = checkStatus(t, blockPool, true, expected) + err = checkStatus(nil, blockPool, true, expected) if err != nil { return } peer1.serveBlocks(3, 7, 8) expected = []int{6, 5, 3, 3, 0, 1, 0, 0, 1, 1, 1, 1, 0} - err = checkStatus(t, blockPool, true, expected) + err = checkStatus(nil, blockPool, true, expected) if err != nil { return } peer1.serveBlocks(2, 3) expected = []int{6, 5, 4, 4, 0, 1, 0, 0, 1, 1, 1, 1, 0} - err = checkStatus(t, blockPool, true, expected) + err = checkStatus(nil, blockPool, true, expected) if err != nil { return } peer4.AddPeer() expected = []int{6, 5, 4, 4, 0, 2, 0, 0, 2, 2, 1, 1, 0} - err = checkStatus(t, blockPool, true, expected) + err = checkStatus(nil, blockPool, true, expected) if err != nil { return } peer4.sendBlockHashes(12, 11) expected = []int{6, 5, 4, 4, 0, 2, 0, 0, 2, 2, 1, 1, 0} - err = checkStatus(t, blockPool, true, expected) + err = checkStatus(nil, blockPool, true, expected) if err != nil { return } peer2.AddPeer() expected = []int{6, 5, 4, 4, 0, 3, 0, 0, 3, 3, 1, 2, 0} - err = checkStatus(t, blockPool, true, expected) + err = checkStatus(nil, blockPool, true, expected) if err != nil { return } @@ -153,76 +165,76 @@ func TestBlockPoolStatus(t *testing.T) { peer2.serveBlocks(5, 6) peer2.serveBlockHashes(6, 5, 4, 3, 2) expected = []int{10, 8, 5, 5, 0, 3, 1, 0, 3, 3, 2, 2, 0} - err = checkStatus(t, blockPool, true, expected) + err = checkStatus(nil, blockPool, true, expected) if err != nil { return } peer2.serveBlocks(2, 3, 4) expected = []int{10, 8, 6, 6, 0, 3, 1, 0, 3, 3, 2, 2, 0} - err = checkStatus(t, blockPool, true, expected) + err = checkStatus(nil, blockPool, true, expected) if err != nil { return } blockPool.RemovePeer("peer2") expected = []int{10, 8, 6, 6, 0, 3, 1, 0, 3, 2, 2, 2, 0} - err = checkStatus(t, blockPool, true, expected) + err = checkStatus(nil, blockPool, true, expected) if err != nil { return } peer1.serveBlockHashes(2, 1, 0) expected = []int{11, 9, 6, 6, 0, 3, 1, 0, 3, 2, 2, 2, 0} - err = checkStatus(t, blockPool, true, expected) + err = checkStatus(nil, blockPool, true, expected) if err != nil { return } peer1.serveBlocks(1, 2) expected = []int{11, 9, 7, 7, 0, 3, 1, 0, 3, 2, 2, 2, 0} - err = checkStatus(t, blockPool, true, expected) + err = checkStatus(nil, blockPool, true, expected) if err != nil { return } peer1.serveBlocks(4, 5) expected = []int{11, 9, 8, 8, 0, 3, 1, 0, 3, 2, 2, 2, 0} - err = checkStatus(t, blockPool, true, expected) + err = checkStatus(nil, blockPool, true, expected) if err != nil { return } peer3.AddPeer() expected = []int{11, 9, 8, 8, 0, 4, 1, 0, 4, 3, 2, 3, 0} - err = checkStatus(t, blockPool, true, expected) + err = checkStatus(nil, blockPool, true, expected) if err != nil { return } peer3.serveBlocks(10, 11) expected = []int{12, 9, 9, 9, 0, 4, 1, 0, 4, 3, 3, 3, 0} - err = checkStatus(t, blockPool, true, expected) + err = checkStatus(nil, blockPool, true, expected) if err != nil { return } peer3.serveBlockHashes(11, 10, 9) expected = []int{14, 11, 9, 9, 0, 4, 1, 0, 4, 3, 3, 3, 0} - err = checkStatus(t, blockPool, true, expected) + err = checkStatus(nil, blockPool, true, expected) if err != nil { return } peer4.sendBlocks(11, 12) expected = []int{14, 11, 9, 9, 0, 4, 1, 0, 4, 3, 4, 3, 1} - err = checkStatus(t, blockPool, true, expected) + err = checkStatus(nil, blockPool, true, expected) if err != nil { return } peer3.serveBlocks(9, 10) expected = []int{14, 11, 10, 10, 0, 4, 1, 0, 4, 3, 4, 3, 1} - err = checkStatus(t, blockPool, true, expected) + err = checkStatus(nil, blockPool, true, expected) if err != nil { return } @@ -231,10 +243,9 @@ func TestBlockPoolStatus(t *testing.T) { blockPool.Wait(waitTimeout) time.Sleep(200 * time.Millisecond) expected = []int{14, 3, 11, 3, 8, 4, 1, 8, 4, 3, 4, 3, 1} - err = checkStatus(t, blockPool, false, expected) + err = checkStatus(nil, blockPool, false, expected) if err != nil { return } - - blockPool.Stop() + return nil } diff --git a/blockpool/test/util.go b/blockpool/test/util.go index 0349493c3..930601278 100644 --- a/blockpool/test/util.go +++ b/blockpool/test/util.go @@ -10,16 +10,20 @@ import ( func CheckInt(name string, got int, expected int, t *testing.T) (err error) { if got != expected { - t.Errorf("status for %v incorrect. expected %v, got %v", name, expected, got) - err = fmt.Errorf("") + err = fmt.Errorf("status for %v incorrect. expected %v, got %v", name, expected, got) + if t != nil { + t.Error(err) + } } return } func CheckDuration(name string, got time.Duration, expected time.Duration, t *testing.T) (err error) { if got != expected { - t.Errorf("status for %v incorrect. expected %v, got %v", name, expected, got) - err = fmt.Errorf("") + err = fmt.Errorf("status for %v incorrect. expected %v, got %v", name, expected, got) + if t != nil { + t.Error(err) + } } return } From fd8d18ec280c3fe2c3d2651870c31c65b02039ba Mon Sep 17 00:00:00 2001 From: zelig Date: Tue, 24 Mar 2015 12:37:00 +0000 Subject: [PATCH 03/12] unlocking coinbase - extract accounts.getKey method - if given empty address it retrieves coinbase (first account) - cli -unlock coinbase will unlock coinbase --- accounts/account_manager.go | 15 +++++++++++++-- cmd/ethereum/main.go | 5 ++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 670d4337f..21ef46991 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -101,6 +101,17 @@ func (am *Manager) firstAddr() ([]byte, error) { return addrs[0], nil } +func (am *Manager) getKey(addr []byte, keyAuth string) (*crypto.Key, error) { + if len(addr) == 0 { + var err error + addr, err = am.firstAddr() + if err != nil { + return nil, err + } + } + return am.keyStore.GetKey(addr, keyAuth) +} + func (am *Manager) DeleteAccount(address []byte, auth string) error { return am.keyStore.DeleteKey(address, auth) } @@ -119,7 +130,7 @@ func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error) // TimedUnlock unlocks the account with the given address. // When timeout has passed, the account will be locked again. func (am *Manager) TimedUnlock(addr []byte, keyAuth string, timeout time.Duration) error { - key, err := am.keyStore.GetKey(addr, keyAuth) + key, err := am.getKey(addr, keyAuth) if err != nil { return err } @@ -132,7 +143,7 @@ func (am *Manager) TimedUnlock(addr []byte, keyAuth string, timeout time.Duratio // stays unlocked until the program exits or until a TimedUnlock // timeout (started after the call to Unlock) expires. func (am *Manager) Unlock(addr []byte, keyAuth string) error { - key, err := am.keyStore.GetKey(addr, keyAuth) + key, err := am.getKey(addr, keyAuth) if err != nil { return err } diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 276480195..fea3fbf61 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -134,7 +134,7 @@ Exports the given account's private key into keyfile using the canonical EC form 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
+ ethereum --password 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. @@ -305,6 +305,9 @@ func startEth(ctx *cli.Context, eth *eth.Ethereum) { account := ctx.GlobalString(utils.UnlockedAccountFlag.Name) if len(account) > 0 { + if account == "coinbase" { + account = "" + } unlockAccount(ctx, am, account) } // Start auxiliary services if enabled. From 1c4c71dcff442e3ae30e510fef312d3c05341f30 Mon Sep 17 00:00:00 2001 From: zelig Date: Tue, 24 Mar 2015 14:09:06 +0000 Subject: [PATCH 04/12] cli: fix liner not closing (spuriously opened) in noninteractive jsre --- cmd/ethereum/js.go | 4 ++-- cmd/ethereum/main.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/ethereum/js.go b/cmd/ethereum/js.go index 1f0033daa..599af0a16 100644 --- a/cmd/ethereum/js.go +++ b/cmd/ethereum/js.go @@ -67,14 +67,14 @@ type jsre struct { prompter } -func newJSRE(ethereum *eth.Ethereum, libPath string) *jsre { +func newJSRE(ethereum *eth.Ethereum, libPath string, interactive bool) *jsre { js := &jsre{ethereum: ethereum, ps1: "> "} js.xeth = xeth.New(ethereum, js) js.re = re.New(libPath) js.apiBindings() js.adminBindings() - if !liner.TerminalSupported() { + if !liner.TerminalSupported() || !interactive { js.prompter = dumbterm{bufio.NewReader(os.Stdin)} } else { lr := liner.NewLiner() diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index fea3fbf61..59c6ef485 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -246,7 +246,7 @@ func console(ctx *cli.Context) { } startEth(ctx, ethereum) - repl := newJSRE(ethereum, ctx.String(utils.JSpathFlag.Name)) + repl := newJSRE(ethereum, ctx.String(utils.JSpathFlag.Name), true) repl.interactive() ethereum.Stop() @@ -261,7 +261,7 @@ func execJSFiles(ctx *cli.Context) { } startEth(ctx, ethereum) - repl := newJSRE(ethereum, ctx.String(utils.JSpathFlag.Name)) + repl := newJSRE(ethereum, ctx.String(utils.JSpathFlag.Name), false) for _, file := range ctx.Args() { repl.exec(file) } From 34d5a6c156a014ce000b4f850f2b0f11533387f0 Mon Sep 17 00:00:00 2001 From: zelig Date: Tue, 24 Mar 2015 16:05:27 +0000 Subject: [PATCH 05/12] cli: help formatting --- cmd/ethereum/main.go | 31 ++++++++++++++++++------------- cmd/utils/flags.go | 2 +- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 59c6ef485..39a0a9d7f 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -83,9 +83,6 @@ The output of this command is supposed to be machine-readable. Action: accountList, Name: "list", Usage: "print account addresses", - Description: ` - -`, }, { Action: accountCreate, @@ -111,12 +108,14 @@ For non-interactive use the passphrase can be specified with the --password flag 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. +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: +For non-interactive use the passphrase can be specified with the -password flag: ethereum --password account import @@ -130,14 +129,18 @@ For non-interactive use the passphrase can be specified with the --password flag 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: +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 passphrase can be specified with the --unlock flag: ethereum --password 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. +As 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. `, }, }, @@ -156,16 +159,18 @@ Use "ethereum dump 0" to dump the genesis block. Name: "console", Usage: `Ethereum Console: interactive JavaScript environment`, Description: ` -Console is an interactive shell for the Ethereum JavaScript runtime environment which exposes a node admin interface as well as the DAPP JavaScript API. +Console is an interactive shell for the Ethereum JavaScript runtime environment +which exposes a node admin interface as well as the DAPP JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Frontier-Console `, }, { Action: execJSFiles, Name: "js", - Usage: `executes the given JavaScript files in the Ethereum Frontier JavaScript VM`, + Usage: `executes the given JavaScript files in the Ethereum JavaScript VM`, Description: ` -The Ethereum JavaScript VM exposes a node admin interface as well as the DAPP JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Frontier-Console +The Ethereum JavaScript VM exposes a node admin interface as well as the DAPP +JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console `, }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index f94ec3a69..dda409502 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -109,7 +109,7 @@ var ( } 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.", + Usage: "Path to password file for (un)locking an existing account.", Value: "", } From d1b52efdb581ca90613d2047b974d3a128f9bc58 Mon Sep 17 00:00:00 2001 From: zelig Date: Tue, 24 Mar 2015 16:19:11 +0000 Subject: [PATCH 06/12] cli: implement ethereum presale wallet import via cli --- accounts/account_manager.go | 12 ++++++ cmd/ethereum/main.go | 74 +++++++++++++++++++++++-------------- 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 21ef46991..f063f8ca5 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -239,3 +239,15 @@ func (am *Manager) Import(path string, keyAuth string) (Account, error) { } return Account{Address: key.Address}, nil } + +func (am *Manager) ImportPreSaleKey(keyJSON []byte, password string) (acc Account, err error) { + var key *crypto.Key + key, err = crypto.ImportPreSaleKey(am.keyStore, keyJSON, password) + if err != nil { + return + } + if err = am.keyStore.StoreKey(key, password); err != nil { + return + } + return Account{Address: key.Address}, nil +} diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 39a0a9d7f..57729b206 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -23,6 +23,7 @@ package main import ( "bufio" "fmt" + "io/ioutil" "os" "runtime" "strconv" @@ -74,6 +75,19 @@ Regular users do not need to execute it. The output of this command is supposed to be machine-readable. `, }, + + { + Action: accountList, + Name: "wallet", + Usage: "ethereum presale wallet", + Subcommands: []cli.Command{ + { + Action: importWallet, + Name: "import", + Usage: "import ethereum presale wallet", + }, + }, + }, { Action: accountList, Name: "account", @@ -280,22 +294,7 @@ func unlockAccount(ctx *cli.Context, am *accounts.Manager, account string) (pass 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) - } - - passphrase = auth - - } else { - if passphrase, err = common.ReadAllFile(passfile); err != nil { - utils.Fatalf("Unable to read password file '%s': %v", passfile, err) - } - } - + passphrase := getPassPhrase(ctx, "", false) err = am.Unlock(common.FromHex(account), passphrase) if err != nil { utils.Fatalf("Unlock account failed '%v'", err) @@ -335,22 +334,23 @@ func accountList(ctx *cli.Context) { } } -func getPassPhrase(ctx *cli.Context) (passphrase string) { +func getPassPhrase(ctx *cli.Context, desc string, confirmation bool) (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.") + fmt.Println(desc) 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.") + if confirmation { + confirm, err := readPassword("Repeat Passphrase: ", false) + if err != nil { + utils.Fatalf("%v", err) + } + if auth != confirm { + utils.Fatalf("Passphrases did not match.") + } } passphrase = auth @@ -366,7 +366,7 @@ func getPassPhrase(ctx *cli.Context) (passphrase string) { func accountCreate(ctx *cli.Context) { am := utils.GetAccountManager(ctx) - passphrase := getPassPhrase(ctx) + passphrase := getPassPhrase(ctx, "Your new account is locked with a password. Please give a password. Do not forget this password.", true) acct, err := am.NewAccount(passphrase) if err != nil { utils.Fatalf("Could not create the account: %v", err) @@ -374,13 +374,33 @@ func accountCreate(ctx *cli.Context) { fmt.Printf("Address: %x\n", acct) } +func importWallet(ctx *cli.Context) { + keyfile := ctx.Args().First() + if len(keyfile) == 0 { + utils.Fatalf("keyfile must be given as argument") + } + keyJson, err := ioutil.ReadFile(keyfile) + if err != nil { + utils.Fatalf("Could not read wallet file: %v", err) + } + + am := utils.GetAccountManager(ctx) + passphrase := getPassPhrase(ctx, "", false) + + acct, err := am.ImportPreSaleKey(keyJson, passphrase) + if err != nil { + utils.Fatalf("Could not create the account: %v", err) + } + 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) + passphrase := getPassPhrase(ctx, "Your new account is locked with a password. Please give a password. Do not forget this password.", true) acct, err := am.Import(keyfile, passphrase) if err != nil { utils.Fatalf("Could not create the account: %v", err) From fee224f07582a3f4c74f214347a89061ce75d2a1 Mon Sep 17 00:00:00 2001 From: zelig Date: Tue, 24 Mar 2015 21:53:46 +0000 Subject: [PATCH 07/12] cli test: fix test newJSRE interactive argument --- cmd/ethereum/js_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/ethereum/js_test.go b/cmd/ethereum/js_test.go index a6058b318..580bc7a2b 100644 --- a/cmd/ethereum/js_test.go +++ b/cmd/ethereum/js_test.go @@ -47,7 +47,7 @@ func testJEthRE(t *testing.T) (repl *jsre, ethereum *eth.Ethereum, err error) { return } assetPath := path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist", "assets", "ext") - repl = newJSRE(ethereum, assetPath) + repl = newJSRE(ethereum, assetPath, false) return } From 23e41a57ad7e7cb4bc5a1cbad28bbf8d65907fdd Mon Sep 17 00:00:00 2001 From: zelig Date: Wed, 25 Mar 2015 10:41:36 +0000 Subject: [PATCH 08/12] Applying: fix adming js test regression (maybe otto update?) --- cmd/ethereum/js.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/ethereum/js.go b/cmd/ethereum/js.go index 599af0a16..8e88a1c54 100644 --- a/cmd/ethereum/js.go +++ b/cmd/ethereum/js.go @@ -102,7 +102,7 @@ func (js *jsre) apiBindings() { jethObj := t.Object() jethObj.Set("send", jeth.Send) - err := js.re.Compile("bignum.js", re.BigNumber_JS) + err := js.re.Compile("bignumber.js", re.BigNumber_JS) if err != nil { utils.Fatalf("Error loading bignumber.js: %v", err) } From 4ec38e39320ee9abccd96da765a9c65fccd04151 Mon Sep 17 00:00:00 2001 From: zelig Date: Wed, 25 Mar 2015 14:58:52 +0000 Subject: [PATCH 09/12] common: remove WriteFile and ReadAllFile (use ioutil instead) --- cmd/ethereum/js_test.go | 6 +++--- cmd/ethereum/main.go | 5 +++-- cmd/mist/bindings.go | 7 +++--- cmd/mist/gui.go | 5 +++-- common/path.go | 30 -------------------------- common/path_test.go | 47 +---------------------------------------- crypto/crypto.go | 6 ++++-- jsre/jsre_test.go | 8 +++---- 8 files changed, 22 insertions(+), 92 deletions(-) diff --git a/cmd/ethereum/js_test.go b/cmd/ethereum/js_test.go index 580bc7a2b..5b962f621 100644 --- a/cmd/ethereum/js_test.go +++ b/cmd/ethereum/js_test.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "io/ioutil" "os" "path" "testing" @@ -9,7 +10,6 @@ import ( "github.com/robertkrimen/otto" "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" ) @@ -30,8 +30,8 @@ func testJEthRE(t *testing.T) (repl *jsre, ethereum *eth.Ethereum, err error) { } // FIXME: this does not work ATM ks := crypto.NewKeyStorePlain("/tmp/eth/keys") - common.WriteFile("/tmp/eth/keys/e273f01c99144c438695e10f24926dc1f9fbf62d/e273f01c99144c438695e10f24926dc1f9fbf62d", - []byte(`{"Id":"RhRXD+fNRKS4jx+7ZfEsNA==","Address":"4nPwHJkUTEOGleEPJJJtwfn79i0=","PrivateKey":"h4ACVpe74uIvi5Cg/2tX/Yrm2xdr3J7QoMbMtNX2CNc="}`)) + ioutil.WriteFile("/tmp/eth/keys/e273f01c99144c438695e10f24926dc1f9fbf62d/e273f01c99144c438695e10f24926dc1f9fbf62d", + []byte(`{"Id":"RhRXD+fNRKS4jx+7ZfEsNA==","Address":"4nPwHJkUTEOGleEPJJJtwfn79i0=","PrivateKey":"h4ACVpe74uIvi5Cg/2tX/Yrm2xdr3J7QoMbMtNX2CNc="}`), os.ModePerm) port++ ethereum, err = eth.New(ð.Config{ diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 57729b206..6bbe1044f 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -355,10 +355,11 @@ func getPassPhrase(ctx *cli.Context, desc string, confirmation bool) (passphrase passphrase = auth } else { - var err error - if passphrase, err = common.ReadAllFile(passfile); err != nil { + passbytes, err := ioutil.ReadFile(passfile) + if err != nil { utils.Fatalf("Unable to read password file '%s': %v", passfile, err) } + passphrase = string(passbytes) } } return diff --git a/cmd/mist/bindings.go b/cmd/mist/bindings.go index 8a9ec7cb1..e7ce50c35 100644 --- a/cmd/mist/bindings.go +++ b/cmd/mist/bindings.go @@ -22,13 +22,14 @@ package main import ( "encoding/json" + "io/ioutil" "os" "strconv" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" ) type plugin struct { @@ -46,14 +47,14 @@ func (self *Gui) AddPlugin(pluginPath string) { self.plugins[pluginPath] = plugin{Name: pluginPath, Path: pluginPath} json, _ := json.MarshalIndent(self.plugins, "", " ") - common.WriteFile(self.eth.DataDir+"/plugins.json", json) + ioutil.WriteFile(self.eth.DataDir+"/plugins.json", json, os.ModePerm) } func (self *Gui) RemovePlugin(pluginPath string) { delete(self.plugins, pluginPath) json, _ := json.MarshalIndent(self.plugins, "", " ") - common.WriteFile(self.eth.DataDir+"/plugins.json", json) + ioutil.WriteFile(self.eth.DataDir+"/plugins.json", json, os.ModePerm) } func (self *Gui) DumpState(hash, path string) { diff --git a/cmd/mist/gui.go b/cmd/mist/gui.go index 08f02f833..d37d6f81b 100644 --- a/cmd/mist/gui.go +++ b/cmd/mist/gui.go @@ -25,6 +25,7 @@ import "C" import ( "encoding/json" "fmt" + "io/ioutil" "math/big" "path" "runtime" @@ -91,8 +92,8 @@ func NewWindow(ethereum *eth.Ethereum) *Gui { plugins: make(map[string]plugin), serviceEvents: make(chan ServEv, 1), } - data, _ := common.ReadAllFile(path.Join(ethereum.DataDir, "plugins.json")) - json.Unmarshal([]byte(data), &gui.plugins) + data, _ := ioutil.ReadFile(path.Join(ethereum.DataDir, "plugins.json")) + json.Unmarshal(data, &gui.plugins) return gui } diff --git a/common/path.go b/common/path.go index d38b1fd5b..a74a0d5bd 100644 --- a/common/path.go +++ b/common/path.go @@ -2,7 +2,6 @@ package common import ( "fmt" - "io/ioutil" "os" "os/user" "path" @@ -43,35 +42,6 @@ func FileExist(filePath string) bool { return true } -func ReadAllFile(filePath string) (string, error) { - file, err := os.Open(filePath) - if err != nil { - return "", err - } - - data, err := ioutil.ReadAll(file) - if err != nil { - return "", err - } - - return string(data), nil -} - -func WriteFile(filePath string, content []byte) error { - fh, err := os.OpenFile(filePath, os.O_TRUNC|os.O_RDWR|os.O_CREATE, os.ModePerm) - if err != nil { - return err - } - defer fh.Close() - - _, err = fh.Write(content) - if err != nil { - return err - } - - return nil -} - func AbsolutePath(Datadir string, filename string) string { if path.IsAbs(filename) { return filename diff --git a/common/path_test.go b/common/path_test.go index c831d1a57..4b90c543b 100644 --- a/common/path_test.go +++ b/common/path_test.go @@ -2,56 +2,11 @@ package common import ( "os" - "testing" + // "testing" checker "gopkg.in/check.v1" ) -func TestGoodFile(t *testing.T) { - goodpath := "~/goethereumtest.pass" - path := ExpandHomePath(goodpath) - contentstring := "3.14159265358979323846" - - err := WriteFile(path, []byte(contentstring)) - if err != nil { - t.Error("Could not write file") - } - - if !FileExist(path) { - t.Error("File not found at", path) - } - - v, err := ReadAllFile(path) - if err != nil { - t.Error("Could not read file", path) - } - if v != contentstring { - t.Error("Expected", contentstring, "Got", v) - } - -} - -func TestBadFile(t *testing.T) { - badpath := "/this/path/should/not/exist/goethereumtest.fail" - path := ExpandHomePath(badpath) - contentstring := "3.14159265358979323846" - - err := WriteFile(path, []byte(contentstring)) - if err == nil { - t.Error("Wrote file, but should not be able to", path) - } - - if FileExist(path) { - t.Error("Found file, but should not be able to", path) - } - - v, err := ReadAllFile(path) - if err == nil { - t.Error("Read file, but should not be able to", v) - } - -} - type CommonSuite struct{} var _ = checker.Suite(&CommonSuite{}) diff --git a/crypto/crypto.go b/crypto/crypto.go index 2d26dd25e..442942c6c 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -9,6 +9,7 @@ import ( "crypto/sha256" "fmt" "io" + "io/ioutil" "os" "encoding/hex" @@ -139,9 +140,10 @@ func LoadECDSA(file string) (*ecdsa.PrivateKey, error) { return ToECDSA(buf), nil } -// SaveECDSA saves a secp256k1 private key from the given file. +// SaveECDSA saves a secp256k1 private key to the given file with restrictive +// permissions func SaveECDSA(file string, key *ecdsa.PrivateKey) error { - return common.WriteFile(file, FromECDSA(key)) + return ioutil.WriteFile(file, FromECDSA(key), 0600) } func GenerateKey() (*ecdsa.PrivateKey, error) { diff --git a/jsre/jsre_test.go b/jsre/jsre_test.go index 8a771dae8..667ed4bdc 100644 --- a/jsre/jsre_test.go +++ b/jsre/jsre_test.go @@ -2,9 +2,9 @@ package jsre import ( "github.com/robertkrimen/otto" + "io/ioutil" + "os" "testing" - - "github.com/ethereum/go-ethereum/common" ) type testNativeObjectBinding struct { @@ -26,7 +26,7 @@ func (no *testNativeObjectBinding) TestMethod(call otto.FunctionCall) otto.Value func TestExec(t *testing.T) { jsre := New("/tmp") - common.WriteFile("/tmp/test.js", []byte(`msg = "testMsg"`)) + ioutil.WriteFile("/tmp/test.js", []byte(`msg = "testMsg"`), os.ModePerm) err := jsre.Exec("test.js") if err != nil { t.Errorf("expected no error, got %v", err) @@ -64,7 +64,7 @@ func TestBind(t *testing.T) { func TestLoadScript(t *testing.T) { jsre := New("/tmp") - common.WriteFile("/tmp/test.js", []byte(`msg = "testMsg"`)) + ioutil.WriteFile("/tmp/test.js", []byte(`msg = "testMsg"`), os.ModePerm) _, err := jsre.Run(`loadScript("test.js")`) if err != nil { t.Errorf("expected no error, got %v", err) From 11d2ebc06ffffa8846d5d55cae5663fac6f685f1 Mon Sep 17 00:00:00 2001 From: zelig Date: Wed, 25 Mar 2015 15:45:56 +0000 Subject: [PATCH 10/12] unlocking coinbase without knowing address - accounts: remove Manager.getKey - cli: for -unlock coinbase, use account manager Coinbase() --- accounts/account_manager.go | 18 +++--------------- cmd/ethereum/main.go | 9 +++++++-- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/accounts/account_manager.go b/accounts/account_manager.go index f063f8ca5..392518703 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -36,9 +36,8 @@ import ( "bytes" "crypto/ecdsa" crand "crypto/rand" - "os" - "errors" + "os" "sync" "time" @@ -101,17 +100,6 @@ func (am *Manager) firstAddr() ([]byte, error) { return addrs[0], nil } -func (am *Manager) getKey(addr []byte, keyAuth string) (*crypto.Key, error) { - if len(addr) == 0 { - var err error - addr, err = am.firstAddr() - if err != nil { - return nil, err - } - } - return am.keyStore.GetKey(addr, keyAuth) -} - func (am *Manager) DeleteAccount(address []byte, auth string) error { return am.keyStore.DeleteKey(address, auth) } @@ -130,7 +118,7 @@ func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error) // TimedUnlock unlocks the account with the given address. // When timeout has passed, the account will be locked again. func (am *Manager) TimedUnlock(addr []byte, keyAuth string, timeout time.Duration) error { - key, err := am.getKey(addr, keyAuth) + key, err := am.keyStore.GetKey(addr, keyAuth) if err != nil { return err } @@ -143,7 +131,7 @@ func (am *Manager) TimedUnlock(addr []byte, keyAuth string, timeout time.Duratio // stays unlocked until the program exits or until a TimedUnlock // timeout (started after the call to Unlock) expires. func (am *Manager) Unlock(addr []byte, keyAuth string) error { - key, err := am.getKey(addr, keyAuth) + key, err := am.keyStore.GetKey(addr, keyAuth) if err != nil { return err } diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 6bbe1044f..8983b85a6 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -294,7 +294,7 @@ func unlockAccount(ctx *cli.Context, am *accounts.Manager, account string) (pass var err error // Load startup keys. XXX we are going to need a different format // Attempt to unlock the account - passphrase := getPassPhrase(ctx, "", false) + passphrase = getPassPhrase(ctx, "", false) err = am.Unlock(common.FromHex(account), passphrase) if err != nil { utils.Fatalf("Unlock account failed '%v'", err) @@ -310,7 +310,11 @@ func startEth(ctx *cli.Context, eth *eth.Ethereum) { account := ctx.GlobalString(utils.UnlockedAccountFlag.Name) if len(account) > 0 { if account == "coinbase" { - account = "" + accbytes, err := am.Coinbase() + if err != nil { + utils.Fatalf("no coinbase account: %v", err) + } + account = common.ToHex(accbytes) } unlockAccount(ctx, am, account) } @@ -420,6 +424,7 @@ func accountExport(ctx *cli.Context) { } 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) From abbdf4156057de8a4f866b0840defc00c2c500db Mon Sep 17 00:00:00 2001 From: zelig Date: Wed, 25 Mar 2015 16:10:44 +0000 Subject: [PATCH 11/12] output error message if unlock address is invalid (fixes the wierd "read /path: is a directory") msg --- cmd/ethereum/main.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 8983b85a6..2e721dc71 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -295,7 +295,11 @@ func unlockAccount(ctx *cli.Context, am *accounts.Manager, account string) (pass // Load startup keys. XXX we are going to need a different format // Attempt to unlock the account passphrase = getPassPhrase(ctx, "", false) - err = am.Unlock(common.FromHex(account), passphrase) + accbytes := common.FromHex(account) + if len(accbytes) == 0 { + utils.Fatalf("Invalid account address '%s'", account) + } + err = am.Unlock(accbytes, passphrase) if err != nil { utils.Fatalf("Unlock account failed '%v'", err) } From 7577d1261403dbabdb30e21415d34b4e5da466ec Mon Sep 17 00:00:00 2001 From: zelig Date: Thu, 26 Mar 2015 18:55:39 +0000 Subject: [PATCH 12/12] max paranoia mode to UNsupport unencrypted keys entirely - remove account export functionality from CLI - remove accountExport method, - remove unencrypted-keys flag from everywhere - improve documentation --- accounts/account_manager.go | 2 + cmd/ethereum/main.go | 151 +++++++++++++++++------------------- cmd/utils/flags.go | 14 +--- 3 files changed, 74 insertions(+), 93 deletions(-) diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 392518703..34a2c4891 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -208,6 +208,8 @@ func zeroKey(k *ecdsa.PrivateKey) { } } +// USE WITH CAUTION = this will save an unencrypted private key on disk +// no cli or js interface func (am *Manager) Export(path string, addr []byte, keyAuth string) error { key, err := am.keyStore.GetKey(addr, keyAuth) if err != nil { diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 2e721dc71..42321e8bc 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -77,9 +77,8 @@ The output of this command is supposed to be machine-readable. }, { - Action: accountList, - Name: "wallet", - Usage: "ethereum presale wallet", + Name: "wallet", + Usage: "ethereum presale wallet", Subcommands: []cli.Command{ { Action: importWallet, @@ -92,6 +91,28 @@ The output of this command is supposed to be machine-readable. Action: accountList, Name: "account", Usage: "manage accounts", + Description: ` + +Manage accounts lets you create new accounts, list all existing accounts, +import a private key into a new account. + +It supports interactive mode, when you are prompted for password as well as +non-interactive mode where passwords are supplied via a given password file. +Non-interactive mode is only meant for scripted use on test networks or known +safe environments. + +Make sure you remember the password you gave when creating a new account (with +either new or import). Without it you are not able to unlock your account. + +Note that exporting your key in unencrypted format is NOT supported. + +Keys are stored under /keys. +It is safe to transfer the entire directory or the individual keys therein +between ethereum nodes. +Make sure you backup your keys regularly. + +And finally. DO NOT FORGET YOUR PASSWORD. +`, Subcommands: []cli.Command{ { Action: accountList, @@ -106,12 +127,18 @@ The output of this command is supposed to be machine-readable. 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. +Creates a new account. Prints the address. + +The account is saved in encrypted format, you are prompted for a passphrase. + +You must remember this passphrase to unlock your account in the future. + For non-interactive use the passphrase can be specified with the --password flag: ethereum --password account new +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. `, }, { @@ -122,38 +149,23 @@ For non-interactive use the passphrase can be specified with the --password flag ethereum account import -Imports a private key from and creates a new account with the address -derived from the key. +Imports an unencrypted private key from and creates a new account. +Prints the address. + The keyfile is assumed to contain an unencrypted private key in canonical EC -format. +raw bytes 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. + +You must remember this passphrase to unlock your account in the 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 passphrase can be specified with the --unlock flag: - - ethereum --password account export
- Note: As you can directly copy your encrypted accounts to another ethereum instance, -this import/export mechanism is not needed when you transfer an account between +this import mechanism is not needed when you transfer an account between nodes. `, }, @@ -217,7 +229,6 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso utils.RPCEnabledFlag, utils.RPCListenAddrFlag, utils.RPCPortFlag, - utils.UnencryptedKeysFlag, utils.VMDebugFlag, utils.ProtocolVersionFlag, utils.NetworkIdFlag, @@ -290,19 +301,17 @@ func execJSFiles(ctx *cli.Context) { } 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 - passphrase = getPassPhrase(ctx, "", false) - accbytes := common.FromHex(account) - if len(accbytes) == 0 { - utils.Fatalf("Invalid account address '%s'", account) - } - err = am.Unlock(accbytes, passphrase) - if err != nil { - utils.Fatalf("Unlock account failed '%v'", err) - } + var err error + // Load startup keys. XXX we are going to need a different format + // Attempt to unlock the account + passphrase = getPassPhrase(ctx, "", false) + accbytes := common.FromHex(account) + if len(accbytes) == 0 { + utils.Fatalf("Invalid account address '%s'", account) + } + err = am.Unlock(accbytes, passphrase) + if err != nil { + utils.Fatalf("Unlock account failed '%v'", err) } return } @@ -343,32 +352,30 @@ func accountList(ctx *cli.Context) { } func getPassPhrase(ctx *cli.Context, desc string, confirmation bool) (passphrase string) { - if !ctx.GlobalBool(utils.UnencryptedKeysFlag.Name) { - passfile := ctx.GlobalString(utils.PasswordFileFlag.Name) - if len(passfile) == 0 { - fmt.Println(desc) - auth, err := readPassword("Passphrase: ", true) + passfile := ctx.GlobalString(utils.PasswordFileFlag.Name) + if len(passfile) == 0 { + fmt.Println(desc) + auth, err := readPassword("Passphrase: ", true) + if err != nil { + utils.Fatalf("%v", err) + } + if confirmation { + confirm, err := readPassword("Repeat Passphrase: ", false) if err != nil { utils.Fatalf("%v", err) } - if confirmation { - confirm, err := readPassword("Repeat Passphrase: ", false) - if err != nil { - utils.Fatalf("%v", err) - } - if auth != confirm { - utils.Fatalf("Passphrases did not match.") - } + if auth != confirm { + utils.Fatalf("Passphrases did not match.") } - passphrase = auth - - } else { - passbytes, err := ioutil.ReadFile(passfile) - if err != nil { - utils.Fatalf("Unable to read password file '%s': %v", passfile, err) - } - passphrase = string(passbytes) } + passphrase = auth + + } else { + passbytes, err := ioutil.ReadFile(passfile) + if err != nil { + utils.Fatalf("Unable to read password file '%s': %v", passfile, err) + } + passphrase = string(passbytes) } return } @@ -417,24 +424,6 @@ func accountImport(ctx *cli.Context) { 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) { if len(ctx.Args()) != 1 { utils.Fatalf("This command requires an argument.") diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index dda409502..f948cdb06 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -97,14 +97,9 @@ var ( Usage: "Enable mining", } - // key settings - UnencryptedKeysFlag = cli.BoolFlag{ - Name: "unencrypted-keys", - Usage: "disable private key disk encryption (for testing)", - } UnlockedAccountFlag = cli.StringFlag{ Name: "unlock", - Usage: "unlock the account given until this program exits (prompts for password).", + Usage: "unlock the account given until this program exits (prompts for password). '--unlock coinbase' unlocks the primary (coinbase) account", Value: "", } PasswordFileFlag = cli.StringFlag{ @@ -249,12 +244,7 @@ func GetChain(ctx *cli.Context) (*core.ChainManager, common.Database, common.Dat func GetAccountManager(ctx *cli.Context) *accounts.Manager { dataDir := ctx.GlobalString(DataDirFlag.Name) - var ks crypto.KeyStore2 - if ctx.GlobalBool(UnencryptedKeysFlag.Name) { - ks = crypto.NewKeyStorePlain(path.Join(dataDir, "plainkeys")) - } else { - ks = crypto.NewKeyStorePassphrase(path.Join(dataDir, "keys")) - } + ks := crypto.NewKeyStorePassphrase(path.Join(dataDir, "keys")) return accounts.NewManager(ks) }