123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- // 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 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/event"
- "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()
- if api.cache == nil {
- // If we haven't already fetched the accounts, it's time to do so now
- api.cacheMu.RUnlock()
- api.Accounts()
- api.cacheMu.RLock()
- }
- 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(bases []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) {
- var res hexutil.Bytes
- var signAddress = common.NewMixedcaseAddress(account.Address)
- if err := api.client.Call(&res, "account_signData",
- mimeType,
- &signAddress, // Need to use the pointer here, because of how MarshalJSON is defined
- hexutil.Encode(data)); err != nil {
- return nil, err
- }
- // If V is on 27/28-form, convert to 0/1 for Clique
- if mimeType == accounts.MimetypeClique && (res[64] == 27 || res[64] == 28) {
- res[64] -= 27 // Transform V from 27/28 to 0/1 for Clique use
- }
- return res, nil
- }
- func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]byte, error) {
- var signature hexutil.Bytes
- var signAddress = common.NewMixedcaseAddress(account.Address)
- if err := api.client.Call(&signature, "account_signData",
- accounts.MimetypeTextPlain,
- &signAddress, // Need to use the pointer here, because of how MarshalJSON is defined
- hexutil.Encode(text)); err != nil {
- return nil, err
- }
- if signature[64] == 27 || signature[64] == 28 {
- // If clef is used as a backend, it may already have transformed
- // the signature to ethereum-type signature.
- signature[64] -= 27 // Transform V from Ethereum-legacy to 0/1
- }
- return signature, nil
- }
- // signTransactionResult represents the signinig result returned by clef.
- type signTransactionResult struct {
- Raw hexutil.Bytes `json:"raw"`
- Tx *types.Transaction `json:"tx"`
- }
- func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
- data := hexutil.Bytes(tx.Data())
- var to *common.MixedcaseAddress
- if tx.To() != nil {
- t := common.NewMixedcaseAddress(*tx.To())
- to = &t
- }
- 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),
- // Quorum
- IsPrivate: tx.IsPrivate(),
- }
- // We should request the default chain id that we're operating with
- // (the chain we're executing on)
- if chainID != nil {
- args.ChainID = (*hexutil.Big)(chainID)
- }
- // However, if the user asked for a particular chain id, then we should
- // use that instead.
- if tx.Type() != types.LegacyTxType && tx.ChainId() != nil {
- args.ChainID = (*hexutil.Big)(tx.ChainId())
- }
- if tx.Type() == types.AccessListTxType {
- accessList := tx.AccessList()
- args.AccessList = &accessList
- }
- var res signTransactionResult
- 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("password-operations 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("password-operations not supported on external signers")
- }
- func (api *ExternalSigner) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
- return nil, fmt.Errorf("password-operations 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) pingVersion() (string, error) {
- var v string
- if err := api.client.Call(&v, "account_version"); err != nil {
- return "", err
- }
- return v, nil
- }
|