aboutsummaryrefslogtreecommitdiff
path: root/accounts/external
diff options
context:
space:
mode:
Diffstat (limited to 'accounts/external')
-rw-r--r--accounts/external/backend.go231
1 files changed, 231 insertions, 0 deletions
diff --git a/accounts/external/backend.go b/accounts/external/backend.go
new file mode 100644
index 0000000..16e201d
--- /dev/null
+++ b/accounts/external/backend.go
@@ -0,0 +1,231 @@
+// 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/ava-labs/coreth/accounts"
+ "github.com/ava-labs/coreth/core/types"
+ "github.com/ava-labs/coreth/internal/ethapi"
+ "github.com/ava-labs/coreth/rpc"
+ "github.com/ava-labs/go-ethereum"
+ "github.com/ava-labs/go-ethereum/common"
+ "github.com/ava-labs/go-ethereum/common/hexutil"
+ "github.com/ava-labs/go-ethereum/event"
+ "github.com/ava-labs/go-ethereum/log"
+ "github.com/ava-labs/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(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 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 res hexutil.Bytes
+ var signAddress = common.NewMixedcaseAddress(account.Address)
+ if err := api.client.Call(&res, "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
+ }
+ return res, nil
+}
+
+func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
+ res := ethapi.SignTransactionResult{}
+ 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),
+ }
+ 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
+}