This commit is contained in:
Martin Holst Swende 2018-10-10 07:52:06 +00:00 committed by GitHub
commit 966c635854
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 330 additions and 1189 deletions

View file

@ -1,5 +1,12 @@
### Changelog for external API ### Changelog for external API
### 5.0.0
* The external method `accounts_Sign(address, data)` was replaced with `accounts_signData(contentType, address, data)`.
The addition of `contentType` makes it possible to use the method for different types of objects, such as
* signing clique headers,
* signing [ERC-712](https://eips.ethereum.org/EIPS/eip-712) typed data structures (not yet implemented)
#### 4.0.0 #### 4.0.0
* The external `account_Ecrecover`-method was removed. * The external `account_Ecrecover`-method was removed.

View file

@ -39,14 +39,17 @@ import (
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/console" "github.com/ethereum/go-ethereum/console"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/signer/core" "github.com/ethereum/go-ethereum/signer/core"
"github.com/ethereum/go-ethereum/signer/rules" "github.com/ethereum/go-ethereum/signer/rules"
"github.com/ethereum/go-ethereum/signer/storage" "github.com/ethereum/go-ethereum/signer/storage"
"gopkg.in/urfave/cli.v1" "gopkg.in/urfave/cli.v1"
"math/big"
) )
// ExternalAPIVersion -- see extapi_changelog.md // ExternalAPIVersion -- see extapi_changelog.md
@ -631,10 +634,38 @@ func testExternalUI(api *core.SignerAPI) {
} }
var err error var err error
cliqueHeader := types.Header{
common.HexToHash("0000H45H"),
common.HexToHash("0000H45H"),
common.HexToAddress("0000H45H"),
common.HexToHash("0000H00H"),
common.HexToHash("0000H45H"),
common.HexToHash("0000H45H"),
types.Bloom{},
big.NewInt(1337),
big.NewInt(1337),
1338,
1338,
big.NewInt(1338),
[]byte("Extra data Extra data Extra data Extra data Extra data Extra data Extra data Extra data"),
common.HexToHash("0x0000H45H"),
types.BlockNonce{},
}
cliqueRlp, err := rlp.EncodeToBytes(cliqueHeader)
if err != nil {
utils.Fatalf("Should not error: %v", err)
}
addr, err:= common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
if err != nil {
utils.Fatalf("Should not error: %v", err)
}
_, err = api.SignData(ctx, "application/clique", *addr, cliqueRlp)
checkErr("SignData", err)
_, err = api.SignTransaction(ctx, core.SendTxArgs{From: common.MixedcaseAddress{}}, nil) _, err = api.SignTransaction(ctx, core.SendTxArgs{From: common.MixedcaseAddress{}}, nil)
checkErr("SignTransaction", err) checkErr("SignTransaction", err)
_, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304")) //_, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304"))
checkErr("Sign", err) //checkErr("Sign", err)
_, err = api.List(ctx) _, err = api.List(ctx)
checkErr("List", err) checkErr("List", err)
_, err = api.New(ctx) _, err = api.New(ctx)

View file

@ -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 <http://www.gnu.org/licenses/>.
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: "<keyFile>",
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 <DATADIR>/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: "<address>",
Flags: []cli.Flag{
utils.DataDirFlag,
utils.KeyStoreDirFlag,
utils.LightKDFFlag,
},
Description: `
geth account update <address>
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] <address>
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: "<keyFile>",
Description: `
geth account import <keyfile>
Imports an unencrypted private key from <keyfile> 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] <keyfile>
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
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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()
}

View file

@ -152,9 +152,7 @@ func enableWhisper(ctx *cli.Context) bool {
func makeFullNode(ctx *cli.Context) *node.Node { func makeFullNode(ctx *cli.Context) *node.Node {
stack, cfg := makeConfigNode(ctx) stack, cfg := makeConfigNode(ctx)
utils.RegisterEthService(stack, &cfg.Eth) utils.RegisterEthService(stack, &cfg.Eth)
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) { if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit) utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit)
} }

View file

@ -25,21 +25,18 @@ import (
godebug "runtime/debug" godebug "runtime/debug"
"sort" "sort"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/elastic/gosigar" "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/cmd/utils"
"github.com/ethereum/go-ethereum/console" "github.com/ethereum/go-ethereum/console"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"gopkg.in/urfave/cli.v1" "gopkg.in/urfave/cli.v1"
"github.com/ethereum/go-ethereum/internal/ethapi"
) )
const ( const (
@ -54,13 +51,10 @@ var (
// flags that configure the node // flags that configure the node
nodeFlags = []cli.Flag{ nodeFlags = []cli.Flag{
utils.IdentityFlag, utils.IdentityFlag,
utils.UnlockedAccountFlag,
utils.PasswordFileFlag,
utils.BootnodesFlag, utils.BootnodesFlag,
utils.BootnodesV4Flag, utils.BootnodesV4Flag,
utils.BootnodesV5Flag, utils.BootnodesV5Flag,
utils.DataDirFlag, utils.DataDirFlag,
utils.KeyStoreDirFlag,
utils.NoUSBFlag, utils.NoUSBFlag,
utils.DashboardEnabledFlag, utils.DashboardEnabledFlag,
utils.DashboardAddrFlag, utils.DashboardAddrFlag,
@ -73,6 +67,7 @@ var (
utils.EthashDatasetsInMemoryFlag, utils.EthashDatasetsInMemoryFlag,
utils.EthashDatasetsOnDiskFlag, utils.EthashDatasetsOnDiskFlag,
utils.TxPoolLocalsFlag, utils.TxPoolLocalsFlag,
utils.ExternalSignerFlag,
utils.TxPoolNoLocalsFlag, utils.TxPoolNoLocalsFlag,
utils.TxPoolJournalFlag, utils.TxPoolJournalFlag,
utils.TxPoolRejournalFlag, utils.TxPoolRejournalFlag,
@ -183,9 +178,6 @@ func init() {
dumpCommand, dumpCommand,
// See monitorcmd.go: // See monitorcmd.go:
monitorCommand, monitorCommand,
// See accountcmd.go:
accountCommand,
walletCommand,
// See consolecmd.go: // See consolecmd.go:
consoleCommand, consoleCommand,
attachCommand, attachCommand,
@ -276,60 +268,19 @@ func geth(ctx *cli.Context) error {
func startNode(ctx *cli.Context, stack *node.Node) { func startNode(ctx *cli.Context, stack *node.Node) {
debug.Memsize.Add("node", stack) 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 // Start up the node itself
utils.StartNode(stack) 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 // Start auxiliary services if enabled
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) { if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) {
// Mining only makes sense if a full Ethereum node is running // Mining only makes sense if a full Ethereum node is running

View file

@ -69,7 +69,6 @@ var AppHelpFlagGroups = []flagGroup{
Flags: []cli.Flag{ Flags: []cli.Flag{
configFileFlag, configFileFlag,
utils.DataDirFlag, utils.DataDirFlag,
utils.KeyStoreDirFlag,
utils.NoUSBFlag, utils.NoUSBFlag,
utils.NetworkIdFlag, utils.NetworkIdFlag,
utils.TestnetFlag, utils.TestnetFlag,
@ -136,13 +135,6 @@ var AppHelpFlagGroups = []flagGroup{
utils.TrieCacheGenFlag, utils.TrieCacheGenFlag,
}, },
}, },
{
Name: "ACCOUNT",
Flags: []cli.Flag{
utils.UnlockedAccountFlag,
utils.PasswordFileFlag,
},
},
{ {
Name: "API AND CONSOLE", Name: "API AND CONSOLE",
Flags: []cli.Flag{ Flags: []cli.Flag{

View file

@ -565,7 +565,6 @@ pv(1) tool to get a progress bar:
utils.NATFlag, utils.NATFlag,
utils.IPCDisabledFlag, utils.IPCDisabledFlag,
utils.IPCPathFlag, utils.IPCPathFlag,
utils.PasswordFileFlag,
// bzzd-specific flags // bzzd-specific flags
CorsStringFlag, CorsStringFlag,
EnsAPIFlag, 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)) log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey))
return key return key
} }
// Otherwise try getting it from the keystore. utils.Fatalf(SWARM_ERR_NO_BZZACCOUNT)
am := stack.AccountManager() return nil
ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
return decryptStoreAccount(ks, bzzaccount, utils.MakePasswordList(ctx))
} }
// getPrivKey returns the private key of the specified bzzaccount // getPrivKey returns the private key of the specified bzzaccount

View file

@ -20,16 +20,13 @@ package utils
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"fmt" "fmt"
"io/ioutil"
"math/big" "math/big"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"time" "time"
"github.com/ethereum/go-ethereum/accounts" "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"
"github.com/ethereum/go-ethereum/common/fdlimit" "github.com/ethereum/go-ethereum/common/fdlimit"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
@ -379,17 +376,15 @@ var (
Usage: "Disable remote sealing verification", Usage: "Disable remote sealing verification",
} }
// Account settings // Account settings
UnlockedAccountFlag = cli.StringFlag{
Name: "unlock",
Usage: "Comma separated list of accounts to unlock",
Value: "",
}
PasswordFileFlag = cli.StringFlag{ PasswordFileFlag = cli.StringFlag{
Name: "password", Name: "password",
Usage: "Password file to use for non-interactive password input", 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: "", Value: "",
} }
VMEnableDebugFlag = cli.BoolFlag{ VMEnableDebugFlag = cli.BoolFlag{
Name: "vmdebug", Name: "vmdebug",
Usage: "Record information useful for VM and contract debugging", 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 // MakeAddress converts an account specified directly as a hex encoded string or
// a key index in the key store to an internal account representation. // 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 the specified account is a valid address, return it
if common.IsHexAddress(account) { ma, err := common.NewMixedcaseAddressFromString(account)
return accounts.Account{Address: common.HexToAddress(account)}, nil 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 if !ma.ValidChecksum() {
index, err := strconv.Atoi(account) return accounts.Account{}, fmt.Errorf("Invalid checksum on address %v", account)
if err != nil || index < 0 {
return accounts.Account{}, fmt.Errorf("invalid account address or index %q", account)
} }
log.Warn("-------------------------------------------------------------------") return accounts.Account{Address: common.HexToAddress(account)}, nil
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
} }
// setEtherbase retrieves the etherbase either from the directly specified // setEtherbase retrieves the etherbase either from the directly specified
// command line flags or from the keystore if CLI indexed. // 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 // Extract the current etherbase, new flag overriding legacy one
var etherbase string var etherbase string
if ctx.GlobalIsSet(MinerLegacyEtherbaseFlag.Name) { 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 // Convert the etherbase into an address and configure it
if etherbase != "" { if etherbase != "" {
account, err := MakeAddress(ks, etherbase) account, err := MakeAddress(etherbase)
if err != nil { if err != nil {
Fatalf("Invalid miner etherbase: %v", err) 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) { func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
setNodeKey(ctx, cfg) setNodeKey(ctx, cfg)
setNAT(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, DeveloperFlag, TestnetFlag, RinkebyFlag)
checkExclusive(ctx, LightServFlag, SyncModeFlag, "light") checkExclusive(ctx, LightServFlag, SyncModeFlag, "light")
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) setEtherbase(ctx, cfg)
setEtherbase(ctx, ks, cfg)
setGPO(ctx, &cfg.GPO) setGPO(ctx, &cfg.GPO)
setTxPool(ctx, &cfg.TxPool) setTxPool(ctx, &cfg.TxPool)
setEthash(ctx, cfg) setEthash(ctx, cfg)
@ -1223,24 +1188,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
cfg.NetworkId = 1337 cfg.NetworkId = 1337
} }
// Create new developer account or reuse existing one // Create new developer account or reuse existing one
var ( // TODO @holiman
developer accounts.Account Fatalf("Failed to create developer account: not implemented")
err error //cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address)
)
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)
if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(MinerLegacyGasPriceFlag.Name) { if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(MinerLegacyGasPriceFlag.Name) {
cfg.MinerGasPrice = big.NewInt(1) cfg.MinerGasPrice = big.NewInt(1)
} }
@ -1267,7 +1217,7 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) {
}) })
} else { } else {
err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { 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 { if fullNode != nil && cfg.LightServ > 0 {
ls, _ := les.NewLesServer(fullNode, cfg) ls, _ := les.NewLesServer(fullNode, cfg)
fullNode.AddLesServer(ls) fullNode.AddLesServer(ls)

View file

@ -39,7 +39,7 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
lru "github.com/hashicorp/golang-lru" "github.com/hashicorp/golang-lru"
) )
const ( const (
@ -136,9 +136,9 @@ var (
errRecentlySigned = errors.New("recently signed") errRecentlySigned = errors.New("recently signed")
) )
// SignerFn is a signer callback function to request a hash to be signed by a // SignerFn is a signer callback function to request a block header to be signed by a
// backing account. // backing account.
type SignerFn func(accounts.Account, []byte) ([]byte, error) type SignerFn func(accounts.Account, *types.Header) ([]byte, error)
// sigHash returns the hash which is used as input for the proof-of-authority // sigHash returns the hash which is used as input for the proof-of-authority
// signing. It is the hash of the entire header apart from the 65 byte signature // signing. It is the hash of the entire header apart from the 65 byte signature
@ -646,7 +646,7 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results c
log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle)) log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle))
} }
// Sign all the things! // Sign all the things!
sighash, err := signFn(accounts.Account{Address: signer}, sigHash(header).Bytes()) sighash, err := signFn(accounts.Account{Address: signer}, header)
if err != nil { if err != nil {
return err return err
} }

View file

@ -159,37 +159,6 @@ func (c *Console) init(preload []string) error {
// Initialize the global name register (disabled for now) // Initialize the global name register (disabled for now)
//c.jsre.Run(`var GlobalRegistrar = eth.contract(` + registrar.GlobalRegistrarAbi + `); registrar = GlobalRegistrar.at("` + registrar.GlobalRegistrarAddr + `");`) //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. // The admin.sleep and admin.sleepBlocks are offered by the console and not by the RPC layer.
admin, err := c.jsre.Get("admin") admin, err := c.jsre.Get("admin")
if err != nil { if err != nil {

View file

@ -20,7 +20,6 @@ import (
"context" "context"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
@ -34,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/internal/ethapi"
) )
// EthAPIBackend implements ethapi.Backend for full nodes // EthAPIBackend implements ethapi.Backend for full nodes
@ -209,10 +209,6 @@ func (b *EthAPIBackend) EventMux() *event.TypeMux {
return b.eth.EventMux() return b.eth.EventMux()
} }
func (b *EthAPIBackend) AccountManager() *accounts.Manager {
return b.eth.AccountManager()
}
func (b *EthAPIBackend) BloomStatus() (uint64, uint64) { func (b *EthAPIBackend) BloomStatus() (uint64, uint64) {
sections, _, _ := b.eth.bloomIndexer.Sections() sections, _, _ := b.eth.bloomIndexer.Sections()
return params.BloomBitsBlocks, 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) go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests)
} }
} }
func (b *EthAPIBackend) ExternalSigner() *ethapi.ExternalSignerClient {
return b.eth.externalSigner
}

View file

@ -20,12 +20,12 @@ package eth
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/accounts"
"math/big" "math/big"
"runtime" "runtime"
"sync" "sync"
"sync/atomic" "sync/atomic"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
@ -77,7 +77,7 @@ type Ethereum struct {
eventMux *event.TypeMux eventMux *event.TypeMux
engine consensus.Engine engine consensus.Engine
accountManager *accounts.Manager externalSigner *ethapi.ExternalSignerClient
bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports
@ -101,7 +101,7 @@ func (s *Ethereum) AddLesServer(ls LesServer) {
// New creates a new Ethereum object (including the // New creates a new Ethereum object (including the
// initialisation of the common Ethereum object) // initialisation of the common Ethereum object)
func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { func New(ctx *node.ServiceContext, config *Config, api *ethapi.ExternalSignerClient) (*Ethereum, error) {
// Ensure configuration values are compatible and sane // Ensure configuration values are compatible and sane
if config.SyncMode == downloader.LightSync { if config.SyncMode == downloader.LightSync {
return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum") return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum")
@ -126,10 +126,10 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
eth := &Ethereum{ eth := &Ethereum{
config: config, config: config,
externalSigner: api,
chainDb: chainDb, chainDb: chainDb,
chainConfig: chainConfig, chainConfig: chainConfig,
eventMux: ctx.EventMux, eventMux: ctx.EventMux,
accountManager: ctx.AccountManager,
engine: CreateConsensusEngine(ctx, chainConfig, &config.Ethash, config.MinerNotify, config.MinerNoverify, chainDb), engine: CreateConsensusEngine(ctx, chainConfig, &config.Ethash, config.MinerNotify, config.MinerNoverify, chainDb),
shutdownChan: make(chan bool), shutdownChan: make(chan bool),
networkID: config.NetworkId, networkID: config.NetworkId,
@ -319,18 +319,6 @@ func (s *Ethereum) Etherbase() (eb common.Address, err error) {
if etherbase != (common.Address{}) { if etherbase != (common.Address{}) {
return etherbase, nil 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") return common.Address{}, fmt.Errorf("etherbase must be explicitly specified")
} }
@ -427,12 +415,18 @@ func (s *Ethereum) StartMining(threads int) error {
return fmt.Errorf("etherbase missing: %v", err) return fmt.Errorf("etherbase missing: %v", err)
} }
if clique, ok := s.engine.(*clique.Clique); ok { if clique, ok := s.engine.(*clique.Clique); ok {
wallet, err := s.accountManager.Find(accounts.Account{Address: eb}) if s.ExternalSigner() != nil {
if wallet == nil || err != nil { signerFn := func(a accounts.Account, header *types.Header) ([]byte, error) {
log.Error("Etherbase account unavailable locally", "err", err) rlpHeader, err := rlp.EncodeToBytes(header)
return fmt.Errorf("signer missing: %v", err) if err != nil {
return nil, err
}
return s.ExternalSigner().SignCliqueBlock(a.Address, rlpHeader)
}
clique.Authorize(eb, signerFn)
} else {
return fmt.Errorf("external signer not configured")
} }
clique.Authorize(eb, wallet.SignHash)
} }
// If mining is started, we can disable the transaction rejection mechanism // If mining is started, we can disable the transaction rejection mechanism
// introduced to speed sync times. // introduced to speed sync times.
@ -460,16 +454,16 @@ func (s *Ethereum) StopMining() {
func (s *Ethereum) IsMining() bool { return s.miner.Mining() } func (s *Ethereum) IsMining() bool { return s.miner.Mining() }
func (s *Ethereum) Miner() *miner.Miner { return s.miner } 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) BlockChain() *core.BlockChain { return s.blockchain } func (s *Ethereum) TxPool() *core.TxPool { return s.txPool }
func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux }
func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } func (s *Ethereum) Engine() consensus.Engine { return s.engine }
func (s *Ethereum) Engine() consensus.Engine { return s.engine } func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb }
func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } func (s *Ethereum) IsListening() bool { return true } // Always listening
func (s *Ethereum) IsListening() bool { return true } // Always listening func (s *Ethereum) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) }
func (s *Ethereum) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) } func (s *Ethereum) NetVersion() uint64 { return s.networkID }
func (s *Ethereum) NetVersion() uint64 { return s.networkID } func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader }
func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } func (s *Ethereum) ExternalSigner() *ethapi.ExternalSignerClient { return s.externalSigner }
// Protocols implements node.Service, returning all the currently configured // Protocols implements node.Service, returning all the currently configured
// network protocols to start. // network protocols to start.

View file

@ -177,55 +177,31 @@ func (s *PublicTxPoolAPI) Inspect() map[string]map[string]map[string]string {
return content 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. // PrivateAccountAPI provides an API to access accounts managed by this node.
// It offers methods to create, (un)lock en list accounts. Some methods accept // It offers methods to create, (un)lock en list accounts. Some methods accept
// passwords and are therefore considered private by default. // passwords and are therefore considered private by default.
type PrivateAccountAPI struct { type PrivateAccountAPI struct {
am *accounts.Manager
nonceLock *AddrLocker nonceLock *AddrLocker
b Backend b Backend
extapi *ExternalSignerClient // external signer, nil if not used
} }
// NewPrivateAccountAPI create a new PrivateAccountAPI. // NewPrivateAccountAPI create a new PrivateAccountAPI.
func NewPrivateAccountAPI(b Backend, nonceLock *AddrLocker) *PrivateAccountAPI { func NewPrivateAccountAPI(b Backend, nonceLock *AddrLocker) *PrivateAccountAPI {
return &PrivateAccountAPI{ p := &PrivateAccountAPI{
am: b.AccountManager(),
nonceLock: nonceLock, nonceLock: nonceLock,
b: b, b: b,
} }
return p
} }
// ListAccounts will return a list of addresses for accounts this node manages. // ListAccounts will return a list of addresses for accounts this node manages.
func (s *PrivateAccountAPI) ListAccounts() []common.Address { func (s *PrivateAccountAPI) ListAccounts() ([]common.Address, error) {
addresses := make([]common.Address, 0) // return [] instead of nil if empty if extapi := s.b.ExternalSigner(); extapi != nil {
for _, wallet := range s.am.Wallets() { return extapi.ListAccounts()
for _, account := range wallet.Accounts() {
addresses = append(addresses, account.Address)
}
} }
return addresses // return [] instead of nil if empty
return []common.Address{}, errors.New("external signer not configured")
} }
// rawWallet is a JSON representation of an accounts.Wallet interface, with its // rawWallet is a JSON representation of an accounts.Wallet interface, with its
@ -237,65 +213,9 @@ type rawWallet struct {
Accounts []accounts.Account `json:"accounts,omitempty"` 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. // NewAccount will create a new account and returns the address for the new account.
func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error) { func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error) {
acc, err := fetchKeystore(s.am).NewAccount(password) return s.extapi.NewAccount()
if err == nil {
return acc.Address, nil
}
return common.Address{}, err
} }
// fetchKeystore retrives the encrypted keystore from the account manager. // fetchKeystore retrives the encrypted keystore from the account manager.
@ -306,58 +226,30 @@ func fetchKeystore(am *accounts.Manager) *keystore.KeyStore {
// ImportRawKey stores the given hex encoded ECDSA key into the key directory, // ImportRawKey stores the given hex encoded ECDSA key into the key directory,
// encrypting it with the passphrase. // encrypting it with the passphrase.
func (s *PrivateAccountAPI) ImportRawKey(privkey string, password string) (common.Address, error) { func (s *PrivateAccountAPI) ImportRawKey(privkey string, password string) (common.Address, error) {
key, err := crypto.HexToECDSA(privkey) return common.Address{}, errors.New("Importing of raw keys is disabled when external signer is used.")
if err != nil {
return common.Address{}, err
}
acc, err := fetchKeystore(s.am).ImportECDSA(key, password)
return acc.Address, err
} }
// UnlockAccount will unlock the account associated with the given address with // 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 // 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. // 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) { func (s *PrivateAccountAPI) UnlockAccount(addr common.Address, password string, duration *uint64) (bool, error) {
const max = uint64(time.Duration(math.MaxInt64) / time.Second) return false, errors.New("Unlock is disabled when external signer is used.")
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
} }
// LockAccount will lock the account associated with the given address when it's unlocked. // LockAccount will lock the account associated with the given address when it's unlocked.
func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool { 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 // signTransactions sets defaults and signs the given transaction
// NOTE: the caller needs to ensure that the nonceLock is held, if applicable, // 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 // 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) { 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 // Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b); err != nil { if err := args.setDefaults(ctx, s.b); err != nil {
return nil, err return nil, err
} }
// Assemble the transaction and sign with the wallet return s.extapi.SignTransaction(ctx, args)
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)
} }
// SendTransaction will create a transaction from the given arguments and // SendTransaction will create a transaction from the given arguments and
@ -416,32 +308,6 @@ func signHash(data []byte) []byte {
return crypto.Keccak256([]byte(msg)) 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. // 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 // Note, this function is compatible with eth_sign and personal_sign. As such it recovers
// the address of: // the address of:
@ -471,7 +337,7 @@ func (s *PrivateAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.Byt
// SignAndSendTransaction was renamed to SendTransaction. This method is deprecated // 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. // 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) { 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. // PublicBlockChainAPI provides an API to access the Ethereum blockchain.
@ -618,15 +484,8 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr
if state == nil || err != nil { if state == nil || err != nil {
return nil, 0, false, err return nil, 0, false, err
} }
// Set sender address or use a default if none specified // Set sender address
addr := args.From 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 // Set default gas & gas price if none were set
gas, gasPrice := uint64(args.Gas), args.GasPrice.ToInt() gas, gasPrice := uint64(args.Gas), args.GasPrice.ToInt()
if gas == 0 { if gas == 0 {
@ -943,11 +802,12 @@ func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransa
type PublicTransactionPoolAPI struct { type PublicTransactionPoolAPI struct {
b Backend b Backend
nonceLock *AddrLocker nonceLock *AddrLocker
extapi *ExternalSignerClient // external signer, nil if not used
} }
// NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool. // NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool.
func NewPublicTransactionPoolAPI(b Backend, nonceLock *AddrLocker) *PublicTransactionPoolAPI { 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. // GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number.
@ -1090,23 +950,6 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha
return fields, nil 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. // SendTxArgs represents the arguments to sumbit a new transaction into the transaction pool.
type SendTxArgs struct { type SendTxArgs struct {
From common.Address `json:"from"` From common.Address `json:"from"`
@ -1197,34 +1040,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 // SendTransaction creates a transaction for the given argument, sign it and submit it to the
// transaction pool. // transaction pool.
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) { func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {
if s.extapi == nil {
// Look up the wallet containing the requested signer return common.Hash{}, errors.New("No external signer configured")
account := accounts.Account{Address: args.From}
wallet, err := s.b.AccountManager().Find(account)
if err != nil {
return common.Hash{}, err
} }
if args.Nonce == nil { if args.Nonce == nil {
// Hold the addresse's mutex around signing to prevent concurrent assignment of // Hold the addresse's mutex around signing to prevent concurrent assignment of
// the same nonce to multiple accounts. // the same nonce to multiple accounts.
s.nonceLock.LockAddr(args.From) s.nonceLock.LockAddr(args.From)
defer s.nonceLock.UnlockAddr(args.From) defer s.nonceLock.UnlockAddr(args.From)
} }
// Set some sanity defaults and terminate on failure // Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b); err != nil { if err := args.setDefaults(ctx, s.b); err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
// Assemble the transaction and sign with the wallet signed, err := s.extapi.SignTransaction(ctx, args)
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)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
@ -1251,19 +1080,7 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod
// //
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
func (s *PublicTransactionPoolAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) { func (s *PublicTransactionPoolAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
// Look up the wallet containing the requested signer return nil, errors.New("Sign data not implemented")
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
} }
// SignTransactionResult represents a RLP encoded signed transaction. // SignTransactionResult represents a RLP encoded signed transaction.
@ -1288,7 +1105,13 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen
if err := args.setDefaults(ctx, s.b); err != nil { if err := args.setDefaults(ctx, s.b); err != nil {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }
@ -1302,28 +1125,9 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen
// PendingTransactions returns the transactions that are in the transaction pool // PendingTransactions returns the transactions that are in the transaction pool
// and have a from address that is one of the accounts this node manages. // and have a from address that is one of the accounts this node manages.
func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, error) { func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, error) {
pending, err := s.b.GetPoolTransactions() return nil, errors.New("Not implemented")
if err != nil { // TODO @holiman, make this call take a list of addresses, and show the penading transactions that are from that
return nil, err // set of addresses
}
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
} }
// Resend accepts an existing transaction and a new gas price and limit. It will remove // Resend accepts an existing transaction and a new gas price and limit. It will remove
@ -1340,7 +1144,9 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
var (
signedTx *types.Transaction
)
for _, p := range pending { for _, p := range pending {
var signer types.Signer = types.HomesteadSigner{} var signer types.Signer = types.HomesteadSigner{}
if p.Protected() { if p.Protected() {
@ -1356,7 +1162,7 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr
if gasLimit != nil && *gasLimit != 0 { if gasLimit != nil && *gasLimit != 0 {
sendArgs.Gas = gasLimit sendArgs.Gas = gasLimit
} }
signedTx, err := s.sign(sendArgs.From, sendArgs.toTransaction()) signedTx, err = s.extapi.SignTransaction(ctx, sendArgs)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
@ -1489,3 +1295,67 @@ func (s *PublicNetAPI) PeerCount() hexutil.Uint {
func (s *PublicNetAPI) Version() string { func (s *PublicNetAPI) Version() string {
return fmt.Sprintf("%d", s.networkVersion) return fmt.Sprintf("%d", s.networkVersion)
} }
// ExternalSignerClient provides an API to interact with an external signer (clef)
// It proxies request to the external signer while forwarding relevant
// request headers
type ExternalSignerClient struct {
client *rpc.Client
}
func NewExternalSigner(endpoint string) (*ExternalSignerClient, error) {
client, err := rpc.DialHTTP(endpoint)
if err != nil {
return nil, err
}
return &ExternalSignerClient{
client: client,
}, nil
}
func (api *ExternalSignerClient) 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 *ExternalSignerClient) 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 *ExternalSignerClient) 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 common.Address{}, err
}
return res.Address, nil
}
func (api *ExternalSignerClient) SignCliqueBlock(a common.Address, rlpBlock hexutil.Bytes) (hexutil.Bytes, error) {
if api == nil {
return nil, errors.New("External API not initialized")
}
var sig hexutil.Bytes
if err := api.client.Call(&sig, "account_signData", "application/clique", a, rlpBlock); err != nil {
return nil, err
}
if sig[64] != 27 && sig[64] != 28 {
return nil, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)")
}
sig[64] -= 27 // Transform V from 27/28 to 0/1 for Clique use
return sig, nil
}

View file

@ -21,7 +21,6 @@ import (
"context" "context"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
@ -43,7 +42,7 @@ type Backend interface {
SuggestPrice(ctx context.Context) (*big.Int, error) SuggestPrice(ctx context.Context) (*big.Int, error)
ChainDb() ethdb.Database ChainDb() ethdb.Database
EventMux() *event.TypeMux EventMux() *event.TypeMux
AccountManager() *accounts.Manager ExternalSigner() *ExternalSignerClient
// BlockChain API // BlockChain API
SetHead(number uint64) SetHead(number uint64)
@ -103,11 +102,6 @@ func GetAPIs(apiBackend Backend) []rpc.API {
Namespace: "debug", Namespace: "debug",
Version: "1.0", Version: "1.0",
Service: NewPrivateDebugAPI(apiBackend), Service: NewPrivateDebugAPI(apiBackend),
}, {
Namespace: "eth",
Version: "1.0",
Service: NewPublicAccountAPI(apiBackend.AccountManager()),
Public: true,
}, { }, {
Namespace: "personal", Namespace: "personal",
Version: "1.0", Version: "1.0",

View file

@ -20,7 +20,6 @@ import (
"context" "context"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
@ -36,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/internal/ethapi"
) )
type LesApiBackend struct { type LesApiBackend struct {
@ -183,8 +183,8 @@ func (b *LesApiBackend) EventMux() *event.TypeMux {
return b.eth.eventMux return b.eth.eventMux
} }
func (b *LesApiBackend) AccountManager() *accounts.Manager { func (b *LesApiBackend) ExternalSigner() *ethapi.ExternalSignerClient {
return b.eth.accountManager return b.eth.externalSigner
} }
func (b *LesApiBackend) BloomStatus() (uint64, uint64) { func (b *LesApiBackend) BloomStatus() (uint64, uint64) {

View file

@ -22,7 +22,6 @@ import (
"sync" "sync"
"time" "time"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
@ -69,7 +68,7 @@ type LightEthereum struct {
eventMux *event.TypeMux eventMux *event.TypeMux
engine consensus.Engine engine consensus.Engine
accountManager *accounts.Manager externalSigner *ethapi.ExternalSignerClient
networkId uint64 networkId uint64
netRPCService *ethapi.PublicNetAPI netRPCService *ethapi.PublicNetAPI
@ -101,7 +100,6 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
eventMux: ctx.EventMux, eventMux: ctx.EventMux,
peers: peers, peers: peers,
reqDist: newRequestDistributor(peers, quitSync), reqDist: newRequestDistributor(peers, quitSync),
accountManager: ctx.AccountManager,
engine: eth.CreateConsensusEngine(ctx, chainConfig, &config.Ethash, nil, false, chainDb), engine: eth.CreateConsensusEngine(ctx, chainConfig, &config.Ethash, nil, false, chainDb),
shutdownChan: make(chan bool), shutdownChan: make(chan bool),
networkId: config.NetworkId, networkId: config.NetworkId,

View file

@ -86,6 +86,10 @@ type Config struct {
// NoUSB disables hardware wallet monitoring and connectivity. // NoUSB disables hardware wallet monitoring and connectivity.
NoUSB bool `toml:",omitempty"` 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 // 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 // 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 // pipe path on Windows), whereas if it's a resolvable path name (absolute or

View file

@ -26,7 +26,6 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/debug"
@ -34,15 +33,15 @@ import (
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/prometheus/prometheus/util/flock" "github.com/prometheus/prometheus/util/flock"
"github.com/ethereum/go-ethereum/internal/ethapi"
) )
// Node is a container on which services can be registered. // Node is a container on which services can be registered.
type Node struct { type Node struct {
eventmux *event.TypeMux // Event multiplexer used between the services of a stack eventmux *event.TypeMux // Event multiplexer used between the services of a stack
config *Config config *Config
accman *accounts.Manager extapi *ethapi.ExternalSignerClient // 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 instanceDirLock flock.Releaser // prevents concurrent use of instance directory
serverConfig p2p.Config serverConfig p2p.Config
@ -97,20 +96,12 @@ func New(conf *Config) (*Node, error) {
if strings.HasSuffix(conf.Name, ".ipc") { if strings.HasSuffix(conf.Name, ".ipc") {
return nil, errors.New(`Config.Name cannot end in ".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 { if conf.Logger == nil {
conf.Logger = log.New() conf.Logger = log.New()
} }
// Note: any interaction with Config that would create/touch files // Note: any interaction with Config that would create/touch files
// in the data directory or instance directory is delayed until Start. // in the data directory or instance directory is delayed until Start.
return &Node{ return &Node{
accman: am,
ephemeralKeystore: ephemeralKeystore,
config: conf, config: conf,
serviceFuncs: []ServiceConstructor{}, serviceFuncs: []ServiceConstructor{},
ipcEndpoint: conf.IPCEndpoint(), ipcEndpoint: conf.IPCEndpoint(),
@ -173,7 +164,6 @@ func (n *Node) Start() error {
config: n.config, config: n.config,
services: make(map[reflect.Type]Service), services: make(map[reflect.Type]Service),
EventMux: n.eventmux, EventMux: n.eventmux,
AccountManager: n.accman,
} }
for kind, s := range services { // copy needed for threaded access for kind, s := range services { // copy needed for threaded access
ctx.services[kind] = s ctx.services[kind] = s
@ -435,18 +425,9 @@ func (n *Node) Stop() error {
// unblock n.Wait // unblock n.Wait
close(n.stop) 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 { if len(failure.Services) > 0 {
return failure return failure
} }
if keystoreErr != nil {
return keystoreErr
}
return nil return nil
} }
@ -537,11 +518,6 @@ func (n *Node) InstanceDir() string {
return n.config.instanceDir() 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. // IPCEndpoint retrieves the current IPC endpoint used by the protocol stack.
func (n *Node) IPCEndpoint() string { func (n *Node) IPCEndpoint() string {
return n.ipcEndpoint return n.ipcEndpoint
@ -578,6 +554,13 @@ func (n *Node) ResolvePath(x string) string {
return n.config.ResolvePath(x) return n.config.ResolvePath(x)
} }
func (n *Node) SetExternalAPI(api *ethapi.ExternalSignerClient){
n.extapi = api
}
func (n *Node) ExternalSignerAPI() *ethapi.ExternalSignerClient {
return n.extapi
}
// apis returns the collection of RPC descriptors this node offers. // apis returns the collection of RPC descriptors this node offers.
func (n *Node) apis() []rpc.API { func (n *Node) apis() []rpc.API {
return []rpc.API{ return []rpc.API{

View file

@ -19,7 +19,6 @@ package node
import ( import (
"reflect" "reflect"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
@ -33,7 +32,6 @@ type ServiceContext struct {
config *Config config *Config
services map[reflect.Type]Service // Index of the already constructed services services map[reflect.Type]Service // Index of the already constructed services
EventMux *event.TypeMux // Event multiplexer used for decoupled notifications 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 // OpenDatabase opens an existing database with the given name (or creates one

View file

@ -30,10 +30,13 @@ import (
"github.com/ethereum/go-ethereum/accounts/usbwallet" "github.com/ethereum/go-ethereum/accounts/usbwallet"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"mime"
) )
// numberOfAccountsToDerive For hardware wallets, the number of accounts to derive // numberOfAccountsToDerive For hardware wallets, the number of accounts to derive
@ -48,7 +51,9 @@ type ExternalAPI interface {
// SignTransaction request to sign the specified transaction // SignTransaction request to sign the specified transaction
SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error)
// Sign - request to sign the given data (plus prefix) // Sign - request to sign the given data (plus prefix)
Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error)
// EcRecover - request to perform ecrecover
//EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error)
// Export - request to export an account // Export - request to export an account
Export(ctx context.Context, addr common.Address) (json.RawMessage, error) Export(ctx context.Context, addr common.Address) (json.RawMessage, error)
// Import - request to import an account // Import - request to import an account
@ -170,11 +175,12 @@ type (
NewPassword string `json:"new_password"` NewPassword string `json:"new_password"`
} }
SignDataRequest struct { SignDataRequest struct {
Address common.MixedcaseAddress `json:"address"` Address common.MixedcaseAddress `json:"address"`
Rawdata hexutil.Bytes `json:"raw_data"` Rawdata hexutil.Bytes `json:"raw_data"`
Message string `json:"message"` Message string `json:"message"`
Hash hexutil.Bytes `json:"hash"` Hash hexutil.Bytes `json:"hash"`
Meta Metadata `json:"meta"` Meta Metadata `json:"meta"`
ContentType string `json:"content_type"`
} }
SignDataResponse struct { SignDataResponse struct {
Approved bool `json:"approved"` Approved bool `json:"approved"`
@ -510,22 +516,96 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth
} }
// Sign calculates an Ethereum ECDSA signature for: // cliqueSigHash returns the hash which is used as input for the proof-of-authority
// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) // signing. It is the hash of the entire header apart from the 65 byte signature
// contained at the end of the extra data.
//
// The method requires the extra data to be at least 65 bytes -- the original implementation
// in clique.go panics if this is the case, thus it's been reimplemented here to avoid the panic
// and simply return an error instead
func cliqueSigHash(header *types.Header) (hexutil.Bytes, error) {
hash := common.Hash{}
if len(header.Extra) < 65 {
return hash.Bytes(), fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra))
}
hasher := sha3.NewKeccak256()
rlp.Encode(hasher, []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra[:len(header.Extra)-65],
header.MixDigest,
header.Nonce,
})
hasher.Sum(hash[:0])
return hash.Bytes(), nil
}
func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) {
var req *SignDataRequest
mediaType, _, err := mime.ParseMediaType(contentType)
if err != nil {
return nil, err
}
switch mediaType {
case "application/clique":
header := &types.Header{}
if err := rlp.DecodeBytes(data, header); err != nil {
return nil, err
}
sighash, err := cliqueSigHash(header)
if err != nil {
return nil, err
}
msg := fmt.Sprintf("Clique block %d [0x%x]", header.Number, header.Hash())
req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType}
case "text/plain":
// Sign calculates an Ethereum ECDSA signature for:
// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message))
// In the cases where it matter ensure that the charset is handled. The charset
// resides in the 'params' returned as the second returnvalue from mime.ParseMediaType
// charset, ok := params["charset"]
// As it is now, we accept any charset and just treat it as 'raw'.
sighash, msg := SignHash(data)
req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType}
default:
//TODO! Add a content-type for EIP712 typed data.
return nil, fmt.Errorf("content type '%s' not implemented for signing")
}
return req, nil
}
// SignData signs the hash of the provided data, but does so differently
// depending on the content-type specified.
//
// Depending on the content-type, different types of validations will occur.
// //
// Note, the produced signature conforms to the secp256k1 curve R, S and V values, // 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. // where the V value will be 27 or 28 for legacy reasons.
// func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) {
// The key used to calculate the signature is decrypted with the given password.
// var req, err = api.determineSignatureFormat(contentType, data)
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign if err != nil {
func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { return nil, err
sighash, msg := SignHash(data) }
req.Address = addr
req.Meta = MetadataFromContext(ctx)
// We make the request prior to looking up if we actually have the account, to prevent // We make the request prior to looking up if we actually have the account, to prevent
// account-enumeration via the API // account-enumeration via the API
req := &SignDataRequest{Address: addr, Rawdata: data, Message: msg, Hash: sighash, Meta: MetadataFromContext(ctx)}
res, err := api.UI.ApproveSignData(req) res, err := api.UI.ApproveSignData(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -538,8 +618,8 @@ func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, da
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Assemble sign the data with the wallet // Sign the data with the wallet
signature, err := wallet.SignHashWithPassphrase(account, res.Password, sighash) signature, err := wallet.SignHashWithPassphrase(account, res.Password, req.Hash)
if err != nil { if err != nil {
api.UI.ShowError(err.Error()) api.UI.ShowError(err.Error())
return nil, err return nil, err

View file

@ -259,7 +259,7 @@ func TestSignData(t *testing.T) {
control <- "Y" control <- "Y"
control <- "wrongpassword" control <- "wrongpassword"
h, err := api.Sign(context.Background(), a, []byte("EHLO world")) h, err := api.SignData(context.Background(), "text/plain", a, []byte("EHLO world"))
if h != nil { if h != nil {
t.Errorf("Expected nil-data, got %x", h) t.Errorf("Expected nil-data, got %x", h)
} }
@ -267,7 +267,7 @@ func TestSignData(t *testing.T) {
t.Errorf("Expected ErrLocked! %v", err) t.Errorf("Expected ErrLocked! %v", err)
} }
control <- "No way" control <- "No way"
h, err = api.Sign(context.Background(), a, []byte("EHLO world")) h, err = api.SignData(context.Background(), "text/plain", a, []byte("EHLO world"))
if h != nil { if h != nil {
t.Errorf("Expected nil-data, got %x", h) t.Errorf("Expected nil-data, got %x", h)
} }
@ -276,7 +276,7 @@ func TestSignData(t *testing.T) {
} }
control <- "Y" control <- "Y"
control <- "a_long_password" control <- "a_long_password"
h, err = api.Sign(context.Background(), a, []byte("EHLO world")) h, err = api.SignData(context.Background(), "text/plain", a, []byte("EHLO world"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -63,11 +63,11 @@ func (l *AuditLogger) SignTransaction(ctx context.Context, args SendTxArgs, meth
return res, e return res, e
} }
func (l *AuditLogger) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) {
l.log.Info("Sign", "type", "request", "metadata", MetadataFromContext(ctx).String(), l.log.Info("SignData", "type", "request", "metadata", MetadataFromContext(ctx).String(),
"addr", addr.String(), "data", common.Bytes2Hex(data)) "addr", addr.String(), "data", common.Bytes2Hex(data), "content-type", contentType)
b, e := l.api.Sign(ctx, addr, data) b, e := l.api.SignData(ctx, contentType, addr, data)
l.log.Info("Sign", "type", "response", "data", common.Bytes2Hex(b), "error", e) l.log.Info("SignData", "type", "response", "data", common.Bytes2Hex(b), "error", e)
return b, e return b, e
} }