mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
Merge b1da7dfece into 4e474c74dc
This commit is contained in:
commit
966c635854
23 changed files with 330 additions and 1189 deletions
|
|
@ -1,5 +1,12 @@
|
|||
### 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
|
||||
|
||||
* The external `account_Ecrecover`-method was removed.
|
||||
|
|
|
|||
|
|
@ -39,14 +39,17 @@ import (
|
|||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/console"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/signer/core"
|
||||
"github.com/ethereum/go-ethereum/signer/rules"
|
||||
"github.com/ethereum/go-ethereum/signer/storage"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// ExternalAPIVersion -- see extapi_changelog.md
|
||||
|
|
@ -631,10 +634,38 @@ func testExternalUI(api *core.SignerAPI) {
|
|||
}
|
||||
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)
|
||||
checkErr("SignTransaction", err)
|
||||
_, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304"))
|
||||
checkErr("Sign", err)
|
||||
//_, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304"))
|
||||
//checkErr("Sign", err)
|
||||
_, err = api.List(ctx)
|
||||
checkErr("List", err)
|
||||
_, err = api.New(ctx)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -152,9 +152,7 @@ func enableWhisper(ctx *cli.Context) bool {
|
|||
|
||||
func makeFullNode(ctx *cli.Context) *node.Node {
|
||||
stack, cfg := makeConfigNode(ctx)
|
||||
|
||||
utils.RegisterEthService(stack, &cfg.Eth)
|
||||
|
||||
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
|
||||
utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,21 +25,18 @@ import (
|
|||
godebug "runtime/debug"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/elastic/gosigar"
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/console"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -54,13 +51,10 @@ var (
|
|||
// flags that configure the node
|
||||
nodeFlags = []cli.Flag{
|
||||
utils.IdentityFlag,
|
||||
utils.UnlockedAccountFlag,
|
||||
utils.PasswordFileFlag,
|
||||
utils.BootnodesFlag,
|
||||
utils.BootnodesV4Flag,
|
||||
utils.BootnodesV5Flag,
|
||||
utils.DataDirFlag,
|
||||
utils.KeyStoreDirFlag,
|
||||
utils.NoUSBFlag,
|
||||
utils.DashboardEnabledFlag,
|
||||
utils.DashboardAddrFlag,
|
||||
|
|
@ -73,6 +67,7 @@ var (
|
|||
utils.EthashDatasetsInMemoryFlag,
|
||||
utils.EthashDatasetsOnDiskFlag,
|
||||
utils.TxPoolLocalsFlag,
|
||||
utils.ExternalSignerFlag,
|
||||
utils.TxPoolNoLocalsFlag,
|
||||
utils.TxPoolJournalFlag,
|
||||
utils.TxPoolRejournalFlag,
|
||||
|
|
@ -183,9 +178,6 @@ func init() {
|
|||
dumpCommand,
|
||||
// See monitorcmd.go:
|
||||
monitorCommand,
|
||||
// See accountcmd.go:
|
||||
accountCommand,
|
||||
walletCommand,
|
||||
// See consolecmd.go:
|
||||
consoleCommand,
|
||||
attachCommand,
|
||||
|
|
@ -276,60 +268,19 @@ func geth(ctx *cli.Context) error {
|
|||
func startNode(ctx *cli.Context, stack *node.Node) {
|
||||
debug.Memsize.Add("node", stack)
|
||||
|
||||
// Start account management
|
||||
if extEndpoint := ctx.GlobalString(utils.ExternalSignerFlag.Name); extEndpoint != ""{
|
||||
extApi, err := ethapi.NewExternalSigner(extEndpoint)
|
||||
if err != nil{
|
||||
utils.Fatalf("Could not connect to external signer at %v: %v" , extEndpoint, err)
|
||||
}
|
||||
log.Info("External signer connected", "url", extEndpoint)
|
||||
stack.SetExternalAPI(extApi)
|
||||
}
|
||||
|
||||
// Start up the node itself
|
||||
utils.StartNode(stack)
|
||||
|
||||
// Unlock any account specifically requested
|
||||
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
||||
|
||||
passwords := utils.MakePasswordList(ctx)
|
||||
unlocks := strings.Split(ctx.GlobalString(utils.UnlockedAccountFlag.Name), ",")
|
||||
for i, account := range unlocks {
|
||||
if trimmed := strings.TrimSpace(account); trimmed != "" {
|
||||
unlockAccount(ctx, ks, trimmed, i, passwords)
|
||||
}
|
||||
}
|
||||
// Register wallet event handlers to open and auto-derive wallets
|
||||
events := make(chan accounts.WalletEvent, 16)
|
||||
stack.AccountManager().Subscribe(events)
|
||||
|
||||
go func() {
|
||||
// Create a chain state reader for self-derivation
|
||||
rpcClient, err := stack.Attach()
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to attach to self: %v", err)
|
||||
}
|
||||
stateReader := ethclient.NewClient(rpcClient)
|
||||
|
||||
// Open any wallets already attached
|
||||
for _, wallet := range stack.AccountManager().Wallets() {
|
||||
if err := wallet.Open(""); err != nil {
|
||||
log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err)
|
||||
}
|
||||
}
|
||||
// Listen for wallet event till termination
|
||||
for event := range events {
|
||||
switch event.Kind {
|
||||
case accounts.WalletArrived:
|
||||
if err := event.Wallet.Open(""); err != nil {
|
||||
log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err)
|
||||
}
|
||||
case accounts.WalletOpened:
|
||||
status, _ := event.Wallet.Status()
|
||||
log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status)
|
||||
|
||||
derivationPath := accounts.DefaultBaseDerivationPath
|
||||
if event.Wallet.URL().Scheme == "ledger" {
|
||||
derivationPath = accounts.DefaultLedgerBaseDerivationPath
|
||||
}
|
||||
event.Wallet.SelfDerive(derivationPath, stateReader)
|
||||
|
||||
case accounts.WalletDropped:
|
||||
log.Info("Old wallet dropped", "url", event.Wallet.URL())
|
||||
event.Wallet.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
// Start auxiliary services if enabled
|
||||
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) {
|
||||
// Mining only makes sense if a full Ethereum node is running
|
||||
|
|
|
|||
|
|
@ -69,7 +69,6 @@ var AppHelpFlagGroups = []flagGroup{
|
|||
Flags: []cli.Flag{
|
||||
configFileFlag,
|
||||
utils.DataDirFlag,
|
||||
utils.KeyStoreDirFlag,
|
||||
utils.NoUSBFlag,
|
||||
utils.NetworkIdFlag,
|
||||
utils.TestnetFlag,
|
||||
|
|
@ -136,13 +135,6 @@ var AppHelpFlagGroups = []flagGroup{
|
|||
utils.TrieCacheGenFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ACCOUNT",
|
||||
Flags: []cli.Flag{
|
||||
utils.UnlockedAccountFlag,
|
||||
utils.PasswordFileFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "API AND CONSOLE",
|
||||
Flags: []cli.Flag{
|
||||
|
|
|
|||
|
|
@ -565,7 +565,6 @@ pv(1) tool to get a progress bar:
|
|||
utils.NATFlag,
|
||||
utils.IPCDisabledFlag,
|
||||
utils.IPCPathFlag,
|
||||
utils.PasswordFileFlag,
|
||||
// bzzd-specific flags
|
||||
CorsStringFlag,
|
||||
EnsAPIFlag,
|
||||
|
|
@ -722,11 +721,8 @@ func getAccount(bzzaccount string, ctx *cli.Context, stack *node.Node) *ecdsa.Pr
|
|||
log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey))
|
||||
return key
|
||||
}
|
||||
// Otherwise try getting it from the keystore.
|
||||
am := stack.AccountManager()
|
||||
ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
||||
|
||||
return decryptStoreAccount(ks, bzzaccount, utils.MakePasswordList(ctx))
|
||||
utils.Fatalf(SWARM_ERR_NO_BZZACCOUNT)
|
||||
return nil
|
||||
}
|
||||
|
||||
// getPrivKey returns the private key of the specified bzzaccount
|
||||
|
|
|
|||
|
|
@ -20,16 +20,13 @@ package utils
|
|||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/fdlimit"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
|
|
@ -379,17 +376,15 @@ var (
|
|||
Usage: "Disable remote sealing verification",
|
||||
}
|
||||
// Account settings
|
||||
UnlockedAccountFlag = cli.StringFlag{
|
||||
Name: "unlock",
|
||||
Usage: "Comma separated list of accounts to unlock",
|
||||
Value: "",
|
||||
}
|
||||
PasswordFileFlag = cli.StringFlag{
|
||||
Name: "password",
|
||||
Usage: "Password file to use for non-interactive password input",
|
||||
}
|
||||
ExternalSignerFlag = cli.StringFlag{
|
||||
Name: "signer",
|
||||
Usage: "External signer (url or path to ipc file)",
|
||||
Value: "",
|
||||
}
|
||||
|
||||
VMEnableDebugFlag = cli.BoolFlag{
|
||||
Name: "vmdebug",
|
||||
Usage: "Record information useful for VM and contract debugging",
|
||||
|
|
@ -836,32 +831,21 @@ func makeDatabaseHandles() int {
|
|||
|
||||
// MakeAddress converts an account specified directly as a hex encoded string or
|
||||
// a key index in the key store to an internal account representation.
|
||||
func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error) {
|
||||
func MakeAddress(account string) (accounts.Account, error) {
|
||||
// If the specified account is a valid address, return it
|
||||
if common.IsHexAddress(account) {
|
||||
ma, err := common.NewMixedcaseAddressFromString(account)
|
||||
if err != nil {
|
||||
return accounts.Account{}, fmt.Errorf("Not valid hex address (%v): %v", account, err)
|
||||
}
|
||||
if !ma.ValidChecksum() {
|
||||
return accounts.Account{}, fmt.Errorf("Invalid checksum on address %v", account)
|
||||
}
|
||||
return accounts.Account{Address: common.HexToAddress(account)}, nil
|
||||
}
|
||||
// Otherwise try to interpret the account as a keystore index
|
||||
index, err := strconv.Atoi(account)
|
||||
if err != nil || index < 0 {
|
||||
return accounts.Account{}, fmt.Errorf("invalid account address or index %q", account)
|
||||
}
|
||||
log.Warn("-------------------------------------------------------------------")
|
||||
log.Warn("Referring to accounts by order in the keystore folder is dangerous!")
|
||||
log.Warn("This functionality is deprecated and will be removed in the future!")
|
||||
log.Warn("Please use explicit addresses! (can search via `geth account list`)")
|
||||
log.Warn("-------------------------------------------------------------------")
|
||||
|
||||
accs := ks.Accounts()
|
||||
if len(accs) <= index {
|
||||
return accounts.Account{}, fmt.Errorf("index %d higher than number of accounts %d", index, len(accs))
|
||||
}
|
||||
return accs[index], nil
|
||||
}
|
||||
|
||||
// setEtherbase retrieves the etherbase either from the directly specified
|
||||
// command line flags or from the keystore if CLI indexed.
|
||||
func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) {
|
||||
func setEtherbase(ctx *cli.Context, cfg *eth.Config) {
|
||||
// Extract the current etherbase, new flag overriding legacy one
|
||||
var etherbase string
|
||||
if ctx.GlobalIsSet(MinerLegacyEtherbaseFlag.Name) {
|
||||
|
|
@ -872,7 +856,7 @@ func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) {
|
|||
}
|
||||
// Convert the etherbase into an address and configure it
|
||||
if etherbase != "" {
|
||||
account, err := MakeAddress(ks, etherbase)
|
||||
account, err := MakeAddress(etherbase)
|
||||
if err != nil {
|
||||
Fatalf("Invalid miner etherbase: %v", err)
|
||||
}
|
||||
|
|
@ -880,24 +864,6 @@ func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) {
|
|||
}
|
||||
}
|
||||
|
||||
// MakePasswordList reads password lines from the file specified by the global --password flag.
|
||||
func MakePasswordList(ctx *cli.Context) []string {
|
||||
path := ctx.GlobalString(PasswordFileFlag.Name)
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
text, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
Fatalf("Failed to read password file: %v", err)
|
||||
}
|
||||
lines := strings.Split(string(text), "\n")
|
||||
// Sanitise DOS line endings.
|
||||
for i := range lines {
|
||||
lines[i] = strings.TrimRight(lines[i], "\r")
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
|
||||
setNodeKey(ctx, cfg)
|
||||
setNAT(ctx, cfg)
|
||||
|
|
@ -1128,8 +1094,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
|||
checkExclusive(ctx, DeveloperFlag, TestnetFlag, RinkebyFlag)
|
||||
checkExclusive(ctx, LightServFlag, SyncModeFlag, "light")
|
||||
|
||||
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
||||
setEtherbase(ctx, ks, cfg)
|
||||
setEtherbase(ctx, cfg)
|
||||
setGPO(ctx, &cfg.GPO)
|
||||
setTxPool(ctx, &cfg.TxPool)
|
||||
setEthash(ctx, cfg)
|
||||
|
|
@ -1223,24 +1188,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
|||
cfg.NetworkId = 1337
|
||||
}
|
||||
// Create new developer account or reuse existing one
|
||||
var (
|
||||
developer accounts.Account
|
||||
err error
|
||||
)
|
||||
if accs := ks.Accounts(); len(accs) > 0 {
|
||||
developer = ks.Accounts()[0]
|
||||
} else {
|
||||
developer, err = ks.NewAccount("")
|
||||
if err != nil {
|
||||
Fatalf("Failed to create developer account: %v", err)
|
||||
}
|
||||
}
|
||||
if err := ks.Unlock(developer, ""); err != nil {
|
||||
Fatalf("Failed to unlock developer account: %v", err)
|
||||
}
|
||||
log.Info("Using developer account", "address", developer.Address)
|
||||
|
||||
cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address)
|
||||
// TODO @holiman
|
||||
Fatalf("Failed to create developer account: not implemented")
|
||||
//cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address)
|
||||
if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(MinerLegacyGasPriceFlag.Name) {
|
||||
cfg.MinerGasPrice = big.NewInt(1)
|
||||
}
|
||||
|
|
@ -1267,7 +1217,7 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) {
|
|||
})
|
||||
} else {
|
||||
err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
fullNode, err := eth.New(ctx, cfg)
|
||||
fullNode, err := eth.New(ctx, cfg, stack.ExternalSignerAPI())
|
||||
if fullNode != nil && cfg.LightServ > 0 {
|
||||
ls, _ := les.NewLesServer(fullNode, cfg)
|
||||
fullNode.AddLesServer(ls)
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -136,9 +136,9 @@ var (
|
|||
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.
|
||||
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
|
||||
// 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))
|
||||
}
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,37 +159,6 @@ func (c *Console) init(preload []string) error {
|
|||
// Initialize the global name register (disabled for now)
|
||||
//c.jsre.Run(`var GlobalRegistrar = eth.contract(` + registrar.GlobalRegistrarAbi + `); registrar = GlobalRegistrar.at("` + registrar.GlobalRegistrarAddr + `");`)
|
||||
|
||||
// If the console is in interactive mode, instrument password related methods to query the user
|
||||
if c.prompter != nil {
|
||||
// Retrieve the account management object to instrument
|
||||
personal, err := c.jsre.Get("personal")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Override the openWallet, unlockAccount, newAccount and sign methods since
|
||||
// these require user interaction. Assign these method in the Console the
|
||||
// original web3 callbacks. These will be called by the jeth.* methods after
|
||||
// they got the password from the user and send the original web3 request to
|
||||
// the backend.
|
||||
if obj := personal.Object(); obj != nil { // make sure the personal api is enabled over the interface
|
||||
if _, err = c.jsre.Run(`jeth.openWallet = personal.openWallet;`); err != nil {
|
||||
return fmt.Errorf("personal.openWallet: %v", err)
|
||||
}
|
||||
if _, err = c.jsre.Run(`jeth.unlockAccount = personal.unlockAccount;`); err != nil {
|
||||
return fmt.Errorf("personal.unlockAccount: %v", err)
|
||||
}
|
||||
if _, err = c.jsre.Run(`jeth.newAccount = personal.newAccount;`); err != nil {
|
||||
return fmt.Errorf("personal.newAccount: %v", err)
|
||||
}
|
||||
if _, err = c.jsre.Run(`jeth.sign = personal.sign;`); err != nil {
|
||||
return fmt.Errorf("personal.sign: %v", err)
|
||||
}
|
||||
obj.Set("openWallet", bridge.OpenWallet)
|
||||
obj.Set("unlockAccount", bridge.UnlockAccount)
|
||||
obj.Set("newAccount", bridge.NewAccount)
|
||||
obj.Set("sign", bridge.Sign)
|
||||
}
|
||||
}
|
||||
// The admin.sleep and admin.sleepBlocks are offered by the console and not by the RPC layer.
|
||||
admin, err := c.jsre.Get("admin")
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import (
|
|||
"context"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
|
|
@ -34,6 +33,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
)
|
||||
|
||||
// EthAPIBackend implements ethapi.Backend for full nodes
|
||||
|
|
@ -209,10 +209,6 @@ func (b *EthAPIBackend) EventMux() *event.TypeMux {
|
|||
return b.eth.EventMux()
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) AccountManager() *accounts.Manager {
|
||||
return b.eth.AccountManager()
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) BloomStatus() (uint64, uint64) {
|
||||
sections, _, _ := b.eth.bloomIndexer.Sections()
|
||||
return params.BloomBitsBlocks, sections
|
||||
|
|
@ -223,3 +219,8 @@ func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma
|
|||
go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests)
|
||||
}
|
||||
}
|
||||
func (b *EthAPIBackend) ExternalSigner() *ethapi.ExternalSignerClient {
|
||||
return b.eth.externalSigner
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -20,12 +20,12 @@ package eth
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"math/big"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
|
|
@ -77,7 +77,7 @@ type Ethereum struct {
|
|||
|
||||
eventMux *event.TypeMux
|
||||
engine consensus.Engine
|
||||
accountManager *accounts.Manager
|
||||
externalSigner *ethapi.ExternalSignerClient
|
||||
|
||||
bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
|
||||
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
|
||||
// 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
|
||||
if config.SyncMode == downloader.LightSync {
|
||||
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{
|
||||
config: config,
|
||||
externalSigner: api,
|
||||
chainDb: chainDb,
|
||||
chainConfig: chainConfig,
|
||||
eventMux: ctx.EventMux,
|
||||
accountManager: ctx.AccountManager,
|
||||
engine: CreateConsensusEngine(ctx, chainConfig, &config.Ethash, config.MinerNotify, config.MinerNoverify, chainDb),
|
||||
shutdownChan: make(chan bool),
|
||||
networkID: config.NetworkId,
|
||||
|
|
@ -319,18 +319,6 @@ func (s *Ethereum) Etherbase() (eb common.Address, err error) {
|
|||
if etherbase != (common.Address{}) {
|
||||
return etherbase, nil
|
||||
}
|
||||
if wallets := s.AccountManager().Wallets(); len(wallets) > 0 {
|
||||
if accounts := wallets[0].Accounts(); len(accounts) > 0 {
|
||||
etherbase := accounts[0].Address
|
||||
|
||||
s.lock.Lock()
|
||||
s.etherbase = etherbase
|
||||
s.lock.Unlock()
|
||||
|
||||
log.Info("Etherbase automatically configured", "address", etherbase)
|
||||
return etherbase, nil
|
||||
}
|
||||
}
|
||||
return common.Address{}, fmt.Errorf("etherbase must be explicitly specified")
|
||||
}
|
||||
|
||||
|
|
@ -427,12 +415,18 @@ func (s *Ethereum) StartMining(threads int) error {
|
|||
return fmt.Errorf("etherbase missing: %v", err)
|
||||
}
|
||||
if clique, ok := s.engine.(*clique.Clique); ok {
|
||||
wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
|
||||
if wallet == nil || err != nil {
|
||||
log.Error("Etherbase account unavailable locally", "err", err)
|
||||
return fmt.Errorf("signer missing: %v", err)
|
||||
if s.ExternalSigner() != nil {
|
||||
signerFn := func(a accounts.Account, header *types.Header) ([]byte, error) {
|
||||
rlpHeader, err := rlp.EncodeToBytes(header)
|
||||
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
|
||||
// introduced to speed sync times.
|
||||
|
|
@ -460,7 +454,6 @@ func (s *Ethereum) StopMining() {
|
|||
func (s *Ethereum) IsMining() bool { return s.miner.Mining() }
|
||||
func (s *Ethereum) Miner() *miner.Miner { return s.miner }
|
||||
|
||||
func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager }
|
||||
func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain }
|
||||
func (s *Ethereum) TxPool() *core.TxPool { return s.txPool }
|
||||
func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux }
|
||||
|
|
@ -470,6 +463,7 @@ func (s *Ethereum) IsListening() bool { return true } // Always
|
|||
func (s *Ethereum) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) }
|
||||
func (s *Ethereum) NetVersion() uint64 { return s.networkID }
|
||||
func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader }
|
||||
func (s *Ethereum) ExternalSigner() *ethapi.ExternalSignerClient { return s.externalSigner }
|
||||
|
||||
// Protocols implements node.Service, returning all the currently configured
|
||||
// network protocols to start.
|
||||
|
|
|
|||
|
|
@ -177,55 +177,31 @@ func (s *PublicTxPoolAPI) Inspect() map[string]map[string]map[string]string {
|
|||
return content
|
||||
}
|
||||
|
||||
// PublicAccountAPI provides an API to access accounts managed by this node.
|
||||
// It offers only methods that can retrieve accounts.
|
||||
type PublicAccountAPI struct {
|
||||
am *accounts.Manager
|
||||
}
|
||||
|
||||
// NewPublicAccountAPI creates a new PublicAccountAPI.
|
||||
func NewPublicAccountAPI(am *accounts.Manager) *PublicAccountAPI {
|
||||
return &PublicAccountAPI{am: am}
|
||||
}
|
||||
|
||||
// Accounts returns the collection of accounts this node manages
|
||||
func (s *PublicAccountAPI) Accounts() []common.Address {
|
||||
addresses := make([]common.Address, 0) // return [] instead of nil if empty
|
||||
for _, wallet := range s.am.Wallets() {
|
||||
for _, account := range wallet.Accounts() {
|
||||
addresses = append(addresses, account.Address)
|
||||
}
|
||||
}
|
||||
return addresses
|
||||
}
|
||||
|
||||
// PrivateAccountAPI provides an API to access accounts managed by this node.
|
||||
// It offers methods to create, (un)lock en list accounts. Some methods accept
|
||||
// passwords and are therefore considered private by default.
|
||||
type PrivateAccountAPI struct {
|
||||
am *accounts.Manager
|
||||
nonceLock *AddrLocker
|
||||
b Backend
|
||||
extapi *ExternalSignerClient // external signer, nil if not used
|
||||
}
|
||||
|
||||
// NewPrivateAccountAPI create a new PrivateAccountAPI.
|
||||
func NewPrivateAccountAPI(b Backend, nonceLock *AddrLocker) *PrivateAccountAPI {
|
||||
return &PrivateAccountAPI{
|
||||
am: b.AccountManager(),
|
||||
p := &PrivateAccountAPI{
|
||||
nonceLock: nonceLock,
|
||||
b: b,
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// ListAccounts will return a list of addresses for accounts this node manages.
|
||||
func (s *PrivateAccountAPI) ListAccounts() []common.Address {
|
||||
addresses := make([]common.Address, 0) // return [] instead of nil if empty
|
||||
for _, wallet := range s.am.Wallets() {
|
||||
for _, account := range wallet.Accounts() {
|
||||
addresses = append(addresses, account.Address)
|
||||
func (s *PrivateAccountAPI) ListAccounts() ([]common.Address, error) {
|
||||
if extapi := s.b.ExternalSigner(); extapi != nil {
|
||||
return extapi.ListAccounts()
|
||||
}
|
||||
}
|
||||
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
|
||||
|
|
@ -237,65 +213,9 @@ type rawWallet struct {
|
|||
Accounts []accounts.Account `json:"accounts,omitempty"`
|
||||
}
|
||||
|
||||
// ListWallets will return a list of wallets this node manages.
|
||||
func (s *PrivateAccountAPI) ListWallets() []rawWallet {
|
||||
wallets := make([]rawWallet, 0) // return [] instead of nil if empty
|
||||
for _, wallet := range s.am.Wallets() {
|
||||
status, failure := wallet.Status()
|
||||
|
||||
raw := rawWallet{
|
||||
URL: wallet.URL().String(),
|
||||
Status: status,
|
||||
Accounts: wallet.Accounts(),
|
||||
}
|
||||
if failure != nil {
|
||||
raw.Failure = failure.Error()
|
||||
}
|
||||
wallets = append(wallets, raw)
|
||||
}
|
||||
return wallets
|
||||
}
|
||||
|
||||
// OpenWallet initiates a hardware wallet opening procedure, establishing a USB
|
||||
// connection and attempting to authenticate via the provided passphrase. Note,
|
||||
// the method may return an extra challenge requiring a second open (e.g. the
|
||||
// Trezor PIN matrix challenge).
|
||||
func (s *PrivateAccountAPI) OpenWallet(url string, passphrase *string) error {
|
||||
wallet, err := s.am.Wallet(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pass := ""
|
||||
if passphrase != nil {
|
||||
pass = *passphrase
|
||||
}
|
||||
return wallet.Open(pass)
|
||||
}
|
||||
|
||||
// DeriveAccount requests a HD wallet to derive a new account, optionally pinning
|
||||
// it for later reuse.
|
||||
func (s *PrivateAccountAPI) DeriveAccount(url string, path string, pin *bool) (accounts.Account, error) {
|
||||
wallet, err := s.am.Wallet(url)
|
||||
if err != nil {
|
||||
return accounts.Account{}, err
|
||||
}
|
||||
derivPath, err := accounts.ParseDerivationPath(path)
|
||||
if err != nil {
|
||||
return accounts.Account{}, err
|
||||
}
|
||||
if pin == nil {
|
||||
pin = new(bool)
|
||||
}
|
||||
return wallet.Derive(derivPath, *pin)
|
||||
}
|
||||
|
||||
// NewAccount will create a new account and returns the address for the new account.
|
||||
func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error) {
|
||||
acc, err := fetchKeystore(s.am).NewAccount(password)
|
||||
if err == nil {
|
||||
return acc.Address, nil
|
||||
}
|
||||
return common.Address{}, err
|
||||
return s.extapi.NewAccount()
|
||||
}
|
||||
|
||||
// fetchKeystore retrives the encrypted keystore from the account manager.
|
||||
|
|
@ -306,58 +226,30 @@ func fetchKeystore(am *accounts.Manager) *keystore.KeyStore {
|
|||
// ImportRawKey stores the given hex encoded ECDSA key into the key directory,
|
||||
// encrypting it with the passphrase.
|
||||
func (s *PrivateAccountAPI) ImportRawKey(privkey string, password string) (common.Address, error) {
|
||||
key, err := crypto.HexToECDSA(privkey)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
acc, err := fetchKeystore(s.am).ImportECDSA(key, password)
|
||||
return acc.Address, err
|
||||
return common.Address{}, errors.New("Importing of raw keys is disabled when external signer is used.")
|
||||
}
|
||||
|
||||
// UnlockAccount will unlock the account associated with the given address with
|
||||
// the given password for duration seconds. If duration is nil it will use a
|
||||
// default of 300 seconds. It returns an indication if the account was unlocked.
|
||||
func (s *PrivateAccountAPI) UnlockAccount(addr common.Address, password string, duration *uint64) (bool, error) {
|
||||
const max = uint64(time.Duration(math.MaxInt64) / time.Second)
|
||||
var d time.Duration
|
||||
if duration == nil {
|
||||
d = 300 * time.Second
|
||||
} else if *duration > max {
|
||||
return false, errors.New("unlock duration too large")
|
||||
} else {
|
||||
d = time.Duration(*duration) * time.Second
|
||||
}
|
||||
err := fetchKeystore(s.am).TimedUnlock(accounts.Account{Address: addr}, password, d)
|
||||
return err == nil, err
|
||||
return false, errors.New("Unlock is disabled when external signer is used.")
|
||||
}
|
||||
|
||||
// LockAccount will lock the account associated with the given address when it's unlocked.
|
||||
func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool {
|
||||
return fetchKeystore(s.am).Lock(addr) == nil
|
||||
return false
|
||||
}
|
||||
|
||||
// signTransactions sets defaults and signs the given transaction
|
||||
// NOTE: the caller needs to ensure that the nonceLock is held, if applicable,
|
||||
// and release it after the transaction has been submitted to the tx pool
|
||||
func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args SendTxArgs, passwd string) (*types.Transaction, error) {
|
||||
// Look up the wallet containing the requested signer
|
||||
account := accounts.Account{Address: args.From}
|
||||
wallet, err := s.am.Find(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Set some sanity defaults and terminate on failure
|
||||
if err := args.setDefaults(ctx, s.b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Assemble the transaction and sign with the wallet
|
||||
tx := args.toTransaction()
|
||||
|
||||
var chainID *big.Int
|
||||
if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) {
|
||||
chainID = config.ChainID
|
||||
}
|
||||
return wallet.SignTxWithPassphrase(account, passwd, tx, chainID)
|
||||
return s.extapi.SignTransaction(ctx, args)
|
||||
}
|
||||
|
||||
// SendTransaction will create a transaction from the given arguments and
|
||||
|
|
@ -416,32 +308,6 @@ func signHash(data []byte) []byte {
|
|||
return crypto.Keccak256([]byte(msg))
|
||||
}
|
||||
|
||||
// Sign calculates an Ethereum ECDSA signature for:
|
||||
// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message))
|
||||
//
|
||||
// Note, the produced signature conforms to the secp256k1 curve R, S and V values,
|
||||
// where the V value will be 27 or 28 for legacy reasons.
|
||||
//
|
||||
// The key used to calculate the signature is decrypted with the given password.
|
||||
//
|
||||
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
|
||||
func (s *PrivateAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr common.Address, passwd string) (hexutil.Bytes, error) {
|
||||
// Look up the wallet containing the requested signer
|
||||
account := accounts.Account{Address: addr}
|
||||
|
||||
wallet, err := s.b.AccountManager().Find(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Assemble sign the data with the wallet
|
||||
signature, err := wallet.SignHashWithPassphrase(account, passwd, signHash(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
// EcRecover returns the address for the account that was used to create the signature.
|
||||
// Note, this function is compatible with eth_sign and personal_sign. As such it recovers
|
||||
// the address of:
|
||||
|
|
@ -471,7 +337,7 @@ func (s *PrivateAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.Byt
|
|||
// SignAndSendTransaction was renamed to SendTransaction. This method is deprecated
|
||||
// and will be removed in the future. It primary goal is to give clients time to update.
|
||||
func (s *PrivateAccountAPI) SignAndSendTransaction(ctx context.Context, args SendTxArgs, passwd string) (common.Hash, error) {
|
||||
return s.SendTransaction(ctx, args, passwd)
|
||||
return common.Hash{}, errors.New("SignAndSend is deprecated, please use sendTransaction instead")
|
||||
}
|
||||
|
||||
// PublicBlockChainAPI provides an API to access the Ethereum blockchain.
|
||||
|
|
@ -618,15 +484,8 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr
|
|||
if state == nil || err != nil {
|
||||
return nil, 0, false, err
|
||||
}
|
||||
// Set sender address or use a default if none specified
|
||||
// Set sender address
|
||||
addr := args.From
|
||||
if addr == (common.Address{}) {
|
||||
if wallets := s.b.AccountManager().Wallets(); len(wallets) > 0 {
|
||||
if accounts := wallets[0].Accounts(); len(accounts) > 0 {
|
||||
addr = accounts[0].Address
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set default gas & gas price if none were set
|
||||
gas, gasPrice := uint64(args.Gas), args.GasPrice.ToInt()
|
||||
if gas == 0 {
|
||||
|
|
@ -943,11 +802,12 @@ func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransa
|
|||
type PublicTransactionPoolAPI struct {
|
||||
b Backend
|
||||
nonceLock *AddrLocker
|
||||
extapi *ExternalSignerClient // external signer, nil if not used
|
||||
}
|
||||
|
||||
// NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool.
|
||||
func NewPublicTransactionPoolAPI(b Backend, nonceLock *AddrLocker) *PublicTransactionPoolAPI {
|
||||
return &PublicTransactionPoolAPI{b, nonceLock}
|
||||
return &PublicTransactionPoolAPI{b, nonceLock, b.ExternalSigner()}
|
||||
}
|
||||
|
||||
// GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number.
|
||||
|
|
@ -1090,23 +950,6 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha
|
|||
return fields, nil
|
||||
}
|
||||
|
||||
// sign is a helper function that signs a transaction with the private key of the given address.
|
||||
func (s *PublicTransactionPoolAPI) sign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
|
||||
// Look up the wallet containing the requested signer
|
||||
account := accounts.Account{Address: addr}
|
||||
|
||||
wallet, err := s.b.AccountManager().Find(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Request the wallet to sign the transaction
|
||||
var chainID *big.Int
|
||||
if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) {
|
||||
chainID = config.ChainID
|
||||
}
|
||||
return wallet.SignTx(account, tx, chainID)
|
||||
}
|
||||
|
||||
// SendTxArgs represents the arguments to sumbit a new transaction into the transaction pool.
|
||||
type SendTxArgs struct {
|
||||
From common.Address `json:"from"`
|
||||
|
|
@ -1197,34 +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
|
||||
// transaction pool.
|
||||
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {
|
||||
|
||||
// Look up the wallet containing the requested signer
|
||||
account := accounts.Account{Address: args.From}
|
||||
|
||||
wallet, err := s.b.AccountManager().Find(account)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
if s.extapi == nil {
|
||||
return common.Hash{}, errors.New("No external signer configured")
|
||||
}
|
||||
|
||||
if args.Nonce == nil {
|
||||
// Hold the addresse's mutex around signing to prevent concurrent assignment of
|
||||
// the same nonce to multiple accounts.
|
||||
s.nonceLock.LockAddr(args.From)
|
||||
defer s.nonceLock.UnlockAddr(args.From)
|
||||
}
|
||||
|
||||
// Set some sanity defaults and terminate on failure
|
||||
if err := args.setDefaults(ctx, s.b); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
// Assemble the transaction and sign with the wallet
|
||||
tx := args.toTransaction()
|
||||
|
||||
var chainID *big.Int
|
||||
if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) {
|
||||
chainID = config.ChainID
|
||||
}
|
||||
signed, err := wallet.SignTx(account, tx, chainID)
|
||||
signed, err := s.extapi.SignTransaction(ctx, args)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
|
@ -1251,19 +1080,7 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod
|
|||
//
|
||||
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
|
||||
func (s *PublicTransactionPoolAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
|
||||
// Look up the wallet containing the requested signer
|
||||
account := accounts.Account{Address: addr}
|
||||
|
||||
wallet, err := s.b.AccountManager().Find(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Sign the requested hash with the wallet
|
||||
signature, err := wallet.SignHash(account, signHash(data))
|
||||
if err == nil {
|
||||
signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
||||
}
|
||||
return signature, err
|
||||
return nil, errors.New("Sign data not implemented")
|
||||
}
|
||||
|
||||
// SignTransactionResult represents a RLP encoded signed transaction.
|
||||
|
|
@ -1288,7 +1105,13 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen
|
|||
if err := args.setDefaults(ctx, s.b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tx, err := s.sign(args.From, args.toTransaction())
|
||||
var (
|
||||
tx *types.Transaction
|
||||
err error
|
||||
)
|
||||
|
||||
tx, err = s.extapi.SignTransaction(ctx, args)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1302,28 +1125,9 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen
|
|||
// PendingTransactions returns the transactions that are in the transaction pool
|
||||
// and have a from address that is one of the accounts this node manages.
|
||||
func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, error) {
|
||||
pending, err := s.b.GetPoolTransactions()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accounts := make(map[common.Address]struct{})
|
||||
for _, wallet := range s.b.AccountManager().Wallets() {
|
||||
for _, account := range wallet.Accounts() {
|
||||
accounts[account.Address] = struct{}{}
|
||||
}
|
||||
}
|
||||
transactions := make([]*RPCTransaction, 0, len(pending))
|
||||
for _, tx := range pending {
|
||||
var signer types.Signer = types.HomesteadSigner{}
|
||||
if tx.Protected() {
|
||||
signer = types.NewEIP155Signer(tx.ChainId())
|
||||
}
|
||||
from, _ := types.Sender(signer, tx)
|
||||
if _, exists := accounts[from]; exists {
|
||||
transactions = append(transactions, newRPCPendingTransaction(tx))
|
||||
}
|
||||
}
|
||||
return transactions, nil
|
||||
return nil, errors.New("Not implemented")
|
||||
// TODO @holiman, make this call take a list of addresses, and show the penading transactions that are from that
|
||||
// set of addresses
|
||||
}
|
||||
|
||||
// Resend accepts an existing transaction and a new gas price and limit. It will remove
|
||||
|
|
@ -1340,7 +1144,9 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr
|
|||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
var (
|
||||
signedTx *types.Transaction
|
||||
)
|
||||
for _, p := range pending {
|
||||
var signer types.Signer = types.HomesteadSigner{}
|
||||
if p.Protected() {
|
||||
|
|
@ -1356,7 +1162,7 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr
|
|||
if gasLimit != nil && *gasLimit != 0 {
|
||||
sendArgs.Gas = gasLimit
|
||||
}
|
||||
signedTx, err := s.sign(sendArgs.From, sendArgs.toTransaction())
|
||||
signedTx, err = s.extapi.SignTransaction(ctx, sendArgs)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
|
@ -1489,3 +1295,67 @@ func (s *PublicNetAPI) PeerCount() hexutil.Uint {
|
|||
func (s *PublicNetAPI) Version() string {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import (
|
|||
"context"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
|
|
@ -43,7 +42,7 @@ type Backend interface {
|
|||
SuggestPrice(ctx context.Context) (*big.Int, error)
|
||||
ChainDb() ethdb.Database
|
||||
EventMux() *event.TypeMux
|
||||
AccountManager() *accounts.Manager
|
||||
ExternalSigner() *ExternalSignerClient
|
||||
|
||||
// BlockChain API
|
||||
SetHead(number uint64)
|
||||
|
|
@ -103,11 +102,6 @@ func GetAPIs(apiBackend Backend) []rpc.API {
|
|||
Namespace: "debug",
|
||||
Version: "1.0",
|
||||
Service: NewPrivateDebugAPI(apiBackend),
|
||||
}, {
|
||||
Namespace: "eth",
|
||||
Version: "1.0",
|
||||
Service: NewPublicAccountAPI(apiBackend.AccountManager()),
|
||||
Public: true,
|
||||
}, {
|
||||
Namespace: "personal",
|
||||
Version: "1.0",
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import (
|
|||
"context"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
|
|
@ -36,6 +35,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/light"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
)
|
||||
|
||||
type LesApiBackend struct {
|
||||
|
|
@ -183,8 +183,8 @@ func (b *LesApiBackend) EventMux() *event.TypeMux {
|
|||
return b.eth.eventMux
|
||||
}
|
||||
|
||||
func (b *LesApiBackend) AccountManager() *accounts.Manager {
|
||||
return b.eth.accountManager
|
||||
func (b *LesApiBackend) ExternalSigner() *ethapi.ExternalSignerClient {
|
||||
return b.eth.externalSigner
|
||||
}
|
||||
|
||||
func (b *LesApiBackend) BloomStatus() (uint64, uint64) {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
|
|
@ -69,7 +68,7 @@ type LightEthereum struct {
|
|||
|
||||
eventMux *event.TypeMux
|
||||
engine consensus.Engine
|
||||
accountManager *accounts.Manager
|
||||
externalSigner *ethapi.ExternalSignerClient
|
||||
|
||||
networkId uint64
|
||||
netRPCService *ethapi.PublicNetAPI
|
||||
|
|
@ -101,7 +100,6 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
|
|||
eventMux: ctx.EventMux,
|
||||
peers: peers,
|
||||
reqDist: newRequestDistributor(peers, quitSync),
|
||||
accountManager: ctx.AccountManager,
|
||||
engine: eth.CreateConsensusEngine(ctx, chainConfig, &config.Ethash, nil, false, chainDb),
|
||||
shutdownChan: make(chan bool),
|
||||
networkId: config.NetworkId,
|
||||
|
|
|
|||
|
|
@ -86,6 +86,10 @@ type Config struct {
|
|||
// NoUSB disables hardware wallet monitoring and connectivity.
|
||||
NoUSB bool `toml:",omitempty"`
|
||||
|
||||
// ExternalSigner path/url to external signer. Implicitly disables native account
|
||||
// management
|
||||
ExternalSigner string `toml:",omitempty"`
|
||||
|
||||
// IPCPath is the requested location to place the IPC endpoint. If the path is
|
||||
// a simple file name, it is placed inside the data directory (or on the root
|
||||
// pipe path on Windows), whereas if it's a resolvable path name (absolute or
|
||||
|
|
|
|||
35
node/node.go
35
node/node.go
|
|
@ -26,7 +26,6 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
|
|
@ -34,15 +33,15 @@ import (
|
|||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/prometheus/prometheus/util/flock"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
)
|
||||
|
||||
// Node is a container on which services can be registered.
|
||||
type Node struct {
|
||||
eventmux *event.TypeMux // Event multiplexer used between the services of a stack
|
||||
config *Config
|
||||
accman *accounts.Manager
|
||||
extapi *ethapi.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
|
||||
|
||||
serverConfig p2p.Config
|
||||
|
|
@ -97,20 +96,12 @@ func New(conf *Config) (*Node, error) {
|
|||
if strings.HasSuffix(conf.Name, ".ipc") {
|
||||
return nil, errors.New(`Config.Name cannot end in ".ipc"`)
|
||||
}
|
||||
// Ensure that the AccountManager method works before the node has started.
|
||||
// We rely on this in cmd/geth.
|
||||
am, ephemeralKeystore, err := makeAccountManager(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if conf.Logger == nil {
|
||||
conf.Logger = log.New()
|
||||
}
|
||||
// Note: any interaction with Config that would create/touch files
|
||||
// in the data directory or instance directory is delayed until Start.
|
||||
return &Node{
|
||||
accman: am,
|
||||
ephemeralKeystore: ephemeralKeystore,
|
||||
config: conf,
|
||||
serviceFuncs: []ServiceConstructor{},
|
||||
ipcEndpoint: conf.IPCEndpoint(),
|
||||
|
|
@ -173,7 +164,6 @@ func (n *Node) Start() error {
|
|||
config: n.config,
|
||||
services: make(map[reflect.Type]Service),
|
||||
EventMux: n.eventmux,
|
||||
AccountManager: n.accman,
|
||||
}
|
||||
for kind, s := range services { // copy needed for threaded access
|
||||
ctx.services[kind] = s
|
||||
|
|
@ -435,18 +425,9 @@ func (n *Node) Stop() error {
|
|||
// unblock n.Wait
|
||||
close(n.stop)
|
||||
|
||||
// Remove the keystore if it was created ephemerally.
|
||||
var keystoreErr error
|
||||
if n.ephemeralKeystore != "" {
|
||||
keystoreErr = os.RemoveAll(n.ephemeralKeystore)
|
||||
}
|
||||
|
||||
if len(failure.Services) > 0 {
|
||||
return failure
|
||||
}
|
||||
if keystoreErr != nil {
|
||||
return keystoreErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -537,11 +518,6 @@ func (n *Node) InstanceDir() string {
|
|||
return n.config.instanceDir()
|
||||
}
|
||||
|
||||
// AccountManager retrieves the account manager used by the protocol stack.
|
||||
func (n *Node) AccountManager() *accounts.Manager {
|
||||
return n.accman
|
||||
}
|
||||
|
||||
// IPCEndpoint retrieves the current IPC endpoint used by the protocol stack.
|
||||
func (n *Node) IPCEndpoint() string {
|
||||
return n.ipcEndpoint
|
||||
|
|
@ -578,6 +554,13 @@ func (n *Node) ResolvePath(x string) string {
|
|||
return n.config.ResolvePath(x)
|
||||
}
|
||||
|
||||
func (n *Node) SetExternalAPI(api *ethapi.ExternalSignerClient){
|
||||
n.extapi = api
|
||||
}
|
||||
|
||||
func (n *Node) ExternalSignerAPI() *ethapi.ExternalSignerClient {
|
||||
return n.extapi
|
||||
}
|
||||
// apis returns the collection of RPC descriptors this node offers.
|
||||
func (n *Node) apis() []rpc.API {
|
||||
return []rpc.API{
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ package node
|
|||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
|
|
@ -33,7 +32,6 @@ type ServiceContext struct {
|
|||
config *Config
|
||||
services map[reflect.Type]Service // Index of the already constructed services
|
||||
EventMux *event.TypeMux // Event multiplexer used for decoupled notifications
|
||||
AccountManager *accounts.Manager // Account manager created by the node.
|
||||
}
|
||||
|
||||
// OpenDatabase opens an existing database with the given name (or creates one
|
||||
|
|
|
|||
|
|
@ -30,10 +30,13 @@ import (
|
|||
"github.com/ethereum/go-ethereum/accounts/usbwallet"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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/sha3"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"mime"
|
||||
)
|
||||
|
||||
// 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(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error)
|
||||
// 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(ctx context.Context, addr common.Address) (json.RawMessage, error)
|
||||
// Import - request to import an account
|
||||
|
|
@ -175,6 +180,7 @@ type (
|
|||
Message string `json:"message"`
|
||||
Hash hexutil.Bytes `json:"hash"`
|
||||
Meta Metadata `json:"meta"`
|
||||
ContentType string `json:"content_type"`
|
||||
}
|
||||
SignDataResponse struct {
|
||||
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:
|
||||
// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message))
|
||||
// cliqueSigHash 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
|
||||
// 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,
|
||||
// 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 (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) {
|
||||
sighash, msg := SignHash(data)
|
||||
func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) {
|
||||
|
||||
var req, err = api.determineSignatureFormat(contentType, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Address = addr
|
||||
req.Meta = MetadataFromContext(ctx)
|
||||
|
||||
// We make the request prior to looking up if we actually have the account, to prevent
|
||||
// account-enumeration via the API
|
||||
req := &SignDataRequest{Address: addr, Rawdata: data, Message: msg, Hash: sighash, Meta: MetadataFromContext(ctx)}
|
||||
res, err := api.UI.ApproveSignData(req)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -538,8 +618,8 @@ func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, da
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Assemble sign the data with the wallet
|
||||
signature, err := wallet.SignHashWithPassphrase(account, res.Password, sighash)
|
||||
// Sign the data with the wallet
|
||||
signature, err := wallet.SignHashWithPassphrase(account, res.Password, req.Hash)
|
||||
if err != nil {
|
||||
api.UI.ShowError(err.Error())
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -259,7 +259,7 @@ func TestSignData(t *testing.T) {
|
|||
|
||||
control <- "Y"
|
||||
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 {
|
||||
t.Errorf("Expected nil-data, got %x", h)
|
||||
}
|
||||
|
|
@ -267,7 +267,7 @@ func TestSignData(t *testing.T) {
|
|||
t.Errorf("Expected ErrLocked! %v", err)
|
||||
}
|
||||
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 {
|
||||
t.Errorf("Expected nil-data, got %x", h)
|
||||
}
|
||||
|
|
@ -276,7 +276,7 @@ func TestSignData(t *testing.T) {
|
|||
}
|
||||
control <- "Y"
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,11 +63,11 @@ func (l *AuditLogger) SignTransaction(ctx context.Context, args SendTxArgs, meth
|
|||
return res, e
|
||||
}
|
||||
|
||||
func (l *AuditLogger) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) {
|
||||
l.log.Info("Sign", "type", "request", "metadata", MetadataFromContext(ctx).String(),
|
||||
"addr", addr.String(), "data", common.Bytes2Hex(data))
|
||||
b, e := l.api.Sign(ctx, addr, data)
|
||||
l.log.Info("Sign", "type", "response", "data", common.Bytes2Hex(b), "error", e)
|
||||
func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) {
|
||||
l.log.Info("SignData", "type", "request", "metadata", MetadataFromContext(ctx).String(),
|
||||
"addr", addr.String(), "data", common.Bytes2Hex(data), "content-type", contentType)
|
||||
b, e := l.api.SignData(ctx, contentType, addr, data)
|
||||
l.log.Info("SignData", "type", "response", "data", common.Bytes2Hex(b), "error", e)
|
||||
return b, e
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue