diff --git a/cmd/geth/accountcmd.go b/cmd/geth/accountcmd.go deleted file mode 100644 index 071be85397..0000000000 --- a/cmd/geth/accountcmd.go +++ /dev/null @@ -1,379 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "fmt" - "io/ioutil" - - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/accounts/keystore" - "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/console" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "gopkg.in/urfave/cli.v1" -) - -var ( - walletCommand = cli.Command{ - Name: "wallet", - Usage: "Manage Ethereum presale wallets", - ArgsUsage: "", - Category: "ACCOUNT COMMANDS", - Description: ` - geth wallet import /path/to/my/presale.wallet - -will prompt for your password and imports your ether presale account. -It can be used non-interactively with the --password option taking a -passwordfile as argument containing the wallet password in plaintext.`, - Subcommands: []cli.Command{ - { - - Name: "import", - Usage: "Import Ethereum presale wallet", - ArgsUsage: "", - Action: utils.MigrateFlags(importWallet), - Category: "ACCOUNT COMMANDS", - Flags: []cli.Flag{ - utils.DataDirFlag, - utils.KeyStoreDirFlag, - utils.PasswordFileFlag, - utils.LightKDFFlag, - }, - Description: ` - geth wallet [options] /path/to/my/presale.wallet - -will prompt for your password and imports your ether presale account. -It can be used non-interactively with the --password option taking a -passwordfile as argument containing the wallet password in plaintext.`, - }, - }, - } - - accountCommand = cli.Command{ - Name: "account", - Usage: "Manage accounts", - Category: "ACCOUNT COMMANDS", - Description: ` - -Manage accounts, list all existing accounts, import a private key into a new -account, create a new account or update an existing 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 /keystore. -It is safe to transfer the entire directory or the individual keys therein -between ethereum nodes by simply copying. - -Make sure you backup your keys regularly.`, - Subcommands: []cli.Command{ - { - Name: "list", - Usage: "Print summary of existing accounts", - Action: utils.MigrateFlags(accountList), - Flags: []cli.Flag{ - utils.DataDirFlag, - utils.KeyStoreDirFlag, - }, - Description: ` -Print a short summary of all accounts`, - }, - { - Name: "new", - Usage: "Create a new account", - Action: utils.MigrateFlags(accountCreate), - Flags: []cli.Flag{ - utils.DataDirFlag, - utils.KeyStoreDirFlag, - utils.PasswordFileFlag, - utils.LightKDFFlag, - }, - Description: ` - geth account new - -Creates a new account and 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: - -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. -`, - }, - { - Name: "update", - Usage: "Update an existing account", - Action: utils.MigrateFlags(accountUpdate), - ArgsUsage: "
", - Flags: []cli.Flag{ - utils.DataDirFlag, - utils.KeyStoreDirFlag, - utils.LightKDFFlag, - }, - Description: ` - geth 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: - - geth account update [options]
- -Since only one password can be given, only format update can be performed, -changing your password is only possible interactively. -`, - }, - { - Name: "import", - Usage: "Import a private key into a new account", - Action: utils.MigrateFlags(accountImport), - Flags: []cli.Flag{ - utils.DataDirFlag, - utils.KeyStoreDirFlag, - utils.PasswordFileFlag, - utils.LightKDFFlag, - }, - ArgsUsage: "", - Description: ` - geth account import - -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 hexadecimal format. - -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: - - geth account import [options] - -Note: -As you can directly copy your encrypted accounts to another ethereum instance, -this import mechanism is not needed when you transfer an account between -nodes. -`, - }, - }, - } -) - -func accountList(ctx *cli.Context) error { - stack, _ := makeConfigNode(ctx) - var index int - for _, wallet := range stack.AccountManager().Wallets() { - for _, account := range wallet.Accounts() { - fmt.Printf("Account #%d: {%x} %s\n", index, account.Address, &account.URL) - index++ - } - } - return nil -} - -// tries unlocking the specified account a few times. -func unlockAccount(ctx *cli.Context, ks *keystore.KeyStore, address string, i int, passwords []string) (accounts.Account, string) { - account, err := utils.MakeAddress(ks, address) - if err != nil { - utils.Fatalf("Could not list accounts: %v", err) - } - for trials := 0; trials < 3; trials++ { - prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3) - password := getPassPhrase(prompt, false, i, passwords) - err = ks.Unlock(account, password) - if err == nil { - log.Info("Unlocked account", "address", account.Address.Hex()) - return account, password - } - if err, ok := err.(*keystore.AmbiguousAddrError); ok { - log.Info("Unlocked account", "address", account.Address.Hex()) - return ambiguousAddrRecovery(ks, err, password), password - } - if err != keystore.ErrDecrypt { - // No need to prompt again if the error is not decryption-related. - break - } - } - // All trials expended to unlock account, bail out - utils.Fatalf("Failed to unlock account %s (%v)", address, err) - - return accounts.Account{}, "" -} - -// getPassPhrase retrieves the password associated with an account, either fetched -// from a list of preloaded passphrases, or requested interactively from the user. -func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string { - // If a list of passwords was supplied, retrieve from them - if len(passwords) > 0 { - if i < len(passwords) { - return passwords[i] - } - return passwords[len(passwords)-1] - } - // Otherwise prompt the user for the password - if prompt != "" { - fmt.Println(prompt) - } - password, err := console.Stdin.PromptPassword("Passphrase: ") - if err != nil { - utils.Fatalf("Failed to read passphrase: %v", err) - } - if confirmation { - confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ") - if err != nil { - utils.Fatalf("Failed to read passphrase confirmation: %v", err) - } - if password != confirm { - utils.Fatalf("Passphrases do not match") - } - } - return password -} - -func ambiguousAddrRecovery(ks *keystore.KeyStore, err *keystore.AmbiguousAddrError, auth string) accounts.Account { - fmt.Printf("Multiple key files exist for address %x:\n", err.Addr) - for _, a := range err.Matches { - fmt.Println(" ", a.URL) - } - fmt.Println("Testing your passphrase against all of them...") - var match *accounts.Account - for _, a := range err.Matches { - if err := ks.Unlock(a, auth); err == nil { - match = &a - break - } - } - if match == nil { - utils.Fatalf("None of the listed files could be unlocked.") - } - fmt.Printf("Your passphrase unlocked %s\n", match.URL) - fmt.Println("In order to avoid this warning, you need to remove the following duplicate key files:") - for _, a := range err.Matches { - if a != *match { - fmt.Println(" ", a.URL) - } - } - return *match -} - -// accountCreate creates a new account into the keystore defined by the CLI flags. -func accountCreate(ctx *cli.Context) error { - cfg := gethConfig{Node: defaultNodeConfig()} - // Load config file. - if file := ctx.GlobalString(configFileFlag.Name); file != "" { - if err := loadConfig(file, &cfg); err != nil { - utils.Fatalf("%v", err) - } - } - utils.SetNodeConfig(ctx, &cfg.Node) - scryptN, scryptP, keydir, err := cfg.Node.AccountConfig() - - if err != nil { - utils.Fatalf("Failed to read configuration: %v", err) - } - - password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) - - address, err := keystore.StoreKey(keydir, password, scryptN, scryptP) - - if err != nil { - utils.Fatalf("Failed to create account: %v", err) - } - fmt.Printf("Address: {%x}\n", address) - return nil -} - -// accountUpdate transitions an account from a previous format to the current -// one, also providing the possibility to change the pass-phrase. -func accountUpdate(ctx *cli.Context) error { - if len(ctx.Args()) == 0 { - utils.Fatalf("No accounts specified to update") - } - stack, _ := makeConfigNode(ctx) - ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) - - for _, addr := range ctx.Args() { - account, oldPassword := unlockAccount(ctx, ks, addr, 0, nil) - newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil) - if err := ks.Update(account, oldPassword, newPassword); err != nil { - utils.Fatalf("Could not update the account: %v", err) - } - } - return nil -} - -func importWallet(ctx *cli.Context) error { - 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) - } - - stack, _ := makeConfigNode(ctx) - passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx)) - - ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) - acct, err := ks.ImportPreSaleKey(keyJSON, passphrase) - if err != nil { - utils.Fatalf("%v", err) - } - fmt.Printf("Address: {%x}\n", acct.Address) - return nil -} - -func accountImport(ctx *cli.Context) error { - keyfile := ctx.Args().First() - if len(keyfile) == 0 { - utils.Fatalf("keyfile must be given as argument") - } - key, err := crypto.LoadECDSA(keyfile) - if err != nil { - utils.Fatalf("Failed to load the private key: %v", err) - } - stack, _ := makeConfigNode(ctx) - passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) - - ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) - acct, err := ks.ImportECDSA(key, passphrase) - if err != nil { - utils.Fatalf("Could not create the account: %v", err) - } - fmt.Printf("Address: {%x}\n", acct.Address) - return nil -} diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go deleted file mode 100644 index 3ea22ccfab..0000000000 --- a/cmd/geth/accountcmd_test.go +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "io/ioutil" - "path/filepath" - "runtime" - "strings" - "testing" - - "github.com/cespare/cp" -) - -// These tests are 'smoke tests' for the account related -// subcommands and flags. -// -// For most tests, the test files from package accounts -// are copied into a temporary keystore directory. - -func tmpDatadirWithKeystore(t *testing.T) string { - datadir := tmpdir(t) - keystore := filepath.Join(datadir, "keystore") - source := filepath.Join("..", "..", "accounts", "keystore", "testdata", "keystore") - if err := cp.CopyAll(keystore, source); err != nil { - t.Fatal(err) - } - return datadir -} - -func TestAccountListEmpty(t *testing.T) { - geth := runGeth(t, "account", "list") - geth.ExpectExit() -} - -func TestAccountList(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, "account", "list", "--datadir", datadir) - defer geth.ExpectExit() - if runtime.GOOS == "windows" { - geth.Expect(` -Account #0: {7ef5a6135f1fd6a02593eedc869c6d41d934aef8} keystore://{{.Datadir}}\keystore\UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 -Account #1: {f466859ead1932d743d622cb74fc058882e8648a} keystore://{{.Datadir}}\keystore\aaa -Account #2: {289d485d9771714cce91d3393d764e1311907acc} keystore://{{.Datadir}}\keystore\zzz -`) - } else { - geth.Expect(` -Account #0: {7ef5a6135f1fd6a02593eedc869c6d41d934aef8} keystore://{{.Datadir}}/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 -Account #1: {f466859ead1932d743d622cb74fc058882e8648a} keystore://{{.Datadir}}/keystore/aaa -Account #2: {289d485d9771714cce91d3393d764e1311907acc} keystore://{{.Datadir}}/keystore/zzz -`) - } -} - -func TestAccountNew(t *testing.T) { - geth := runGeth(t, "account", "new", "--lightkdf") - defer geth.ExpectExit() - geth.Expect(` -Your new account is locked with a password. Please give a password. Do not forget this password. -!! Unsupported terminal, password will be echoed. -Passphrase: {{.InputLine "foobar"}} -Repeat passphrase: {{.InputLine "foobar"}} -`) - geth.ExpectRegexp(`Address: \{[0-9a-f]{40}\}\n`) -} - -func TestAccountNewBadRepeat(t *testing.T) { - geth := runGeth(t, "account", "new", "--lightkdf") - defer geth.ExpectExit() - geth.Expect(` -Your new account is locked with a password. Please give a password. Do not forget this password. -!! Unsupported terminal, password will be echoed. -Passphrase: {{.InputLine "something"}} -Repeat passphrase: {{.InputLine "something else"}} -Fatal: Passphrases do not match -`) -} - -func TestAccountUpdate(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, "account", "update", - "--datadir", datadir, "--lightkdf", - "f466859ead1932d743d622cb74fc058882e8648a") - defer geth.ExpectExit() - geth.Expect(` -Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 -!! Unsupported terminal, password will be echoed. -Passphrase: {{.InputLine "foobar"}} -Please give a new password. Do not forget this password. -Passphrase: {{.InputLine "foobar2"}} -Repeat passphrase: {{.InputLine "foobar2"}} -`) -} - -func TestWalletImport(t *testing.T) { - geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json") - defer geth.ExpectExit() - geth.Expect(` -!! Unsupported terminal, password will be echoed. -Passphrase: {{.InputLine "foo"}} -Address: {d4584b5f6229b7be90727b0fc8c6b91bb427821f} -`) - - files, err := ioutil.ReadDir(filepath.Join(geth.Datadir, "keystore")) - if len(files) != 1 { - t.Errorf("expected one key file in keystore directory, found %d files (error: %v)", len(files), err) - } -} - -func TestWalletImportBadPassword(t *testing.T) { - geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json") - defer geth.ExpectExit() - geth.Expect(` -!! Unsupported terminal, password will be echoed. -Passphrase: {{.InputLine "wrong"}} -Fatal: could not decrypt key with given passphrase -`) -} - -func TestUnlockFlag(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, - "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", - "js", "testdata/empty.js") - geth.Expect(` -Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 -!! Unsupported terminal, password will be echoed. -Passphrase: {{.InputLine "foobar"}} -`) - geth.ExpectExit() - - wantMessages := []string{ - "Unlocked account", - "=0xf466859eAD1932D743d622CB74FC058882E8648A", - } - for _, m := range wantMessages { - if !strings.Contains(geth.StderrText(), m) { - t.Errorf("stderr text does not contain %q", m) - } - } -} - -func TestUnlockFlagWrongPassword(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, - "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") - defer geth.ExpectExit() - geth.Expect(` -Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 -!! Unsupported terminal, password will be echoed. -Passphrase: {{.InputLine "wrong1"}} -Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 2/3 -Passphrase: {{.InputLine "wrong2"}} -Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 3/3 -Passphrase: {{.InputLine "wrong3"}} -Fatal: Failed to unlock account f466859ead1932d743d622cb74fc058882e8648a (could not decrypt key with given passphrase) -`) -} - -// https://github.com/ethereum/go-ethereum/issues/1785 -func TestUnlockFlagMultiIndex(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, - "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--unlock", "0,2", - "js", "testdata/empty.js") - geth.Expect(` -Unlocking account 0 | Attempt 1/3 -!! Unsupported terminal, password will be echoed. -Passphrase: {{.InputLine "foobar"}} -Unlocking account 2 | Attempt 1/3 -Passphrase: {{.InputLine "foobar"}} -`) - geth.ExpectExit() - - wantMessages := []string{ - "Unlocked account", - "=0x7EF5A6135f1FD6a02593eEdC869c6D41D934aef8", - "=0x289d485D9771714CCe91D3393D764E1311907ACc", - } - for _, m := range wantMessages { - if !strings.Contains(geth.StderrText(), m) { - t.Errorf("stderr text does not contain %q", m) - } - } -} - -func TestUnlockFlagPasswordFile(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, - "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--password", "testdata/passwords.txt", "--unlock", "0,2", - "js", "testdata/empty.js") - geth.ExpectExit() - - wantMessages := []string{ - "Unlocked account", - "=0x7EF5A6135f1FD6a02593eEdC869c6D41D934aef8", - "=0x289d485D9771714CCe91D3393D764E1311907ACc", - } - for _, m := range wantMessages { - if !strings.Contains(geth.StderrText(), m) { - t.Errorf("stderr text does not contain %q", m) - } - } -} - -func TestUnlockFlagPasswordFileWrongPassword(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, - "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--password", "testdata/wrong-passwords.txt", "--unlock", "0,2") - defer geth.ExpectExit() - geth.Expect(` -Fatal: Failed to unlock account 0 (could not decrypt key with given passphrase) -`) -} - -func TestUnlockFlagAmbiguous(t *testing.T) { - store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes") - geth := runGeth(t, - "--keystore", store, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", - "js", "testdata/empty.js") - defer geth.ExpectExit() - - // Helper for the expect template, returns absolute keystore path. - geth.SetTemplateFunc("keypath", func(file string) string { - abs, _ := filepath.Abs(filepath.Join(store, file)) - return abs - }) - geth.Expect(` -Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 -!! Unsupported terminal, password will be echoed. -Passphrase: {{.InputLine "foobar"}} -Multiple key files exist for address f466859ead1932d743d622cb74fc058882e8648a: - keystore://{{keypath "1"}} - keystore://{{keypath "2"}} -Testing your passphrase against all of them... -Your passphrase unlocked keystore://{{keypath "1"}} -In order to avoid this warning, you need to remove the following duplicate key files: - keystore://{{keypath "2"}} -`) - geth.ExpectExit() - - wantMessages := []string{ - "Unlocked account", - "=0xf466859eAD1932D743d622CB74FC058882E8648A", - } - for _, m := range wantMessages { - if !strings.Contains(geth.StderrText(), m) { - t.Errorf("stderr text does not contain %q", m) - } - } -} - -func TestUnlockFlagAmbiguousWrongPassword(t *testing.T) { - store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes") - geth := runGeth(t, - "--keystore", store, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") - defer geth.ExpectExit() - - // Helper for the expect template, returns absolute keystore path. - geth.SetTemplateFunc("keypath", func(file string) string { - abs, _ := filepath.Abs(filepath.Join(store, file)) - return abs - }) - geth.Expect(` -Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 -!! Unsupported terminal, password will be echoed. -Passphrase: {{.InputLine "wrong"}} -Multiple key files exist for address f466859ead1932d743d622cb74fc058882e8648a: - keystore://{{keypath "1"}} - keystore://{{keypath "2"}} -Testing your passphrase against all of them... -Fatal: None of the listed files could be unlocked. -`) - geth.ExpectExit() -} diff --git a/cmd/geth/config.go b/cmd/geth/config.go index b0749d2329..c0842c4798 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -152,9 +152,7 @@ func enableWhisper(ctx *cli.Context) bool { func makeFullNode(ctx *cli.Context) *node.Node { stack, cfg := makeConfigNode(ctx) - utils.RegisterEthService(stack, &cfg.Eth) - if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) { utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit) } diff --git a/cmd/geth/main.go b/cmd/geth/main.go index fae4b57181..07e8b5bda6 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -25,21 +25,18 @@ import ( godebug "runtime/debug" "sort" "strconv" - "strings" "time" "github.com/elastic/gosigar" - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/console" "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" "gopkg.in/urfave/cli.v1" + "github.com/ethereum/go-ethereum/internal/ethapi" ) const ( @@ -54,13 +51,10 @@ var ( // flags that configure the node nodeFlags = []cli.Flag{ utils.IdentityFlag, - utils.UnlockedAccountFlag, - utils.PasswordFileFlag, utils.BootnodesFlag, utils.BootnodesV4Flag, utils.BootnodesV5Flag, utils.DataDirFlag, - utils.KeyStoreDirFlag, utils.NoUSBFlag, utils.DashboardEnabledFlag, utils.DashboardAddrFlag, @@ -73,6 +67,7 @@ var ( utils.EthashDatasetsInMemoryFlag, utils.EthashDatasetsOnDiskFlag, utils.TxPoolLocalsFlag, + utils.ExternalSignerFlag, utils.TxPoolNoLocalsFlag, utils.TxPoolJournalFlag, utils.TxPoolRejournalFlag, @@ -183,9 +178,6 @@ func init() { dumpCommand, // See monitorcmd.go: monitorCommand, - // See accountcmd.go: - accountCommand, - walletCommand, // See consolecmd.go: consoleCommand, attachCommand, @@ -276,60 +268,19 @@ func geth(ctx *cli.Context) error { func startNode(ctx *cli.Context, stack *node.Node) { debug.Memsize.Add("node", stack) + // Start account management + if extEndpoint := ctx.GlobalString(utils.ExternalSignerFlag.Name); extEndpoint != ""{ + extApi, err := ethapi.NewExternalSigner(extEndpoint) + if err != nil{ + utils.Fatalf("Could not connect to external signer at %v: %v" , extEndpoint, err) + } + log.Info("External signer connected", "url", extEndpoint) + stack.SetExternalAPI(extApi) + } + // Start up the node itself utils.StartNode(stack) - // Unlock any account specifically requested - ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) - - passwords := utils.MakePasswordList(ctx) - unlocks := strings.Split(ctx.GlobalString(utils.UnlockedAccountFlag.Name), ",") - for i, account := range unlocks { - if trimmed := strings.TrimSpace(account); trimmed != "" { - unlockAccount(ctx, ks, trimmed, i, passwords) - } - } - // Register wallet event handlers to open and auto-derive wallets - events := make(chan accounts.WalletEvent, 16) - stack.AccountManager().Subscribe(events) - - go func() { - // Create a chain state reader for self-derivation - rpcClient, err := stack.Attach() - if err != nil { - utils.Fatalf("Failed to attach to self: %v", err) - } - stateReader := ethclient.NewClient(rpcClient) - - // Open any wallets already attached - for _, wallet := range stack.AccountManager().Wallets() { - if err := wallet.Open(""); err != nil { - log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err) - } - } - // Listen for wallet event till termination - for event := range events { - switch event.Kind { - case accounts.WalletArrived: - if err := event.Wallet.Open(""); err != nil { - log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err) - } - case accounts.WalletOpened: - status, _ := event.Wallet.Status() - log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status) - - derivationPath := accounts.DefaultBaseDerivationPath - if event.Wallet.URL().Scheme == "ledger" { - derivationPath = accounts.DefaultLedgerBaseDerivationPath - } - event.Wallet.SelfDerive(derivationPath, stateReader) - - case accounts.WalletDropped: - log.Info("Old wallet dropped", "url", event.Wallet.URL()) - event.Wallet.Close() - } - } - }() // Start auxiliary services if enabled if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) { // Mining only makes sense if a full Ethereum node is running diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 8b0491ce3c..2c6fa3829a 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -69,7 +69,6 @@ var AppHelpFlagGroups = []flagGroup{ Flags: []cli.Flag{ configFileFlag, utils.DataDirFlag, - utils.KeyStoreDirFlag, utils.NoUSBFlag, utils.NetworkIdFlag, utils.TestnetFlag, @@ -136,13 +135,6 @@ var AppHelpFlagGroups = []flagGroup{ utils.TrieCacheGenFlag, }, }, - { - Name: "ACCOUNT", - Flags: []cli.Flag{ - utils.UnlockedAccountFlag, - utils.PasswordFileFlag, - }, - }, { Name: "API AND CONSOLE", Flags: []cli.Flag{ diff --git a/cmd/swarm/main.go b/cmd/swarm/main.go index 88e6b0b0be..28e78f920b 100644 --- a/cmd/swarm/main.go +++ b/cmd/swarm/main.go @@ -565,7 +565,6 @@ pv(1) tool to get a progress bar: utils.NATFlag, utils.IPCDisabledFlag, utils.IPCPathFlag, - utils.PasswordFileFlag, // bzzd-specific flags CorsStringFlag, EnsAPIFlag, @@ -722,11 +721,8 @@ func getAccount(bzzaccount string, ctx *cli.Context, stack *node.Node) *ecdsa.Pr log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey)) return key } - // Otherwise try getting it from the keystore. - am := stack.AccountManager() - ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) - - return decryptStoreAccount(ks, bzzaccount, utils.MakePasswordList(ctx)) + utils.Fatalf(SWARM_ERR_NO_BZZACCOUNT) + return nil } // getPrivKey returns the private key of the specified bzzaccount diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 429c2bbb9b..ebeca0bbab 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -20,16 +20,13 @@ package utils import ( "crypto/ecdsa" "fmt" - "io/ioutil" "math/big" "os" "path/filepath" - "strconv" "strings" "time" "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/fdlimit" "github.com/ethereum/go-ethereum/consensus" @@ -379,17 +376,15 @@ var ( Usage: "Disable remote sealing verification", } // Account settings - UnlockedAccountFlag = cli.StringFlag{ - Name: "unlock", - Usage: "Comma separated list of accounts to unlock", - Value: "", - } PasswordFileFlag = cli.StringFlag{ Name: "password", Usage: "Password file to use for non-interactive password input", + } + ExternalSignerFlag = cli.StringFlag{ + Name: "signer", + Usage: "External signer (url or path to ipc file)", Value: "", } - VMEnableDebugFlag = cli.BoolFlag{ Name: "vmdebug", Usage: "Record information useful for VM and contract debugging", @@ -836,32 +831,21 @@ func makeDatabaseHandles() int { // MakeAddress converts an account specified directly as a hex encoded string or // a key index in the key store to an internal account representation. -func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error) { +func MakeAddress(account string) (accounts.Account, error) { // If the specified account is a valid address, return it - if common.IsHexAddress(account) { - return accounts.Account{Address: common.HexToAddress(account)}, nil + ma, err := common.NewMixedcaseAddressFromString(account) + if err != nil { + return accounts.Account{}, fmt.Errorf("Not valid hex address (%v): %v", account, err) } - // Otherwise try to interpret the account as a keystore index - index, err := strconv.Atoi(account) - if err != nil || index < 0 { - return accounts.Account{}, fmt.Errorf("invalid account address or index %q", account) + if !ma.ValidChecksum() { + return accounts.Account{}, fmt.Errorf("Invalid checksum on address %v", account) } - log.Warn("-------------------------------------------------------------------") - log.Warn("Referring to accounts by order in the keystore folder is dangerous!") - log.Warn("This functionality is deprecated and will be removed in the future!") - log.Warn("Please use explicit addresses! (can search via `geth account list`)") - log.Warn("-------------------------------------------------------------------") - - accs := ks.Accounts() - if len(accs) <= index { - return accounts.Account{}, fmt.Errorf("index %d higher than number of accounts %d", index, len(accs)) - } - return accs[index], nil + return accounts.Account{Address: common.HexToAddress(account)}, nil } // setEtherbase retrieves the etherbase either from the directly specified // command line flags or from the keystore if CLI indexed. -func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) { +func setEtherbase(ctx *cli.Context, cfg *eth.Config) { // Extract the current etherbase, new flag overriding legacy one var etherbase string if ctx.GlobalIsSet(MinerLegacyEtherbaseFlag.Name) { @@ -872,7 +856,7 @@ func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) { } // Convert the etherbase into an address and configure it if etherbase != "" { - account, err := MakeAddress(ks, etherbase) + account, err := MakeAddress(etherbase) if err != nil { Fatalf("Invalid miner etherbase: %v", err) } @@ -880,24 +864,6 @@ func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) { } } -// MakePasswordList reads password lines from the file specified by the global --password flag. -func MakePasswordList(ctx *cli.Context) []string { - path := ctx.GlobalString(PasswordFileFlag.Name) - if path == "" { - return nil - } - text, err := ioutil.ReadFile(path) - if err != nil { - Fatalf("Failed to read password file: %v", err) - } - lines := strings.Split(string(text), "\n") - // Sanitise DOS line endings. - for i := range lines { - lines[i] = strings.TrimRight(lines[i], "\r") - } - return lines -} - func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) { setNodeKey(ctx, cfg) setNAT(ctx, cfg) @@ -1128,8 +1094,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { checkExclusive(ctx, DeveloperFlag, TestnetFlag, RinkebyFlag) checkExclusive(ctx, LightServFlag, SyncModeFlag, "light") - ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) - setEtherbase(ctx, ks, cfg) + setEtherbase(ctx, cfg) setGPO(ctx, &cfg.GPO) setTxPool(ctx, &cfg.TxPool) setEthash(ctx, cfg) @@ -1223,24 +1188,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { cfg.NetworkId = 1337 } // Create new developer account or reuse existing one - var ( - developer accounts.Account - err error - ) - if accs := ks.Accounts(); len(accs) > 0 { - developer = ks.Accounts()[0] - } else { - developer, err = ks.NewAccount("") - if err != nil { - Fatalf("Failed to create developer account: %v", err) - } - } - if err := ks.Unlock(developer, ""); err != nil { - Fatalf("Failed to unlock developer account: %v", err) - } - log.Info("Using developer account", "address", developer.Address) - - cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address) + // TODO @holiman + Fatalf("Failed to create developer account: not implemented") + //cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address) if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(MinerLegacyGasPriceFlag.Name) { cfg.MinerGasPrice = big.NewInt(1) } @@ -1267,7 +1217,7 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) { }) } else { err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { - fullNode, err := eth.New(ctx, cfg) + fullNode, err := eth.New(ctx, cfg, stack.ExternalSignerAPI()) if fullNode != nil && cfg.LightServ > 0 { ls, _ := les.NewLesServer(fullNode, cfg) fullNode.AddLesServer(ls) diff --git a/console/console.go b/console/console.go index 3c397f8006..c0fe72fe68 100644 --- a/console/console.go +++ b/console/console.go @@ -159,37 +159,6 @@ func (c *Console) init(preload []string) error { // Initialize the global name register (disabled for now) //c.jsre.Run(`var GlobalRegistrar = eth.contract(` + registrar.GlobalRegistrarAbi + `); registrar = GlobalRegistrar.at("` + registrar.GlobalRegistrarAddr + `");`) - // If the console is in interactive mode, instrument password related methods to query the user - if c.prompter != nil { - // Retrieve the account management object to instrument - personal, err := c.jsre.Get("personal") - if err != nil { - return err - } - // Override the openWallet, unlockAccount, newAccount and sign methods since - // these require user interaction. Assign these method in the Console the - // original web3 callbacks. These will be called by the jeth.* methods after - // they got the password from the user and send the original web3 request to - // the backend. - if obj := personal.Object(); obj != nil { // make sure the personal api is enabled over the interface - if _, err = c.jsre.Run(`jeth.openWallet = personal.openWallet;`); err != nil { - return fmt.Errorf("personal.openWallet: %v", err) - } - if _, err = c.jsre.Run(`jeth.unlockAccount = personal.unlockAccount;`); err != nil { - return fmt.Errorf("personal.unlockAccount: %v", err) - } - if _, err = c.jsre.Run(`jeth.newAccount = personal.newAccount;`); err != nil { - return fmt.Errorf("personal.newAccount: %v", err) - } - if _, err = c.jsre.Run(`jeth.sign = personal.sign;`); err != nil { - return fmt.Errorf("personal.sign: %v", err) - } - obj.Set("openWallet", bridge.OpenWallet) - obj.Set("unlockAccount", bridge.UnlockAccount) - obj.Set("newAccount", bridge.NewAccount) - obj.Set("sign", bridge.Sign) - } - } // The admin.sleep and admin.sleepBlocks are offered by the console and not by the RPC layer. admin, err := c.jsre.Get("admin") if err != nil { diff --git a/eth/api_backend.go b/eth/api_backend.go index 8748d444fa..6609d6badf 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -20,7 +20,6 @@ import ( "context" "math/big" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core" @@ -34,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/internal/ethapi" ) // EthAPIBackend implements ethapi.Backend for full nodes @@ -209,10 +209,6 @@ func (b *EthAPIBackend) EventMux() *event.TypeMux { return b.eth.EventMux() } -func (b *EthAPIBackend) AccountManager() *accounts.Manager { - return b.eth.AccountManager() -} - func (b *EthAPIBackend) BloomStatus() (uint64, uint64) { sections, _, _ := b.eth.bloomIndexer.Sections() return params.BloomBitsBlocks, sections @@ -223,3 +219,8 @@ func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) } } +func (b *EthAPIBackend) ExternalSigner() *ethapi.ExternalSignerAPI { + return b.eth.externalSigner +} + + diff --git a/eth/backend.go b/eth/backend.go index b555b064ad..f53c0c8c0d 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -25,7 +25,6 @@ import ( "sync" "sync/atomic" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" @@ -77,7 +76,7 @@ type Ethereum struct { eventMux *event.TypeMux engine consensus.Engine - accountManager *accounts.Manager + externalSigner *ethapi.ExternalSignerAPI bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports @@ -101,7 +100,7 @@ func (s *Ethereum) AddLesServer(ls LesServer) { // New creates a new Ethereum object (including the // initialisation of the common Ethereum object) -func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { +func New(ctx *node.ServiceContext, config *Config, api *ethapi.ExternalSignerAPI) (*Ethereum, error) { // Ensure configuration values are compatible and sane if config.SyncMode == downloader.LightSync { return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum") @@ -126,10 +125,10 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { eth := &Ethereum{ config: config, + externalSigner: api, chainDb: chainDb, chainConfig: chainConfig, eventMux: ctx.EventMux, - accountManager: ctx.AccountManager, engine: CreateConsensusEngine(ctx, chainConfig, &config.Ethash, config.MinerNotify, config.MinerNoverify, chainDb), shutdownChan: make(chan bool), networkID: config.NetworkId, @@ -319,18 +318,6 @@ func (s *Ethereum) Etherbase() (eb common.Address, err error) { if etherbase != (common.Address{}) { return etherbase, nil } - if wallets := s.AccountManager().Wallets(); len(wallets) > 0 { - if accounts := wallets[0].Accounts(); len(accounts) > 0 { - etherbase := accounts[0].Address - - s.lock.Lock() - s.etherbase = etherbase - s.lock.Unlock() - - log.Info("Etherbase automatically configured", "address", etherbase) - return etherbase, nil - } - } return common.Address{}, fmt.Errorf("etherbase must be explicitly specified") } @@ -426,13 +413,10 @@ func (s *Ethereum) StartMining(threads int) error { log.Error("Cannot start mining without etherbase", "err", err) return fmt.Errorf("etherbase missing: %v", err) } - if clique, ok := s.engine.(*clique.Clique); ok { - wallet, err := s.accountManager.Find(accounts.Account{Address: eb}) - if wallet == nil || err != nil { - log.Error("Etherbase account unavailable locally", "err", err) - return fmt.Errorf("signer missing: %v", err) - } - clique.Authorize(eb, wallet.SignHash) + if _, ok := s.engine.(*clique.Clique); ok { + //TODO @holiman + log.Error("Etherbase account unavailable locally", "err", err) + return fmt.Errorf("signer not configured: %v", err) } // If mining is started, we can disable the transaction rejection mechanism // introduced to speed sync times. @@ -460,16 +444,16 @@ func (s *Ethereum) StopMining() { func (s *Ethereum) IsMining() bool { return s.miner.Mining() } func (s *Ethereum) Miner() *miner.Miner { return s.miner } -func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager } -func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain } -func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } -func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } -func (s *Ethereum) Engine() consensus.Engine { return s.engine } -func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } -func (s *Ethereum) IsListening() bool { return true } // Always listening -func (s *Ethereum) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) } -func (s *Ethereum) NetVersion() uint64 { return s.networkID } -func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } +func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain } +func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } +func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } +func (s *Ethereum) Engine() consensus.Engine { return s.engine } +func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } +func (s *Ethereum) IsListening() bool { return true } // Always listening +func (s *Ethereum) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) } +func (s *Ethereum) NetVersion() uint64 { return s.networkID } +func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } +func (s *Ethereum) ExternalSigner() *ethapi.ExternalSignerAPI { return s.externalSigner } // Protocols implements node.Service, returning all the currently configured // network protocols to start. diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 8016ebe3d7..4242a8b106 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -177,53 +177,33 @@ func (s *PublicTxPoolAPI) Inspect() map[string]map[string]map[string]string { return content } -// PublicAccountAPI provides an API to access accounts managed by this node. -// It offers only methods that can retrieve accounts. -type PublicAccountAPI struct { - am *accounts.Manager -} - -// NewPublicAccountAPI creates a new PublicAccountAPI. -func NewPublicAccountAPI(am *accounts.Manager) *PublicAccountAPI { - return &PublicAccountAPI{am: am} -} - -// Accounts returns the collection of accounts this node manages -func (s *PublicAccountAPI) Accounts() []common.Address { - addresses := make([]common.Address, 0) // return [] instead of nil if empty - for _, wallet := range s.am.Wallets() { - for _, account := range wallet.Accounts() { - addresses = append(addresses, account.Address) - } - } - return addresses -} - // PrivateAccountAPI provides an API to access accounts managed by this node. // It offers methods to create, (un)lock en list accounts. Some methods accept // passwords and are therefore considered private by default. type PrivateAccountAPI struct { - am *accounts.Manager nonceLock *AddrLocker b Backend + extapi *ExternalSignerAPI // external signer, nil if not used } // NewPrivateAccountAPI create a new PrivateAccountAPI. func NewPrivateAccountAPI(b Backend, nonceLock *AddrLocker) *PrivateAccountAPI { - return &PrivateAccountAPI{ - am: b.AccountManager(), + p := &PrivateAccountAPI{ nonceLock: nonceLock, b: b, + extapi: b.ExternalSigner(), } + return p } // ListAccounts will return a list of addresses for accounts this node manages. func (s *PrivateAccountAPI) ListAccounts() []common.Address { addresses := make([]common.Address, 0) // return [] instead of nil if empty - for _, wallet := range s.am.Wallets() { - for _, account := range wallet.Accounts() { - addresses = append(addresses, account.Address) + if s.extapi != nil { + if accounts, err := s.extapi.listAccounts(); err == nil { + return accounts } + return addresses } return addresses } @@ -237,65 +217,9 @@ type rawWallet struct { Accounts []accounts.Account `json:"accounts,omitempty"` } -// ListWallets will return a list of wallets this node manages. -func (s *PrivateAccountAPI) ListWallets() []rawWallet { - wallets := make([]rawWallet, 0) // return [] instead of nil if empty - for _, wallet := range s.am.Wallets() { - status, failure := wallet.Status() - - raw := rawWallet{ - URL: wallet.URL().String(), - Status: status, - Accounts: wallet.Accounts(), - } - if failure != nil { - raw.Failure = failure.Error() - } - wallets = append(wallets, raw) - } - return wallets -} - -// OpenWallet initiates a hardware wallet opening procedure, establishing a USB -// connection and attempting to authenticate via the provided passphrase. Note, -// the method may return an extra challenge requiring a second open (e.g. the -// Trezor PIN matrix challenge). -func (s *PrivateAccountAPI) OpenWallet(url string, passphrase *string) error { - wallet, err := s.am.Wallet(url) - if err != nil { - return err - } - pass := "" - if passphrase != nil { - pass = *passphrase - } - return wallet.Open(pass) -} - -// DeriveAccount requests a HD wallet to derive a new account, optionally pinning -// it for later reuse. -func (s *PrivateAccountAPI) DeriveAccount(url string, path string, pin *bool) (accounts.Account, error) { - wallet, err := s.am.Wallet(url) - if err != nil { - return accounts.Account{}, err - } - derivPath, err := accounts.ParseDerivationPath(path) - if err != nil { - return accounts.Account{}, err - } - if pin == nil { - pin = new(bool) - } - return wallet.Derive(derivPath, *pin) -} - // NewAccount will create a new account and returns the address for the new account. func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error) { - acc, err := fetchKeystore(s.am).NewAccount(password) - if err == nil { - return acc.Address, nil - } - return common.Address{}, err + return s.extapi.newAccount() } // fetchKeystore retrives the encrypted keystore from the account manager. @@ -306,58 +230,30 @@ func fetchKeystore(am *accounts.Manager) *keystore.KeyStore { // ImportRawKey stores the given hex encoded ECDSA key into the key directory, // encrypting it with the passphrase. func (s *PrivateAccountAPI) ImportRawKey(privkey string, password string) (common.Address, error) { - key, err := crypto.HexToECDSA(privkey) - if err != nil { - return common.Address{}, err - } - acc, err := fetchKeystore(s.am).ImportECDSA(key, password) - return acc.Address, err + return common.Address{}, errors.New("Importing of raw keys is disabled when external signer is used.") } // UnlockAccount will unlock the account associated with the given address with // the given password for duration seconds. If duration is nil it will use a // default of 300 seconds. It returns an indication if the account was unlocked. func (s *PrivateAccountAPI) UnlockAccount(addr common.Address, password string, duration *uint64) (bool, error) { - const max = uint64(time.Duration(math.MaxInt64) / time.Second) - var d time.Duration - if duration == nil { - d = 300 * time.Second - } else if *duration > max { - return false, errors.New("unlock duration too large") - } else { - d = time.Duration(*duration) * time.Second - } - err := fetchKeystore(s.am).TimedUnlock(accounts.Account{Address: addr}, password, d) - return err == nil, err + return false, errors.New("Unlock is disabled when external signer is used.") } // LockAccount will lock the account associated with the given address when it's unlocked. func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool { - return fetchKeystore(s.am).Lock(addr) == nil + return false } // signTransactions sets defaults and signs the given transaction // NOTE: the caller needs to ensure that the nonceLock is held, if applicable, // and release it after the transaction has been submitted to the tx pool func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args SendTxArgs, passwd string) (*types.Transaction, error) { - // Look up the wallet containing the requested signer - account := accounts.Account{Address: args.From} - wallet, err := s.am.Find(account) - if err != nil { - return nil, err - } // Set some sanity defaults and terminate on failure if err := args.setDefaults(ctx, s.b); err != nil { return nil, err } - // Assemble the transaction and sign with the wallet - tx := args.toTransaction() - - var chainID *big.Int - if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) { - chainID = config.ChainID - } - return wallet.SignTxWithPassphrase(account, passwd, tx, chainID) + return s.extapi.signTransaction(ctx, args) } // SendTransaction will create a transaction from the given arguments and @@ -416,32 +312,6 @@ func signHash(data []byte) []byte { return crypto.Keccak256([]byte(msg)) } -// Sign calculates an Ethereum ECDSA signature for: -// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) -// -// Note, the produced signature conforms to the secp256k1 curve R, S and V values, -// where the V value will be 27 or 28 for legacy reasons. -// -// The key used to calculate the signature is decrypted with the given password. -// -// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign -func (s *PrivateAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr common.Address, passwd string) (hexutil.Bytes, error) { - // Look up the wallet containing the requested signer - account := accounts.Account{Address: addr} - - wallet, err := s.b.AccountManager().Find(account) - if err != nil { - return nil, err - } - // Assemble sign the data with the wallet - signature, err := wallet.SignHashWithPassphrase(account, passwd, signHash(data)) - if err != nil { - return nil, err - } - signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - return signature, nil -} - // EcRecover returns the address for the account that was used to create the signature. // Note, this function is compatible with eth_sign and personal_sign. As such it recovers // the address of: @@ -471,7 +341,7 @@ func (s *PrivateAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.Byt // SignAndSendTransaction was renamed to SendTransaction. This method is deprecated // and will be removed in the future. It primary goal is to give clients time to update. func (s *PrivateAccountAPI) SignAndSendTransaction(ctx context.Context, args SendTxArgs, passwd string) (common.Hash, error) { - return s.SendTransaction(ctx, args, passwd) + return common.Hash{}, errors.New("SignAndSend is deprecated, please use sendTransaction instead") } // PublicBlockChainAPI provides an API to access the Ethereum blockchain. @@ -618,15 +488,8 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr if state == nil || err != nil { return nil, 0, false, err } - // Set sender address or use a default if none specified + // Set sender address addr := args.From - if addr == (common.Address{}) { - if wallets := s.b.AccountManager().Wallets(); len(wallets) > 0 { - if accounts := wallets[0].Accounts(); len(accounts) > 0 { - addr = accounts[0].Address - } - } - } // Set default gas & gas price if none were set gas, gasPrice := uint64(args.Gas), args.GasPrice.ToInt() if gas == 0 { @@ -943,11 +806,12 @@ func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransa type PublicTransactionPoolAPI struct { b Backend nonceLock *AddrLocker + extapi *ExternalSignerAPI // external signer, nil if not used } // NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool. func NewPublicTransactionPoolAPI(b Backend, nonceLock *AddrLocker) *PublicTransactionPoolAPI { - return &PublicTransactionPoolAPI{b, nonceLock} + return &PublicTransactionPoolAPI{b, nonceLock, b.ExternalSigner()} } // GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number. @@ -1090,23 +954,6 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha return fields, nil } -// sign is a helper function that signs a transaction with the private key of the given address. -func (s *PublicTransactionPoolAPI) sign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { - // Look up the wallet containing the requested signer - account := accounts.Account{Address: addr} - - wallet, err := s.b.AccountManager().Find(account) - if err != nil { - return nil, err - } - // Request the wallet to sign the transaction - var chainID *big.Int - if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) { - chainID = config.ChainID - } - return wallet.SignTx(account, tx, chainID) -} - // SendTxArgs represents the arguments to sumbit a new transaction into the transaction pool. type SendTxArgs struct { From common.Address `json:"from"` @@ -1197,34 +1044,20 @@ func submitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c // SendTransaction creates a transaction for the given argument, sign it and submit it to the // transaction pool. func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) { - - // Look up the wallet containing the requested signer - account := accounts.Account{Address: args.From} - - wallet, err := s.b.AccountManager().Find(account) - if err != nil { - return common.Hash{}, err + if s.extapi == nil { + return common.Hash{}, errors.New("No external signer configured") } - if args.Nonce == nil { // Hold the addresse's mutex around signing to prevent concurrent assignment of // the same nonce to multiple accounts. s.nonceLock.LockAddr(args.From) defer s.nonceLock.UnlockAddr(args.From) } - // Set some sanity defaults and terminate on failure if err := args.setDefaults(ctx, s.b); err != nil { return common.Hash{}, err } - // Assemble the transaction and sign with the wallet - tx := args.toTransaction() - - var chainID *big.Int - if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) { - chainID = config.ChainID - } - signed, err := wallet.SignTx(account, tx, chainID) + signed, err := s.extapi.signTransaction(ctx, args) if err != nil { return common.Hash{}, err } @@ -1251,19 +1084,7 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign func (s *PublicTransactionPoolAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) { - // Look up the wallet containing the requested signer - account := accounts.Account{Address: addr} - - wallet, err := s.b.AccountManager().Find(account) - if err != nil { - return nil, err - } - // Sign the requested hash with the wallet - signature, err := wallet.SignHash(account, signHash(data)) - if err == nil { - signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - } - return signature, err + return nil, errors.New("Sign data not implemented") } // SignTransactionResult represents a RLP encoded signed transaction. @@ -1288,7 +1109,13 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen if err := args.setDefaults(ctx, s.b); err != nil { return nil, err } - tx, err := s.sign(args.From, args.toTransaction()) + var ( + tx *types.Transaction + err error + ) + + tx, err = s.extapi.signTransaction(ctx, args) + if err != nil { return nil, err } @@ -1302,28 +1129,9 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen // PendingTransactions returns the transactions that are in the transaction pool // and have a from address that is one of the accounts this node manages. func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, error) { - pending, err := s.b.GetPoolTransactions() - if err != nil { - return nil, err - } - accounts := make(map[common.Address]struct{}) - for _, wallet := range s.b.AccountManager().Wallets() { - for _, account := range wallet.Accounts() { - accounts[account.Address] = struct{}{} - } - } - transactions := make([]*RPCTransaction, 0, len(pending)) - for _, tx := range pending { - var signer types.Signer = types.HomesteadSigner{} - if tx.Protected() { - signer = types.NewEIP155Signer(tx.ChainId()) - } - from, _ := types.Sender(signer, tx) - if _, exists := accounts[from]; exists { - transactions = append(transactions, newRPCPendingTransaction(tx)) - } - } - return transactions, nil + return nil, errors.New("Not implemented") + // TODO @holiman, make this call take a list of addresses, and show the penading transactions that are from that + // set of addresses } // Resend accepts an existing transaction and a new gas price and limit. It will remove @@ -1340,7 +1148,9 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr if err != nil { return common.Hash{}, err } - + var ( + signedTx *types.Transaction + ) for _, p := range pending { var signer types.Signer = types.HomesteadSigner{} if p.Protected() { @@ -1356,7 +1166,7 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr if gasLimit != nil && *gasLimit != 0 { sendArgs.Gas = gasLimit } - signedTx, err := s.sign(sendArgs.From, sendArgs.toTransaction()) + signedTx, err = s.extapi.signTransaction(ctx, sendArgs) if err != nil { return common.Hash{}, err } @@ -1489,3 +1299,53 @@ func (s *PublicNetAPI) PeerCount() hexutil.Uint { func (s *PublicNetAPI) Version() string { return fmt.Sprintf("%d", s.networkVersion) } + +// ExternalSignerAPI provides an API to interact with an external signer (clef) +// It proxies request to the external signer while forwarding relevant +// request headers +type ExternalSignerAPI struct { + client *rpc.Client +} + +func NewExternalSigner(endpoint string) (*ExternalSignerAPI, error) { + client, err := rpc.DialHTTP(endpoint) + if err != nil { + return nil, err + } + return &ExternalSignerAPI{ + client: client, + }, nil +} + +func (api *ExternalSignerAPI) signTransaction(ctx context.Context, args SendTxArgs) (*types.Transaction, error) { + if api == nil{ + return nil, errors.New("External API not initialized") + } + res := SignTransactionResult{} + if err := api.client.Call(&res, "account_signTransaction", args); err != nil { + return nil, err + } + return res.Tx, nil +} +func (api *ExternalSignerAPI) listAccounts() ([]common.Address, error) { + if api == nil{ + return []common.Address{}, errors.New("External API not initialized") + } + var res []common.Address + if err := api.client.Call(&res, "account_listAccounts"); err != nil { + return nil, err + } + return res, nil +} +func (api *ExternalSignerAPI) newAccount() (common.Address, error) { + if api == nil{ + return common.Address{}, errors.New("External API not initialized") + } + var res accounts.Account + + if err := api.client.Call(&res, "account_new"); err != nil { + return res.Address, nil + } + return common.Address{}, nil + +} diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index c9ffe230c6..2e785b853c 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -21,7 +21,6 @@ import ( "context" "math/big" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" @@ -43,8 +42,8 @@ type Backend interface { SuggestPrice(ctx context.Context) (*big.Int, error) ChainDb() ethdb.Database EventMux() *event.TypeMux - AccountManager() *accounts.Manager - + ExternalSigner() *ExternalSignerAPI + // BlockChain API SetHead(number uint64) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) @@ -103,12 +102,7 @@ func GetAPIs(apiBackend Backend) []rpc.API { Namespace: "debug", Version: "1.0", Service: NewPrivateDebugAPI(apiBackend), - }, { - Namespace: "eth", - Version: "1.0", - Service: NewPublicAccountAPI(apiBackend.AccountManager()), - Public: true, - }, { + },{ Namespace: "personal", Version: "1.0", Service: NewPrivateAccountAPI(apiBackend, nonceLock), diff --git a/les/api_backend.go b/les/api_backend.go index aa748a4ea1..2e1dfc5342 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -20,7 +20,6 @@ import ( "context" "math/big" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core" @@ -36,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/internal/ethapi" ) type LesApiBackend struct { @@ -183,8 +183,8 @@ func (b *LesApiBackend) EventMux() *event.TypeMux { return b.eth.eventMux } -func (b *LesApiBackend) AccountManager() *accounts.Manager { - return b.eth.accountManager +func (b *LesApiBackend) ExternalSigner() *ethapi.ExternalSignerAPI{ + return b.eth.externalSigner } func (b *LesApiBackend) BloomStatus() (uint64, uint64) { diff --git a/les/backend.go b/les/backend.go index a3474a6830..5ebcf977da 100644 --- a/les/backend.go +++ b/les/backend.go @@ -22,7 +22,6 @@ import ( "sync" "time" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" @@ -69,7 +68,7 @@ type LightEthereum struct { eventMux *event.TypeMux engine consensus.Engine - accountManager *accounts.Manager + externalSigner *ethapi.ExternalSignerAPI networkId uint64 netRPCService *ethapi.PublicNetAPI @@ -101,7 +100,6 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { eventMux: ctx.EventMux, peers: peers, reqDist: newRequestDistributor(peers, quitSync), - accountManager: ctx.AccountManager, engine: eth.CreateConsensusEngine(ctx, chainConfig, &config.Ethash, nil, false, chainDb), shutdownChan: make(chan bool), networkId: config.NetworkId, diff --git a/node/config.go b/node/config.go index 8f10f4f613..88cbe530c3 100644 --- a/node/config.go +++ b/node/config.go @@ -86,6 +86,10 @@ type Config struct { // NoUSB disables hardware wallet monitoring and connectivity. NoUSB bool `toml:",omitempty"` + // ExternalSigner path/url to external signer. Implicitly disables native account + // management + ExternalSigner string `toml:",omitempty"` + // IPCPath is the requested location to place the IPC endpoint. If the path is // a simple file name, it is placed inside the data directory (or on the root // pipe path on Windows), whereas if it's a resolvable path name (absolute or diff --git a/node/node.go b/node/node.go index ada3837217..60923b2c07 100644 --- a/node/node.go +++ b/node/node.go @@ -26,7 +26,6 @@ import ( "strings" "sync" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/debug" @@ -34,15 +33,15 @@ import ( "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rpc" "github.com/prometheus/prometheus/util/flock" + "github.com/ethereum/go-ethereum/internal/ethapi" ) // Node is a container on which services can be registered. type Node struct { eventmux *event.TypeMux // Event multiplexer used between the services of a stack config *Config - accman *accounts.Manager + extapi *ethapi.ExternalSignerAPI// If non-empty, the ipc-path or http(s)-url to an external signer - ephemeralKeystore string // if non-empty, the key directory that will be removed by Stop instanceDirLock flock.Releaser // prevents concurrent use of instance directory serverConfig p2p.Config @@ -97,20 +96,12 @@ func New(conf *Config) (*Node, error) { if strings.HasSuffix(conf.Name, ".ipc") { return nil, errors.New(`Config.Name cannot end in ".ipc"`) } - // Ensure that the AccountManager method works before the node has started. - // We rely on this in cmd/geth. - am, ephemeralKeystore, err := makeAccountManager(conf) - if err != nil { - return nil, err - } if conf.Logger == nil { conf.Logger = log.New() } // Note: any interaction with Config that would create/touch files // in the data directory or instance directory is delayed until Start. return &Node{ - accman: am, - ephemeralKeystore: ephemeralKeystore, config: conf, serviceFuncs: []ServiceConstructor{}, ipcEndpoint: conf.IPCEndpoint(), @@ -173,7 +164,6 @@ func (n *Node) Start() error { config: n.config, services: make(map[reflect.Type]Service), EventMux: n.eventmux, - AccountManager: n.accman, } for kind, s := range services { // copy needed for threaded access ctx.services[kind] = s @@ -435,18 +425,9 @@ func (n *Node) Stop() error { // unblock n.Wait close(n.stop) - // Remove the keystore if it was created ephemerally. - var keystoreErr error - if n.ephemeralKeystore != "" { - keystoreErr = os.RemoveAll(n.ephemeralKeystore) - } - if len(failure.Services) > 0 { return failure } - if keystoreErr != nil { - return keystoreErr - } return nil } @@ -537,11 +518,6 @@ func (n *Node) InstanceDir() string { return n.config.instanceDir() } -// AccountManager retrieves the account manager used by the protocol stack. -func (n *Node) AccountManager() *accounts.Manager { - return n.accman -} - // IPCEndpoint retrieves the current IPC endpoint used by the protocol stack. func (n *Node) IPCEndpoint() string { return n.ipcEndpoint @@ -578,6 +554,13 @@ func (n *Node) ResolvePath(x string) string { return n.config.ResolvePath(x) } +func (n *Node) SetExternalAPI(api *ethapi.ExternalSignerAPI){ + n.extapi = api +} + +func (n *Node) ExternalSignerAPI() *ethapi.ExternalSignerAPI{ + return n.extapi +} // apis returns the collection of RPC descriptors this node offers. func (n *Node) apis() []rpc.API { return []rpc.API{ diff --git a/node/service.go b/node/service.go index 6a96d9b1e1..e2e38c5542 100644 --- a/node/service.go +++ b/node/service.go @@ -19,7 +19,6 @@ package node import ( "reflect" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/p2p" @@ -33,7 +32,6 @@ type ServiceContext struct { config *Config services map[reflect.Type]Service // Index of the already constructed services EventMux *event.TypeMux // Event multiplexer used for decoupled notifications - AccountManager *accounts.Manager // Account manager created by the node. } // OpenDatabase opens an existing database with the given name (or creates one