github.com/benorgera/go-ethereum@v1.10.18-0.20220401011646-b3f57b1a73ba/accounts/external/backend.go (about) 1 // Copyright 2019 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package external 18 19 import ( 20 "fmt" 21 "math/big" 22 "sync" 23 24 "github.com/ethereum/go-ethereum" 25 "github.com/ethereum/go-ethereum/accounts" 26 "github.com/ethereum/go-ethereum/common" 27 "github.com/ethereum/go-ethereum/common/hexutil" 28 "github.com/ethereum/go-ethereum/core/types" 29 "github.com/ethereum/go-ethereum/event" 30 "github.com/ethereum/go-ethereum/log" 31 "github.com/ethereum/go-ethereum/rpc" 32 "github.com/ethereum/go-ethereum/signer/core/apitypes" 33 ) 34 35 type ExternalBackend struct { 36 signers []accounts.Wallet 37 } 38 39 func (eb *ExternalBackend) Wallets() []accounts.Wallet { 40 return eb.signers 41 } 42 43 func NewExternalBackend(endpoint string) (*ExternalBackend, error) { 44 signer, err := NewExternalSigner(endpoint) 45 if err != nil { 46 return nil, err 47 } 48 return &ExternalBackend{ 49 signers: []accounts.Wallet{signer}, 50 }, nil 51 } 52 53 func (eb *ExternalBackend) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { 54 return event.NewSubscription(func(quit <-chan struct{}) error { 55 <-quit 56 return nil 57 }) 58 } 59 60 // ExternalSigner provides an API to interact with an external signer (clef) 61 // It proxies request to the external signer while forwarding relevant 62 // request headers 63 type ExternalSigner struct { 64 client *rpc.Client 65 endpoint string 66 status string 67 cacheMu sync.RWMutex 68 cache []accounts.Account 69 } 70 71 func NewExternalSigner(endpoint string) (*ExternalSigner, error) { 72 client, err := rpc.Dial(endpoint) 73 if err != nil { 74 return nil, err 75 } 76 extsigner := &ExternalSigner{ 77 client: client, 78 endpoint: endpoint, 79 } 80 // Check if reachable 81 version, err := extsigner.pingVersion() 82 if err != nil { 83 return nil, err 84 } 85 extsigner.status = fmt.Sprintf("ok [version=%v]", version) 86 return extsigner, nil 87 } 88 89 func (api *ExternalSigner) URL() accounts.URL { 90 return accounts.URL{ 91 Scheme: "extapi", 92 Path: api.endpoint, 93 } 94 } 95 96 func (api *ExternalSigner) Status() (string, error) { 97 return api.status, nil 98 } 99 100 func (api *ExternalSigner) Open(passphrase string) error { 101 return fmt.Errorf("operation not supported on external signers") 102 } 103 104 func (api *ExternalSigner) Close() error { 105 return fmt.Errorf("operation not supported on external signers") 106 } 107 108 func (api *ExternalSigner) Accounts() []accounts.Account { 109 var accnts []accounts.Account 110 res, err := api.listAccounts() 111 if err != nil { 112 log.Error("account listing failed", "error", err) 113 return accnts 114 } 115 for _, addr := range res { 116 accnts = append(accnts, accounts.Account{ 117 URL: accounts.URL{ 118 Scheme: "extapi", 119 Path: api.endpoint, 120 }, 121 Address: addr, 122 }) 123 } 124 api.cacheMu.Lock() 125 api.cache = accnts 126 api.cacheMu.Unlock() 127 return accnts 128 } 129 130 func (api *ExternalSigner) Contains(account accounts.Account) bool { 131 api.cacheMu.RLock() 132 defer api.cacheMu.RUnlock() 133 if api.cache == nil { 134 // If we haven't already fetched the accounts, it's time to do so now 135 api.cacheMu.RUnlock() 136 api.Accounts() 137 api.cacheMu.RLock() 138 } 139 for _, a := range api.cache { 140 if a.Address == account.Address && (account.URL == (accounts.URL{}) || account.URL == api.URL()) { 141 return true 142 } 143 } 144 return false 145 } 146 147 func (api *ExternalSigner) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { 148 return accounts.Account{}, fmt.Errorf("operation not supported on external signers") 149 } 150 151 func (api *ExternalSigner) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) { 152 log.Error("operation SelfDerive not supported on external signers") 153 } 154 155 func (api *ExternalSigner) signHash(account accounts.Account, hash []byte) ([]byte, error) { 156 return []byte{}, fmt.Errorf("operation not supported on external signers") 157 } 158 159 // SignData signs keccak256(data). The mimetype parameter describes the type of data being signed 160 func (api *ExternalSigner) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { 161 var res hexutil.Bytes 162 var signAddress = common.NewMixedcaseAddress(account.Address) 163 if err := api.client.Call(&res, "account_signData", 164 mimeType, 165 &signAddress, // Need to use the pointer here, because of how MarshalJSON is defined 166 hexutil.Encode(data)); err != nil { 167 return nil, err 168 } 169 // If V is on 27/28-form, convert to 0/1 for Clique 170 if mimeType == accounts.MimetypeClique && (res[64] == 27 || res[64] == 28) { 171 res[64] -= 27 // Transform V from 27/28 to 0/1 for Clique use 172 } 173 return res, nil 174 } 175 176 func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]byte, error) { 177 var signature hexutil.Bytes 178 var signAddress = common.NewMixedcaseAddress(account.Address) 179 if err := api.client.Call(&signature, "account_signData", 180 accounts.MimetypeTextPlain, 181 &signAddress, // Need to use the pointer here, because of how MarshalJSON is defined 182 hexutil.Encode(text)); err != nil { 183 return nil, err 184 } 185 if signature[64] == 27 || signature[64] == 28 { 186 // If clef is used as a backend, it may already have transformed 187 // the signature to ethereum-type signature. 188 signature[64] -= 27 // Transform V from Ethereum-legacy to 0/1 189 } 190 return signature, nil 191 } 192 193 // signTransactionResult represents the signinig result returned by clef. 194 type signTransactionResult struct { 195 Raw hexutil.Bytes `json:"raw"` 196 Tx *types.Transaction `json:"tx"` 197 } 198 199 // SignTx sends the transaction to the external signer. 200 // If chainID is nil, or tx.ChainID is zero, the chain ID will be assigned 201 // by the external signer. For non-legacy transactions, the chain ID of the 202 // transaction overrides the chainID parameter. 203 func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { 204 data := hexutil.Bytes(tx.Data()) 205 var to *common.MixedcaseAddress 206 if tx.To() != nil { 207 t := common.NewMixedcaseAddress(*tx.To()) 208 to = &t 209 } 210 args := &apitypes.SendTxArgs{ 211 Data: &data, 212 Nonce: hexutil.Uint64(tx.Nonce()), 213 Value: hexutil.Big(*tx.Value()), 214 Gas: hexutil.Uint64(tx.Gas()), 215 To: to, 216 From: common.NewMixedcaseAddress(account.Address), 217 } 218 switch tx.Type() { 219 case types.LegacyTxType, types.AccessListTxType: 220 args.GasPrice = (*hexutil.Big)(tx.GasPrice()) 221 case types.DynamicFeeTxType: 222 args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap()) 223 args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap()) 224 default: 225 return nil, fmt.Errorf("unsupported tx type %d", tx.Type()) 226 } 227 // We should request the default chain id that we're operating with 228 // (the chain we're executing on) 229 if chainID != nil && chainID.Sign() != 0 { 230 args.ChainID = (*hexutil.Big)(chainID) 231 } 232 if tx.Type() != types.LegacyTxType { 233 // However, if the user asked for a particular chain id, then we should 234 // use that instead. 235 if tx.ChainId().Sign() != 0 { 236 args.ChainID = (*hexutil.Big)(tx.ChainId()) 237 } 238 accessList := tx.AccessList() 239 args.AccessList = &accessList 240 } 241 var res signTransactionResult 242 if err := api.client.Call(&res, "account_signTransaction", args); err != nil { 243 return nil, err 244 } 245 return res.Tx, nil 246 } 247 248 func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { 249 return []byte{}, fmt.Errorf("password-operations not supported on external signers") 250 } 251 252 func (api *ExternalSigner) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { 253 return nil, fmt.Errorf("password-operations not supported on external signers") 254 } 255 func (api *ExternalSigner) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { 256 return nil, fmt.Errorf("password-operations not supported on external signers") 257 } 258 259 func (api *ExternalSigner) listAccounts() ([]common.Address, error) { 260 var res []common.Address 261 if err := api.client.Call(&res, "account_list"); err != nil { 262 return nil, err 263 } 264 return res, nil 265 } 266 267 func (api *ExternalSigner) pingVersion() (string, error) { 268 var v string 269 if err := api.client.Call(&v, "account_version"); err != nil { 270 return "", err 271 } 272 return v, nil 273 }