accounts, eth, clique, signer: support for external signer API (#18079)
* accounts, eth, clique: implement external backend + move sighash calc to backend * signer: implement account_Version on external API * accounts/external: enable ipc, add copyright * accounts, internal, signer: formatting * node: go fmt * flags: disallow --dev in combo with --externalsigner * accounts: remove clique-specific signing method, replace with more generic * accounts, consensus: formatting + fix error in tests * signer/core: remove (test-) import cycle * clique: remove unused import * accounts: remove CliqueHash and avoid dependency on package crypto * consensus/clique: unduplicate header encoding
This commit is contained in:
parent
520024dfd6
commit
43e8efe895
@ -18,12 +18,14 @@
|
|||||||
package accounts
|
package accounts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
ethereum "github.com/ethereum/go-ethereum"
|
ethereum "github.com/ethereum/go-ethereum"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Account represents an Ethereum account located at a specific location defined
|
// Account represents an Ethereum account located at a specific location defined
|
||||||
@ -87,8 +89,7 @@ type Wallet interface {
|
|||||||
// chain state reader.
|
// chain state reader.
|
||||||
SelfDerive(base DerivationPath, chain ethereum.ChainStateReader)
|
SelfDerive(base DerivationPath, chain ethereum.ChainStateReader)
|
||||||
|
|
||||||
// SignHash requests the wallet to sign the given hash.
|
// SignData requests the wallet to sign the hash of the given data
|
||||||
//
|
|
||||||
// It looks up the account specified either solely via its address contained within,
|
// It looks up the account specified either solely via its address contained within,
|
||||||
// or optionally with the aid of any location metadata from the embedded URL field.
|
// or optionally with the aid of any location metadata from the embedded URL field.
|
||||||
//
|
//
|
||||||
@ -98,7 +99,20 @@ type Wallet interface {
|
|||||||
// about which fields or actions are needed. The user may retry by providing
|
// about which fields or actions are needed. The user may retry by providing
|
||||||
// the needed details via SignHashWithPassphrase, or by other means (e.g. unlock
|
// the needed details via SignHashWithPassphrase, or by other means (e.g. unlock
|
||||||
// the account in a keystore).
|
// the account in a keystore).
|
||||||
SignHash(account Account, hash []byte) ([]byte, error)
|
SignData(account Account, mimeType string, data []byte) ([]byte, error)
|
||||||
|
|
||||||
|
// Signtext requests the wallet to sign the hash of a given piece of data, prefixed
|
||||||
|
// by the Ethereum prefix scheme
|
||||||
|
// It looks up the account specified either solely via its address contained within,
|
||||||
|
// or optionally with the aid of any location metadata from the embedded URL field.
|
||||||
|
//
|
||||||
|
// If the wallet requires additional authentication to sign the request (e.g.
|
||||||
|
// a password to decrypt the account, or a PIN code o verify the transaction),
|
||||||
|
// an AuthNeededError instance will be returned, containing infos for the user
|
||||||
|
// about which fields or actions are needed. The user may retry by providing
|
||||||
|
// the needed details via SignHashWithPassphrase, or by other means (e.g. unlock
|
||||||
|
// the account in a keystore).
|
||||||
|
SignText(account Account, text []byte) ([]byte, error)
|
||||||
|
|
||||||
// SignTx requests the wallet to sign the given transaction.
|
// SignTx requests the wallet to sign the given transaction.
|
||||||
//
|
//
|
||||||
@ -113,12 +127,12 @@ type Wallet interface {
|
|||||||
// the account in a keystore).
|
// the account in a keystore).
|
||||||
SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
|
SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
|
||||||
|
|
||||||
// SignHashWithPassphrase requests the wallet to sign the given hash with the
|
// SignTextWithPassphrase requests the wallet to sign the given text with the
|
||||||
// given passphrase as extra authentication information.
|
// given passphrase as extra authentication information.
|
||||||
//
|
//
|
||||||
// It looks up the account specified either solely via its address contained within,
|
// It looks up the account specified either solely via its address contained within,
|
||||||
// or optionally with the aid of any location metadata from the embedded URL field.
|
// or optionally with the aid of any location metadata from the embedded URL field.
|
||||||
SignHashWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error)
|
SignTextWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error)
|
||||||
|
|
||||||
// SignTxWithPassphrase requests the wallet to sign the given transaction, with the
|
// SignTxWithPassphrase requests the wallet to sign the given transaction, with the
|
||||||
// given passphrase as extra authentication information.
|
// given passphrase as extra authentication information.
|
||||||
@ -148,6 +162,19 @@ type Backend interface {
|
|||||||
Subscribe(sink chan<- WalletEvent) event.Subscription
|
Subscribe(sink chan<- WalletEvent) event.Subscription
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TextHash is a helper function that calculates a hash for the given message that can be
|
||||||
|
// safely used to calculate a signature from.
|
||||||
|
//
|
||||||
|
// The hash is calulcated as
|
||||||
|
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
|
||||||
|
//
|
||||||
|
// This gives context to the signed message and prevents signing of transactions.
|
||||||
|
func TextHash(data []byte) []byte {
|
||||||
|
hash := sha3.NewLegacyKeccak256()
|
||||||
|
fmt.Fprintf(hash, "\x19Ethereum Signed Message:\n%d%s", len(data), data)
|
||||||
|
return hash.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
// WalletEventType represents the different event types that can be fired by
|
// WalletEventType represents the different event types that can be fired by
|
||||||
// the wallet subscription subsystem.
|
// the wallet subscription subsystem.
|
||||||
type WalletEventType int
|
type WalletEventType int
|
||||||
|
32
accounts/accounts_test.go
Normal file
32
accounts/accounts_test.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2019 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package accounts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTextHash(t *testing.T) {
|
||||||
|
hash := TextHash([]byte("Hello Joe"))
|
||||||
|
want := hexutil.MustDecode("0xa080337ae51c4e064c189e113edd0ba391df9206e2f49db658bb32cf2911730b")
|
||||||
|
if !bytes.Equal(hash, want) {
|
||||||
|
t.Fatalf("wrong hash: %x", hash)
|
||||||
|
}
|
||||||
|
}
|
220
accounts/external/backend.go
vendored
Normal file
220
accounts/external/backend.go
vendored
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
// Copyright 2018 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 external
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"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/event"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/ethereum/go-ethereum/signer/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExternalBackend struct {
|
||||||
|
signers []accounts.Wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (eb *ExternalBackend) Wallets() []accounts.Wallet {
|
||||||
|
return eb.signers
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExternalBackend(endpoint string) (*ExternalBackend, error) {
|
||||||
|
signer, err := NewExternalSigner(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ExternalBackend{
|
||||||
|
signers: []accounts.Wallet{signer},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (eb *ExternalBackend) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
|
||||||
|
return event.NewSubscription(func(quit <-chan struct{}) error {
|
||||||
|
<-quit
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExternalSigner provides an API to interact with an external signer (clef)
|
||||||
|
// It proxies request to the external signer while forwarding relevant
|
||||||
|
// request headers
|
||||||
|
type ExternalSigner struct {
|
||||||
|
client *rpc.Client
|
||||||
|
endpoint string
|
||||||
|
status string
|
||||||
|
cacheMu sync.RWMutex
|
||||||
|
cache []accounts.Account
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExternalSigner(endpoint string) (*ExternalSigner, error) {
|
||||||
|
client, err := rpc.Dial(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
extsigner := &ExternalSigner{
|
||||||
|
client: client,
|
||||||
|
endpoint: endpoint,
|
||||||
|
}
|
||||||
|
// Check if reachable
|
||||||
|
version, err := extsigner.pingVersion()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
extsigner.status = fmt.Sprintf("ok [version=%v]", version)
|
||||||
|
return extsigner, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) URL() accounts.URL {
|
||||||
|
return accounts.URL{
|
||||||
|
Scheme: "extapi",
|
||||||
|
Path: api.endpoint,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) Status() (string, error) {
|
||||||
|
return api.status, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) Open(passphrase string) error {
|
||||||
|
return fmt.Errorf("operation not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) Close() error {
|
||||||
|
return fmt.Errorf("operation not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) Accounts() []accounts.Account {
|
||||||
|
var accnts []accounts.Account
|
||||||
|
res, err := api.listAccounts()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("account listing failed", "error", err)
|
||||||
|
return accnts
|
||||||
|
}
|
||||||
|
for _, addr := range res {
|
||||||
|
accnts = append(accnts, accounts.Account{
|
||||||
|
URL: accounts.URL{
|
||||||
|
Scheme: "extapi",
|
||||||
|
Path: api.endpoint,
|
||||||
|
},
|
||||||
|
Address: addr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
api.cacheMu.Lock()
|
||||||
|
api.cache = accnts
|
||||||
|
api.cacheMu.Unlock()
|
||||||
|
return accnts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) Contains(account accounts.Account) bool {
|
||||||
|
api.cacheMu.RLock()
|
||||||
|
defer api.cacheMu.RUnlock()
|
||||||
|
for _, a := range api.cache {
|
||||||
|
if a.Address == account.Address && (account.URL == (accounts.URL{}) || account.URL == api.URL()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
|
||||||
|
return accounts.Account{}, fmt.Errorf("operation not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) {
|
||||||
|
log.Error("operation SelfDerive not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) signHash(account accounts.Account, hash []byte) ([]byte, error) {
|
||||||
|
return []byte{}, fmt.Errorf("operation not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed
|
||||||
|
func (api *ExternalSigner) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
|
||||||
|
// TODO! Replace this with a call to clef SignData with correct mime-type for Clique, once we
|
||||||
|
// have that in place
|
||||||
|
return api.signHash(account, crypto.Keccak256(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]byte, error) {
|
||||||
|
return api.signHash(account, accounts.TextHash(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||||
|
res := ethapi.SignTransactionResult{}
|
||||||
|
to := common.NewMixedcaseAddress(*tx.To())
|
||||||
|
data := hexutil.Bytes(tx.Data())
|
||||||
|
args := &core.SendTxArgs{
|
||||||
|
Data: &data,
|
||||||
|
Nonce: hexutil.Uint64(tx.Nonce()),
|
||||||
|
Value: hexutil.Big(*tx.Value()),
|
||||||
|
Gas: hexutil.Uint64(tx.Gas()),
|
||||||
|
GasPrice: hexutil.Big(*tx.GasPrice()),
|
||||||
|
To: &to,
|
||||||
|
From: common.NewMixedcaseAddress(account.Address),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := api.client.Call(&res, "account_signTransaction", args); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res.Tx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
|
||||||
|
return []byte{}, fmt.Errorf("operation not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||||
|
return nil, fmt.Errorf("operation not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) listAccounts() ([]common.Address, error) {
|
||||||
|
var res []common.Address
|
||||||
|
if err := api.client.Call(&res, "account_list"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) signCliqueBlock(a common.Address, rlpBlock hexutil.Bytes) (hexutil.Bytes, error) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) pingVersion() (string, error) {
|
||||||
|
var v string
|
||||||
|
if err := api.client.Call(&v, "account_version"); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
@ -22,6 +22,7 @@ import (
|
|||||||
ethereum "github.com/ethereum/go-ethereum"
|
ethereum "github.com/ethereum/go-ethereum"
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// keystoreWallet implements the accounts.Wallet interface for the original
|
// keystoreWallet implements the accounts.Wallet interface for the original
|
||||||
@ -78,11 +79,11 @@ func (w *keystoreWallet) Derive(path accounts.DerivationPath, pin bool) (account
|
|||||||
// there is no notion of hierarchical account derivation for plain keystore accounts.
|
// there is no notion of hierarchical account derivation for plain keystore accounts.
|
||||||
func (w *keystoreWallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) {}
|
func (w *keystoreWallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) {}
|
||||||
|
|
||||||
// SignHash implements accounts.Wallet, attempting to sign the given hash with
|
// signHash attempts to sign the given hash with
|
||||||
// the given account. If the wallet does not wrap this particular account, an
|
// the given account. If the wallet does not wrap this particular account, an
|
||||||
// error is returned to avoid account leakage (even though in theory we may be
|
// error is returned to avoid account leakage (even though in theory we may be
|
||||||
// able to sign via our shared keystore backend).
|
// able to sign via our shared keystore backend).
|
||||||
func (w *keystoreWallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) {
|
func (w *keystoreWallet) signHash(account accounts.Account, hash []byte) ([]byte, error) {
|
||||||
// Make sure the requested account is contained within
|
// Make sure the requested account is contained within
|
||||||
if !w.Contains(account) {
|
if !w.Contains(account) {
|
||||||
return nil, accounts.ErrUnknownAccount
|
return nil, accounts.ErrUnknownAccount
|
||||||
@ -91,6 +92,15 @@ func (w *keystoreWallet) SignHash(account accounts.Account, hash []byte) ([]byte
|
|||||||
return w.keystore.SignHash(account, hash)
|
return w.keystore.SignHash(account, hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed
|
||||||
|
func (w *keystoreWallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
|
||||||
|
return w.signHash(account, crypto.Keccak256(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *keystoreWallet) SignText(account accounts.Account, text []byte) ([]byte, error) {
|
||||||
|
return w.signHash(account, accounts.TextHash(text))
|
||||||
|
}
|
||||||
|
|
||||||
// SignTx implements accounts.Wallet, attempting to sign the given transaction
|
// SignTx implements accounts.Wallet, attempting to sign the given transaction
|
||||||
// with the given account. If the wallet does not wrap this particular account,
|
// with the given account. If the wallet does not wrap this particular account,
|
||||||
// an error is returned to avoid account leakage (even though in theory we may
|
// an error is returned to avoid account leakage (even though in theory we may
|
||||||
@ -106,13 +116,13 @@ func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction,
|
|||||||
|
|
||||||
// SignHashWithPassphrase implements accounts.Wallet, attempting to sign the
|
// SignHashWithPassphrase implements accounts.Wallet, attempting to sign the
|
||||||
// given hash with the given account using passphrase as extra authentication.
|
// given hash with the given account using passphrase as extra authentication.
|
||||||
func (w *keystoreWallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) {
|
func (w *keystoreWallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
|
||||||
// Make sure the requested account is contained within
|
// Make sure the requested account is contained within
|
||||||
if !w.Contains(account) {
|
if !w.Contains(account) {
|
||||||
return nil, accounts.ErrUnknownAccount
|
return nil, accounts.ErrUnknownAccount
|
||||||
}
|
}
|
||||||
// Account seems valid, request the keystore to sign
|
// Account seems valid, request the keystore to sign
|
||||||
return w.keystore.SignHashWithPassphrase(account, passphrase, hash)
|
return w.keystore.SignHashWithPassphrase(account, passphrase, accounts.TextHash(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
|
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
|
||||||
|
@ -29,6 +29,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/karalabe/hid"
|
"github.com/karalabe/hid"
|
||||||
)
|
)
|
||||||
@ -495,12 +496,21 @@ func (w *wallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainSt
|
|||||||
w.deriveChain = chain
|
w.deriveChain = chain
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignHash implements accounts.Wallet, however signing arbitrary data is not
|
// signHash implements accounts.Wallet, however signing arbitrary data is not
|
||||||
// supported for hardware wallets, so this method will always return an error.
|
// supported for hardware wallets, so this method will always return an error.
|
||||||
func (w *wallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) {
|
func (w *wallet) signHash(account accounts.Account, hash []byte) ([]byte, error) {
|
||||||
return nil, accounts.ErrNotSupported
|
return nil, accounts.ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed
|
||||||
|
func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
|
||||||
|
return w.signHash(account, crypto.Keccak256(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wallet) SignText(account accounts.Account, text []byte) ([]byte, error) {
|
||||||
|
return w.signHash(account, accounts.TextHash(text))
|
||||||
|
}
|
||||||
|
|
||||||
// SignTx implements accounts.Wallet. It sends the transaction over to the Ledger
|
// SignTx implements accounts.Wallet. It sends the transaction over to the Ledger
|
||||||
// wallet to request a confirmation from the user. It returns either the signed
|
// wallet to request a confirmation from the user. It returns either the signed
|
||||||
// transaction or a failure if the user denied the transaction.
|
// transaction or a failure if the user denied the transaction.
|
||||||
@ -550,8 +560,8 @@ func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID
|
|||||||
// SignHashWithPassphrase implements accounts.Wallet, however signing arbitrary
|
// SignHashWithPassphrase implements accounts.Wallet, however signing arbitrary
|
||||||
// data is not supported for Ledger wallets, so this method will always return
|
// data is not supported for Ledger wallets, so this method will always return
|
||||||
// an error.
|
// an error.
|
||||||
func (w *wallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) {
|
func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
|
||||||
return w.SignHash(account, hash)
|
return w.SignText(account, accounts.TextHash(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
|
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
|
||||||
|
@ -49,12 +49,6 @@ import (
|
|||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExternalAPIVersion -- see extapi_changelog.md
|
|
||||||
const ExternalAPIVersion = "4.0.0"
|
|
||||||
|
|
||||||
// InternalAPIVersion -- see intapi_changelog.md
|
|
||||||
const InternalAPIVersion = "3.0.0"
|
|
||||||
|
|
||||||
const legalWarning = `
|
const legalWarning = `
|
||||||
WARNING!
|
WARNING!
|
||||||
|
|
||||||
@ -479,8 +473,8 @@ func signer(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
ui.OnSignerStartup(core.StartupInfo{
|
ui.OnSignerStartup(core.StartupInfo{
|
||||||
Info: map[string]interface{}{
|
Info: map[string]interface{}{
|
||||||
"extapi_version": ExternalAPIVersion,
|
"extapi_version": core.ExternalAPIVersion,
|
||||||
"intapi_version": InternalAPIVersion,
|
"intapi_version": core.InternalAPIVersion,
|
||||||
"extapi_http": extapiURL,
|
"extapi_http": extapiURL,
|
||||||
"extapi_ipc": ipcapiURL,
|
"extapi_ipc": ipcapiURL,
|
||||||
},
|
},
|
||||||
|
@ -62,6 +62,7 @@ var (
|
|||||||
utils.BootnodesV5Flag,
|
utils.BootnodesV5Flag,
|
||||||
utils.DataDirFlag,
|
utils.DataDirFlag,
|
||||||
utils.KeyStoreDirFlag,
|
utils.KeyStoreDirFlag,
|
||||||
|
utils.ExternalSignerFlag,
|
||||||
utils.NoUSBFlag,
|
utils.NoUSBFlag,
|
||||||
utils.DashboardEnabledFlag,
|
utils.DashboardEnabledFlag,
|
||||||
utils.DashboardAddrFlag,
|
utils.DashboardAddrFlag,
|
||||||
@ -293,13 +294,14 @@ func startNode(ctx *cli.Context, stack *node.Node) {
|
|||||||
utils.StartNode(stack)
|
utils.StartNode(stack)
|
||||||
|
|
||||||
// Unlock any account specifically requested
|
// Unlock any account specifically requested
|
||||||
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
if keystores := stack.AccountManager().Backends(keystore.KeyStoreType); len(keystores) > 0 {
|
||||||
|
ks := keystores[0].(*keystore.KeyStore)
|
||||||
passwords := utils.MakePasswordList(ctx)
|
passwords := utils.MakePasswordList(ctx)
|
||||||
unlocks := strings.Split(ctx.GlobalString(utils.UnlockedAccountFlag.Name), ",")
|
unlocks := strings.Split(ctx.GlobalString(utils.UnlockedAccountFlag.Name), ",")
|
||||||
for i, account := range unlocks {
|
for i, account := range unlocks {
|
||||||
if trimmed := strings.TrimSpace(account); trimmed != "" {
|
if trimmed := strings.TrimSpace(account); trimmed != "" {
|
||||||
unlockAccount(ctx, ks, trimmed, i, passwords)
|
unlockAccount(ctx, ks, trimmed, i, passwords)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Register wallet event handlers to open and auto-derive wallets
|
// Register wallet event handlers to open and auto-derive wallets
|
||||||
|
@ -145,6 +145,7 @@ var AppHelpFlagGroups = []flagGroup{
|
|||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
utils.UnlockedAccountFlag,
|
utils.UnlockedAccountFlag,
|
||||||
utils.PasswordFileFlag,
|
utils.PasswordFileFlag,
|
||||||
|
utils.ExternalSignerFlag,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -427,7 +427,11 @@ var (
|
|||||||
Usage: "Password file to use for non-interactive password input",
|
Usage: "Password file to use for non-interactive password input",
|
||||||
Value: "",
|
Value: "",
|
||||||
}
|
}
|
||||||
|
ExternalSignerFlag = cli.StringFlag{
|
||||||
|
Name: "signer",
|
||||||
|
Usage: "External signer (url or path to ipc file)",
|
||||||
|
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",
|
||||||
@ -990,11 +994,15 @@ 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)
|
if ks != nil {
|
||||||
if err != nil {
|
account, err := MakeAddress(ks, etherbase)
|
||||||
Fatalf("Invalid miner etherbase: %v", err)
|
if err != nil {
|
||||||
|
Fatalf("Invalid miner etherbase: %v", err)
|
||||||
|
}
|
||||||
|
cfg.Etherbase = account.Address
|
||||||
|
} else {
|
||||||
|
Fatalf("No etherbase configured")
|
||||||
}
|
}
|
||||||
cfg.Etherbase = account.Address
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1093,6 +1101,10 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
|
|||||||
setNodeUserIdent(ctx, cfg)
|
setNodeUserIdent(ctx, cfg)
|
||||||
setDataDir(ctx, cfg)
|
setDataDir(ctx, cfg)
|
||||||
|
|
||||||
|
if ctx.GlobalIsSet(ExternalSignerFlag.Name) {
|
||||||
|
cfg.ExternalSigner = ctx.GlobalString(ExternalSignerFlag.Name)
|
||||||
|
}
|
||||||
|
|
||||||
if ctx.GlobalIsSet(KeyStoreDirFlag.Name) {
|
if ctx.GlobalIsSet(KeyStoreDirFlag.Name) {
|
||||||
cfg.KeyStoreDir = ctx.GlobalString(KeyStoreDirFlag.Name)
|
cfg.KeyStoreDir = ctx.GlobalString(KeyStoreDirFlag.Name)
|
||||||
}
|
}
|
||||||
@ -1274,8 +1286,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
|||||||
// Avoid conflicting network flags
|
// Avoid conflicting network flags
|
||||||
checkExclusive(ctx, DeveloperFlag, TestnetFlag, RinkebyFlag, GoerliFlag)
|
checkExclusive(ctx, DeveloperFlag, TestnetFlag, RinkebyFlag, GoerliFlag)
|
||||||
checkExclusive(ctx, LightServFlag, SyncModeFlag, "light")
|
checkExclusive(ctx, LightServFlag, SyncModeFlag, "light")
|
||||||
|
// Can't use both ephemeral unlocked and external signer
|
||||||
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
checkExclusive(ctx, DeveloperFlag, ExternalSignerFlag)
|
||||||
|
var ks *keystore.KeyStore
|
||||||
|
if keystores := stack.AccountManager().Backends(keystore.KeyStoreType); len(keystores) > 0 {
|
||||||
|
ks = keystores[0].(*keystore.KeyStore)
|
||||||
|
}
|
||||||
setEtherbase(ctx, ks, cfg)
|
setEtherbase(ctx, ks, cfg)
|
||||||
setGPO(ctx, &cfg.GPO)
|
setGPO(ctx, &cfg.GPO)
|
||||||
setTxPool(ctx, &cfg.TxPool)
|
setTxPool(ctx, &cfg.TxPool)
|
||||||
|
@ -20,6 +20,7 @@ package clique
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"sync"
|
"sync"
|
||||||
@ -136,40 +137,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 header to be signed by a
|
||||||
// backing account.
|
// backing account.
|
||||||
type SignerFn func(accounts.Account, []byte) ([]byte, error)
|
type SignerFn func(accounts.Account, string, []byte) ([]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
|
|
||||||
// contained at the end of the extra data.
|
|
||||||
//
|
|
||||||
// Note, the method requires the extra data to be at least 65 bytes, otherwise it
|
|
||||||
// panics. This is done to avoid accidentally using both forms (signature present
|
|
||||||
// or not), which could be abused to produce different hashes for the same header.
|
|
||||||
func sigHash(header *types.Header) (hash common.Hash) {
|
|
||||||
hasher := sha3.NewLegacyKeccak256()
|
|
||||||
|
|
||||||
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], // Yes, this will panic if extra is too short
|
|
||||||
header.MixDigest,
|
|
||||||
header.Nonce,
|
|
||||||
})
|
|
||||||
hasher.Sum(hash[:0])
|
|
||||||
return hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// ecrecover extracts the Ethereum account address from a signed header.
|
// ecrecover extracts the Ethereum account address from a signed header.
|
||||||
func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) {
|
func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) {
|
||||||
@ -185,7 +155,7 @@ func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, er
|
|||||||
signature := header.Extra[len(header.Extra)-extraSeal:]
|
signature := header.Extra[len(header.Extra)-extraSeal:]
|
||||||
|
|
||||||
// Recover the public key and the Ethereum address
|
// Recover the public key and the Ethereum address
|
||||||
pubkey, err := crypto.Ecrecover(sigHash(header).Bytes(), signature)
|
pubkey, err := crypto.Ecrecover(SealHash(header).Bytes(), signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Address{}, err
|
return common.Address{}, err
|
||||||
}
|
}
|
||||||
@ -646,7 +616,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}, "application/x-clique-header", CliqueRLP(header))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -663,7 +633,7 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results c
|
|||||||
select {
|
select {
|
||||||
case results <- block.WithSeal(header):
|
case results <- block.WithSeal(header):
|
||||||
default:
|
default:
|
||||||
log.Warn("Sealing result is not read by miner", "sealhash", c.SealHash(header))
|
log.Warn("Sealing result is not read by miner", "sealhash", SealHash(header))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -693,7 +663,7 @@ func CalcDifficulty(snap *Snapshot, signer common.Address) *big.Int {
|
|||||||
|
|
||||||
// SealHash returns the hash of a block prior to it being sealed.
|
// SealHash returns the hash of a block prior to it being sealed.
|
||||||
func (c *Clique) SealHash(header *types.Header) common.Hash {
|
func (c *Clique) SealHash(header *types.Header) common.Hash {
|
||||||
return sigHash(header)
|
return SealHash(header)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close implements consensus.Engine. It's a noop for clique as there are no background threads.
|
// Close implements consensus.Engine. It's a noop for clique as there are no background threads.
|
||||||
@ -711,3 +681,47 @@ func (c *Clique) APIs(chain consensus.ChainReader) []rpc.API {
|
|||||||
Public: false,
|
Public: false,
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SealHash returns the hash of a block prior to it being sealed.
|
||||||
|
func SealHash(header *types.Header) (hash common.Hash) {
|
||||||
|
hasher := sha3.NewLegacyKeccak256()
|
||||||
|
encodeSigHeader(hasher, header)
|
||||||
|
hasher.Sum(hash[:0])
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// CliqueRLP returns the rlp bytes which needs to be signed for the proof-of-authority
|
||||||
|
// sealing. The RLP to sign consists of the entire header apart from the 65 byte signature
|
||||||
|
// contained at the end of the extra data.
|
||||||
|
//
|
||||||
|
// Note, the method requires the extra data to be at least 65 bytes, otherwise it
|
||||||
|
// panics. This is done to avoid accidentally using both forms (signature present
|
||||||
|
// or not), which could be abused to produce different hashes for the same header.
|
||||||
|
func CliqueRLP(header *types.Header) []byte {
|
||||||
|
b := new(bytes.Buffer)
|
||||||
|
encodeSigHeader(b, header)
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeSigHeader(w io.Writer, header *types.Header) {
|
||||||
|
err := rlp.Encode(w, []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], // Yes, this will panic if extra is too short
|
||||||
|
header.MixDigest,
|
||||||
|
header.Nonce,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic("can't encode: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -80,7 +80,7 @@ func (ap *testerAccountPool) sign(header *types.Header, signer string) {
|
|||||||
ap.accounts[signer], _ = crypto.GenerateKey()
|
ap.accounts[signer], _ = crypto.GenerateKey()
|
||||||
}
|
}
|
||||||
// Sign the header and embed the signature in extra data
|
// Sign the header and embed the signature in extra data
|
||||||
sig, _ := crypto.Sign(sigHash(header).Bytes(), ap.accounts[signer])
|
sig, _ := crypto.Sign(SealHash(header).Bytes(), ap.accounts[signer])
|
||||||
copy(header.Extra[len(header.Extra)-extraSeal:], sig)
|
copy(header.Extra[len(header.Extra)-extraSeal:], sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,7 +434,7 @@ func (s *Ethereum) StartMining(threads int) error {
|
|||||||
log.Error("Etherbase account unavailable locally", "err", err)
|
log.Error("Etherbase account unavailable locally", "err", err)
|
||||||
return fmt.Errorf("signer missing: %v", err)
|
return fmt.Errorf("signer missing: %v", err)
|
||||||
}
|
}
|
||||||
clique.Authorize(eb, wallet.SignHash)
|
clique.Authorize(eb, wallet.SignData)
|
||||||
}
|
}
|
||||||
// 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.
|
||||||
|
@ -217,7 +217,7 @@ func NewPrivateAccountAPI(b Backend, nonceLock *AddrLocker) *PrivateAccountAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
addresses := make([]common.Address, 0) // return [] instead of nil if empty
|
addresses := make([]common.Address, 0) // return [] instead of nil if empty
|
||||||
for _, wallet := range s.am.Wallets() {
|
for _, wallet := range s.am.Wallets() {
|
||||||
@ -409,18 +409,6 @@ func (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args SendTxArgs
|
|||||||
return &SignTransactionResult{data, signed}, nil
|
return &SignTransactionResult{data, signed}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// signHash is a helper function that calculates a hash for the given message that can be
|
|
||||||
// safely used to calculate a signature from.
|
|
||||||
//
|
|
||||||
// The hash is calulcated as
|
|
||||||
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
|
|
||||||
//
|
|
||||||
// This gives context to the signed message and prevents signing of transactions.
|
|
||||||
func signHash(data []byte) []byte {
|
|
||||||
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data)
|
|
||||||
return crypto.Keccak256([]byte(msg))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign calculates an Ethereum ECDSA signature for:
|
// Sign calculates an Ethereum ECDSA signature for:
|
||||||
// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message))
|
// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message))
|
||||||
//
|
//
|
||||||
@ -439,7 +427,7 @@ func (s *PrivateAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr c
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Assemble sign the data with the wallet
|
// Assemble sign the data with the wallet
|
||||||
signature, err := wallet.SignHashWithPassphrase(account, passwd, signHash(data))
|
signature, err := wallet.SignTextWithPassphrase(account, passwd, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Failed data sign attempt", "address", addr, "err", err)
|
log.Warn("Failed data sign attempt", "address", addr, "err", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -467,7 +455,7 @@ func (s *PrivateAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.Byt
|
|||||||
}
|
}
|
||||||
sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1
|
sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1
|
||||||
|
|
||||||
rpk, err := crypto.SigToPub(signHash(data), sig)
|
rpk, err := crypto.SigToPub(accounts.TextHash(data), sig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Address{}, err
|
return common.Address{}, err
|
||||||
}
|
}
|
||||||
@ -1357,7 +1345,7 @@ func (s *PublicTransactionPoolAPI) Sign(addr common.Address, data hexutil.Bytes)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Sign the requested hash with the wallet
|
// Sign the requested hash with the wallet
|
||||||
signature, err := wallet.SignHash(account, signHash(data))
|
signature, err := wallet.SignText(account, data)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/external"
|
||||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
"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"
|
||||||
@ -80,6 +81,9 @@ type Config struct {
|
|||||||
// is created by New and destroyed when the node is stopped.
|
// is created by New and destroyed when the node is stopped.
|
||||||
KeyStoreDir string `toml:",omitempty"`
|
KeyStoreDir string `toml:",omitempty"`
|
||||||
|
|
||||||
|
// ExternalSigner specifies an external URI for a clef-type signer
|
||||||
|
ExternalSigner string `toml:"omitempty"`
|
||||||
|
|
||||||
// UseLightweightKDF lowers the memory and CPU requirements of the key store
|
// UseLightweightKDF lowers the memory and CPU requirements of the key store
|
||||||
// scrypt KDF at the expense of security.
|
// scrypt KDF at the expense of security.
|
||||||
UseLightweightKDF bool `toml:",omitempty"`
|
UseLightweightKDF bool `toml:",omitempty"`
|
||||||
@ -462,23 +466,37 @@ func makeAccountManager(conf *Config) (*accounts.Manager, string, error) {
|
|||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
// Assemble the account manager and supported backends
|
// Assemble the account manager and supported backends
|
||||||
backends := []accounts.Backend{
|
backends := []accounts.Backend{}
|
||||||
keystore.NewKeyStore(keydir, scryptN, scryptP),
|
if len(conf.ExternalSigner) > 0 {
|
||||||
}
|
log.Info("Using external signer", "url", conf.ExternalSigner)
|
||||||
if !conf.NoUSB {
|
if extapi, err := external.NewExternalBackend(conf.ExternalSigner); err == nil {
|
||||||
// Start a USB hub for Ledger hardware wallets
|
backends = append(backends, extapi)
|
||||||
if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil {
|
|
||||||
log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err))
|
|
||||||
} else {
|
} else {
|
||||||
backends = append(backends, ledgerhub)
|
log.Info("Error configuring external signer", "error", err)
|
||||||
}
|
|
||||||
// Start a USB hub for Trezor hardware wallets
|
|
||||||
if trezorhub, err := usbwallet.NewTrezorHub(); err != nil {
|
|
||||||
log.Warn(fmt.Sprintf("Failed to start Trezor hub, disabling: %v", err))
|
|
||||||
} else {
|
|
||||||
backends = append(backends, trezorhub)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(backends) == 0 {
|
||||||
|
// For now, we're using EITHER external signer OR local signers.
|
||||||
|
// If/when we implement some form of lockfile for USB and keystore wallets,
|
||||||
|
// we can have both, but it's very confusing for the user to see the same
|
||||||
|
// accounts in both externally and locally, plus very racey.
|
||||||
|
backends = append(backends, keystore.NewKeyStore(keydir, scryptN, scryptP))
|
||||||
|
if !conf.NoUSB {
|
||||||
|
// Start a USB hub for Ledger hardware wallets
|
||||||
|
if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil {
|
||||||
|
log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err))
|
||||||
|
} else {
|
||||||
|
backends = append(backends, ledgerhub)
|
||||||
|
}
|
||||||
|
// Start a USB hub for Trezor hardware wallets
|
||||||
|
if trezorhub, err := usbwallet.NewTrezorHub(); err != nil {
|
||||||
|
log.Warn(fmt.Sprintf("Failed to start Trezor hub, disabling: %v", err))
|
||||||
|
} else {
|
||||||
|
backends = append(backends, trezorhub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return accounts.NewManager(backends...), ephemeral, nil
|
return accounts.NewManager(backends...), ephemeral, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,8 +36,14 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// numberOfAccountsToDerive For hardware wallets, the number of accounts to derive
|
const (
|
||||||
const numberOfAccountsToDerive = 10
|
// numberOfAccountsToDerive For hardware wallets, the number of accounts to derive
|
||||||
|
numberOfAccountsToDerive = 10
|
||||||
|
// ExternalAPIVersion -- see extapi_changelog.md
|
||||||
|
ExternalAPIVersion = "4.0.0"
|
||||||
|
// InternalAPIVersion -- see intapi_changelog.md
|
||||||
|
InternalAPIVersion = "3.0.0"
|
||||||
|
)
|
||||||
|
|
||||||
// ExternalAPI defines the external API through which signing requests are made.
|
// ExternalAPI defines the external API through which signing requests are made.
|
||||||
type ExternalAPI interface {
|
type ExternalAPI interface {
|
||||||
@ -55,6 +61,7 @@ type ExternalAPI interface {
|
|||||||
// Should be moved to Internal API, in next phase when we have
|
// Should be moved to Internal API, in next phase when we have
|
||||||
// bi-directional communication
|
// bi-directional communication
|
||||||
//Import(ctx context.Context, keyJSON json.RawMessage) (Account, error)
|
//Import(ctx context.Context, keyJSON json.RawMessage) (Account, error)
|
||||||
|
Version(ctx context.Context) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignerUI specifies what method a UI needs to implement to be able to be used as a UI for the signer
|
// SignerUI specifies what method a UI needs to implement to be able to be used as a UI for the signer
|
||||||
@ -539,7 +546,7 @@ func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, da
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Assemble sign the data with the wallet
|
// Assemble sign the data with the wallet
|
||||||
signature, err := wallet.SignHashWithPassphrase(account, res.Password, sighash)
|
signature, err := wallet.SignTextWithPassphrase(account, res.Password, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.UI.ShowError(err.Error())
|
api.UI.ShowError(err.Error())
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -610,3 +617,9 @@ func (api *SignerAPI) Import(ctx context.Context, keyJSON json.RawMessage) (Acco
|
|||||||
}
|
}
|
||||||
return Account{Typ: "Account", URL: acc.URL, Address: acc.Address}, nil
|
return Account{Typ: "Account", URL: acc.URL, Address: acc.Address}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the external api version. This method does not require user acceptance. Available methods are
|
||||||
|
// available via enumeration anyway, and this info does not contain user-specific data
|
||||||
|
func (api *SignerAPI) Version(ctx context.Context) (string, error) {
|
||||||
|
return ExternalAPIVersion, nil
|
||||||
|
}
|
||||||
|
@ -29,7 +29,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
|
||||||
"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/core/types"
|
||||||
@ -135,7 +134,7 @@ func setup(t *testing.T) (*SignerAPI, chan string) {
|
|||||||
|
|
||||||
db, err := NewAbiDBFromFile("../../cmd/clef/4byte.json")
|
db, err := NewAbiDBFromFile("../../cmd/clef/4byte.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
ui = &HeadlessUI{controller}
|
ui = &HeadlessUI{controller}
|
||||||
|
@ -80,14 +80,13 @@ func (l *AuditLogger) Export(ctx context.Context, addr common.Address) (json.Raw
|
|||||||
return j, e
|
return j, e
|
||||||
}
|
}
|
||||||
|
|
||||||
//func (l *AuditLogger) Import(ctx context.Context, keyJSON json.RawMessage) (Account, error) {
|
func (l *AuditLogger) Version(ctx context.Context) (string, error) {
|
||||||
// // Don't actually log the json contents
|
l.log.Info("Version", "type", "request", "metadata", MetadataFromContext(ctx).String())
|
||||||
// l.log.Info("Import", "type", "request", "metadata", MetadataFromContext(ctx).String(),
|
data, err := l.api.Version(ctx)
|
||||||
// "keyJSON size", len(keyJSON))
|
l.log.Info("Version", "type", "response", "data", data, "error", err)
|
||||||
// a, e := l.api.Import(ctx, keyJSON)
|
return data, err
|
||||||
// l.log.Info("Import", "type", "response", "addr", a.String(), "error", e)
|
|
||||||
// return a, e
|
}
|
||||||
//}
|
|
||||||
|
|
||||||
func NewAuditLogger(path string, api ExternalAPI) (*AuditLogger, error) {
|
func NewAuditLogger(path string, api ExternalAPI) (*AuditLogger, error) {
|
||||||
l := log.New("api", "signer")
|
l := log.New("api", "signer")
|
||||||
|
Loading…
Reference in New Issue
Block a user