github.com/nitinawathare/ethereumassignment3@v0.0.0-20211021213010-f07344c2b868/go-ethereum/signer/core/api.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // This file is part of go-ethereum. 3 // 4 // go-ethereum is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU 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 // go-ethereum 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 General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. 16 17 package core 18 19 import ( 20 "context" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "math/big" 25 "reflect" 26 "strings" 27 28 "github.com/ethereum/go-ethereum/accounts" 29 "github.com/ethereum/go-ethereum/accounts/keystore" 30 "github.com/ethereum/go-ethereum/accounts/usbwallet" 31 "github.com/ethereum/go-ethereum/common" 32 "github.com/ethereum/go-ethereum/common/hexutil" 33 "github.com/ethereum/go-ethereum/internal/ethapi" 34 "github.com/ethereum/go-ethereum/log" 35 "github.com/ethereum/go-ethereum/rlp" 36 "github.com/ethereum/go-ethereum/signer/storage" 37 ) 38 39 const ( 40 // numberOfAccountsToDerive For hardware wallets, the number of accounts to derive 41 numberOfAccountsToDerive = 10 42 // ExternalAPIVersion -- see extapi_changelog.md 43 ExternalAPIVersion = "6.0.0" 44 // InternalAPIVersion -- see intapi_changelog.md 45 InternalAPIVersion = "6.0.0" 46 ) 47 48 // ExternalAPI defines the external API through which signing requests are made. 49 type ExternalAPI interface { 50 // List available accounts 51 List(ctx context.Context) ([]common.Address, error) 52 // New request to create a new account 53 New(ctx context.Context) (common.Address, error) 54 // SignTransaction request to sign the specified transaction 55 SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) 56 // SignData - request to sign the given data (plus prefix) 57 SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) 58 // SignTypedData - request to sign the given structured data (plus prefix) 59 SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) 60 // EcRecover - recover public key from given message and signature 61 EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) 62 // Version info about the APIs 63 Version(ctx context.Context) (string, error) 64 } 65 66 // UIClientAPI specifies what method a UI needs to implement to be able to be used as a 67 // UI for the signer 68 type UIClientAPI interface { 69 // ApproveTx prompt the user for confirmation to request to sign Transaction 70 ApproveTx(request *SignTxRequest) (SignTxResponse, error) 71 // ApproveSignData prompt the user for confirmation to request to sign data 72 ApproveSignData(request *SignDataRequest) (SignDataResponse, error) 73 // ApproveListing prompt the user for confirmation to list accounts 74 // the list of accounts to list can be modified by the UI 75 ApproveListing(request *ListRequest) (ListResponse, error) 76 // ApproveNewAccount prompt the user for confirmation to create new Account, and reveal to caller 77 ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) 78 // ShowError displays error message to user 79 ShowError(message string) 80 // ShowInfo displays info message to user 81 ShowInfo(message string) 82 // OnApprovedTx notifies the UI about a transaction having been successfully signed. 83 // This method can be used by a UI to keep track of e.g. how much has been sent to a particular recipient. 84 OnApprovedTx(tx ethapi.SignTransactionResult) 85 // OnSignerStartup is invoked when the signer boots, and tells the UI info about external API location and version 86 // information 87 OnSignerStartup(info StartupInfo) 88 // OnInputRequired is invoked when clef requires user input, for example master password or 89 // pin-code for unlocking hardware wallets 90 OnInputRequired(info UserInputRequest) (UserInputResponse, error) 91 // RegisterUIServer tells the UI to use the given UIServerAPI for ui->clef communication 92 RegisterUIServer(api *UIServerAPI) 93 } 94 95 // Validator defines the methods required to validate a transaction against some 96 // sanity defaults as well as any underlying 4byte method database. 97 // 98 // Use fourbyte.Database as an implementation. It is separated out of this package 99 // to allow pieces of the signer package to be used without having to load the 100 // 7MB embedded 4byte dump. 101 type Validator interface { 102 // ValidateTransaction does a number of checks on the supplied transaction, and 103 // returns either a list of warnings, or an error (indicating that the transaction 104 // should be immediately rejected). 105 ValidateTransaction(selector *string, tx *SendTxArgs) (*ValidationMessages, error) 106 } 107 108 // SignerAPI defines the actual implementation of ExternalAPI 109 type SignerAPI struct { 110 chainID *big.Int 111 am *accounts.Manager 112 UI UIClientAPI 113 validator Validator 114 rejectMode bool 115 credentials storage.Storage 116 } 117 118 // Metadata about a request 119 type Metadata struct { 120 Remote string `json:"remote"` 121 Local string `json:"local"` 122 Scheme string `json:"scheme"` 123 UserAgent string `json:"User-Agent"` 124 Origin string `json:"Origin"` 125 } 126 127 func StartClefAccountManager(ksLocation string, nousb, lightKDF bool) *accounts.Manager { 128 var ( 129 backends []accounts.Backend 130 n, p = keystore.StandardScryptN, keystore.StandardScryptP 131 ) 132 if lightKDF { 133 n, p = keystore.LightScryptN, keystore.LightScryptP 134 } 135 // support password based accounts 136 if len(ksLocation) > 0 { 137 backends = append(backends, keystore.NewKeyStore(ksLocation, n, p)) 138 } 139 if !nousb { 140 // Start a USB hub for Ledger hardware wallets 141 if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil { 142 log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err)) 143 } else { 144 backends = append(backends, ledgerhub) 145 log.Debug("Ledger support enabled") 146 } 147 // Start a USB hub for Trezor hardware wallets 148 if trezorhub, err := usbwallet.NewTrezorHub(); err != nil { 149 log.Warn(fmt.Sprintf("Failed to start Trezor hub, disabling: %v", err)) 150 } else { 151 backends = append(backends, trezorhub) 152 log.Debug("Trezor support enabled") 153 } 154 } 155 // Clef doesn't allow insecure http account unlock. 156 return accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: false}, backends...) 157 } 158 159 // MetadataFromContext extracts Metadata from a given context.Context 160 func MetadataFromContext(ctx context.Context) Metadata { 161 m := Metadata{"NA", "NA", "NA", "", ""} // batman 162 163 if v := ctx.Value("remote"); v != nil { 164 m.Remote = v.(string) 165 } 166 if v := ctx.Value("scheme"); v != nil { 167 m.Scheme = v.(string) 168 } 169 if v := ctx.Value("local"); v != nil { 170 m.Local = v.(string) 171 } 172 if v := ctx.Value("Origin"); v != nil { 173 m.Origin = v.(string) 174 } 175 if v := ctx.Value("User-Agent"); v != nil { 176 m.UserAgent = v.(string) 177 } 178 return m 179 } 180 181 // String implements Stringer interface 182 func (m Metadata) String() string { 183 s, err := json.Marshal(m) 184 if err == nil { 185 return string(s) 186 } 187 return err.Error() 188 } 189 190 // types for the requests/response types between signer and UI 191 type ( 192 // SignTxRequest contains info about a Transaction to sign 193 SignTxRequest struct { 194 Transaction SendTxArgs `json:"transaction"` 195 Callinfo []ValidationInfo `json:"call_info"` 196 Meta Metadata `json:"meta"` 197 } 198 // SignTxResponse result from SignTxRequest 199 SignTxResponse struct { 200 //The UI may make changes to the TX 201 Transaction SendTxArgs `json:"transaction"` 202 Approved bool `json:"approved"` 203 } 204 SignDataRequest struct { 205 ContentType string `json:"content_type"` 206 Address common.MixedcaseAddress `json:"address"` 207 Rawdata []byte `json:"raw_data"` 208 Message []*NameValueType `json:"message"` 209 Hash hexutil.Bytes `json:"hash"` 210 Meta Metadata `json:"meta"` 211 } 212 SignDataResponse struct { 213 Approved bool `json:"approved"` 214 } 215 NewAccountRequest struct { 216 Meta Metadata `json:"meta"` 217 } 218 NewAccountResponse struct { 219 Approved bool `json:"approved"` 220 } 221 ListRequest struct { 222 Accounts []accounts.Account `json:"accounts"` 223 Meta Metadata `json:"meta"` 224 } 225 ListResponse struct { 226 Accounts []accounts.Account `json:"accounts"` 227 } 228 Message struct { 229 Text string `json:"text"` 230 } 231 StartupInfo struct { 232 Info map[string]interface{} `json:"info"` 233 } 234 UserInputRequest struct { 235 Title string `json:"title"` 236 Prompt string `json:"prompt"` 237 IsPassword bool `json:"isPassword"` 238 } 239 UserInputResponse struct { 240 Text string `json:"text"` 241 } 242 ) 243 244 var ErrRequestDenied = errors.New("Request denied") 245 246 // NewSignerAPI creates a new API that can be used for Account management. 247 // ksLocation specifies the directory where to store the password protected private 248 // key that is generated when a new Account is created. 249 // noUSB disables USB support that is required to support hardware devices such as 250 // ledger and trezor. 251 func NewSignerAPI(am *accounts.Manager, chainID int64, noUSB bool, ui UIClientAPI, validator Validator, advancedMode bool, credentials storage.Storage) *SignerAPI { 252 if advancedMode { 253 log.Info("Clef is in advanced mode: will warn instead of reject") 254 } 255 signer := &SignerAPI{big.NewInt(chainID), am, ui, validator, !advancedMode, credentials} 256 if !noUSB { 257 signer.startUSBListener() 258 } 259 return signer 260 } 261 func (api *SignerAPI) openTrezor(url accounts.URL) { 262 resp, err := api.UI.OnInputRequired(UserInputRequest{ 263 Prompt: "Pin required to open Trezor wallet\n" + 264 "Look at the device for number positions\n\n" + 265 "7 | 8 | 9\n" + 266 "--+---+--\n" + 267 "4 | 5 | 6\n" + 268 "--+---+--\n" + 269 "1 | 2 | 3\n\n", 270 IsPassword: true, 271 Title: "Trezor unlock", 272 }) 273 if err != nil { 274 log.Warn("failed getting trezor pin", "err", err) 275 return 276 } 277 // We're using the URL instead of the pointer to the 278 // Wallet -- perhaps it is not actually present anymore 279 w, err := api.am.Wallet(url.String()) 280 if err != nil { 281 log.Warn("wallet unavailable", "url", url) 282 return 283 } 284 err = w.Open(resp.Text) 285 if err != nil { 286 log.Warn("failed to open wallet", "wallet", url, "err", err) 287 return 288 } 289 290 } 291 292 // startUSBListener starts a listener for USB events, for hardware wallet interaction 293 func (api *SignerAPI) startUSBListener() { 294 events := make(chan accounts.WalletEvent, 16) 295 am := api.am 296 am.Subscribe(events) 297 go func() { 298 299 // Open any wallets already attached 300 for _, wallet := range am.Wallets() { 301 if err := wallet.Open(""); err != nil { 302 log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err) 303 if err == usbwallet.ErrTrezorPINNeeded { 304 go api.openTrezor(wallet.URL()) 305 } 306 } 307 } 308 // Listen for wallet event till termination 309 for event := range events { 310 switch event.Kind { 311 case accounts.WalletArrived: 312 if err := event.Wallet.Open(""); err != nil { 313 log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err) 314 if err == usbwallet.ErrTrezorPINNeeded { 315 go api.openTrezor(event.Wallet.URL()) 316 } 317 } 318 case accounts.WalletOpened: 319 status, _ := event.Wallet.Status() 320 log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status) 321 322 derivationPath := accounts.DefaultBaseDerivationPath 323 if event.Wallet.URL().Scheme == "ledger" { 324 derivationPath = accounts.DefaultLedgerBaseDerivationPath 325 } 326 var nextPath = derivationPath 327 // Derive first N accounts, hardcoded for now 328 for i := 0; i < numberOfAccountsToDerive; i++ { 329 acc, err := event.Wallet.Derive(nextPath, true) 330 if err != nil { 331 log.Warn("account derivation failed", "error", err) 332 } else { 333 log.Info("derived account", "address", acc.Address) 334 } 335 nextPath[len(nextPath)-1]++ 336 } 337 case accounts.WalletDropped: 338 log.Info("Old wallet dropped", "url", event.Wallet.URL()) 339 event.Wallet.Close() 340 } 341 } 342 }() 343 } 344 345 // List returns the set of wallet this signer manages. Each wallet can contain 346 // multiple accounts. 347 func (api *SignerAPI) List(ctx context.Context) ([]common.Address, error) { 348 var accs []accounts.Account 349 for _, wallet := range api.am.Wallets() { 350 accs = append(accs, wallet.Accounts()...) 351 } 352 result, err := api.UI.ApproveListing(&ListRequest{Accounts: accs, Meta: MetadataFromContext(ctx)}) 353 if err != nil { 354 return nil, err 355 } 356 if result.Accounts == nil { 357 return nil, ErrRequestDenied 358 359 } 360 addresses := make([]common.Address, 0) 361 for _, acc := range result.Accounts { 362 addresses = append(addresses, acc.Address) 363 } 364 365 return addresses, nil 366 } 367 368 // New creates a new password protected Account. The private key is protected with 369 // the given password. Users are responsible to backup the private key that is stored 370 // in the keystore location thas was specified when this API was created. 371 func (api *SignerAPI) New(ctx context.Context) (common.Address, error) { 372 be := api.am.Backends(keystore.KeyStoreType) 373 if len(be) == 0 { 374 return common.Address{}, errors.New("password based accounts not supported") 375 } 376 if resp, err := api.UI.ApproveNewAccount(&NewAccountRequest{MetadataFromContext(ctx)}); err != nil { 377 return common.Address{}, err 378 } else if !resp.Approved { 379 return common.Address{}, ErrRequestDenied 380 } 381 382 // Three retries to get a valid password 383 for i := 0; i < 3; i++ { 384 resp, err := api.UI.OnInputRequired(UserInputRequest{ 385 "New account password", 386 fmt.Sprintf("Please enter a password for the new account to be created (attempt %d of 3)", i), 387 true}) 388 if err != nil { 389 log.Warn("error obtaining password", "attempt", i, "error", err) 390 continue 391 } 392 if pwErr := ValidatePasswordFormat(resp.Text); pwErr != nil { 393 api.UI.ShowError(fmt.Sprintf("Account creation attempt #%d failed due to password requirements: %v", (i + 1), pwErr)) 394 } else { 395 // No error 396 acc, err := be[0].(*keystore.KeyStore).NewAccount(resp.Text) 397 return acc.Address, err 398 } 399 } 400 // Otherwise fail, with generic error message 401 return common.Address{}, errors.New("account creation failed") 402 } 403 404 // logDiff logs the difference between the incoming (original) transaction and the one returned from the signer. 405 // it also returns 'true' if the transaction was modified, to make it possible to configure the signer not to allow 406 // UI-modifications to requests 407 func logDiff(original *SignTxRequest, new *SignTxResponse) bool { 408 modified := false 409 if f0, f1 := original.Transaction.From, new.Transaction.From; !reflect.DeepEqual(f0, f1) { 410 log.Info("Sender-account changed by UI", "was", f0, "is", f1) 411 modified = true 412 } 413 if t0, t1 := original.Transaction.To, new.Transaction.To; !reflect.DeepEqual(t0, t1) { 414 log.Info("Recipient-account changed by UI", "was", t0, "is", t1) 415 modified = true 416 } 417 if g0, g1 := original.Transaction.Gas, new.Transaction.Gas; g0 != g1 { 418 modified = true 419 log.Info("Gas changed by UI", "was", g0, "is", g1) 420 } 421 if g0, g1 := big.Int(original.Transaction.GasPrice), big.Int(new.Transaction.GasPrice); g0.Cmp(&g1) != 0 { 422 modified = true 423 log.Info("GasPrice changed by UI", "was", g0, "is", g1) 424 } 425 if v0, v1 := big.Int(original.Transaction.Value), big.Int(new.Transaction.Value); v0.Cmp(&v1) != 0 { 426 modified = true 427 log.Info("Value changed by UI", "was", v0, "is", v1) 428 } 429 if d0, d1 := original.Transaction.Data, new.Transaction.Data; d0 != d1 { 430 d0s := "" 431 d1s := "" 432 if d0 != nil { 433 d0s = hexutil.Encode(*d0) 434 } 435 if d1 != nil { 436 d1s = hexutil.Encode(*d1) 437 } 438 if d1s != d0s { 439 modified = true 440 log.Info("Data changed by UI", "was", d0s, "is", d1s) 441 } 442 } 443 if n0, n1 := original.Transaction.Nonce, new.Transaction.Nonce; n0 != n1 { 444 modified = true 445 log.Info("Nonce changed by UI", "was", n0, "is", n1) 446 } 447 return modified 448 } 449 450 func (api *SignerAPI) lookupPassword(address common.Address) string { 451 return api.credentials.Get(strings.ToLower(address.String())) 452 } 453 func (api *SignerAPI) lookupOrQueryPassword(address common.Address, title, prompt string) (string, error) { 454 if pw := api.lookupPassword(address); pw != "" { 455 return pw, nil 456 } else { 457 pwResp, err := api.UI.OnInputRequired(UserInputRequest{title, prompt, true}) 458 if err != nil { 459 log.Warn("error obtaining password", "error", err) 460 // We'll not forward the error here, in case the error contains info about the response from the UI, 461 // which could leak the password if it was malformed json or something 462 return "", errors.New("internal error") 463 } 464 return pwResp.Text, nil 465 } 466 } 467 468 // SignTransaction signs the given Transaction and returns it both as json and rlp-encoded form 469 func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) { 470 var ( 471 err error 472 result SignTxResponse 473 ) 474 msgs, err := api.validator.ValidateTransaction(methodSelector, &args) 475 if err != nil { 476 return nil, err 477 } 478 // If we are in 'rejectMode', then reject rather than show the user warnings 479 if api.rejectMode { 480 if err := msgs.getWarnings(); err != nil { 481 return nil, err 482 } 483 } 484 485 req := SignTxRequest{ 486 Transaction: args, 487 Meta: MetadataFromContext(ctx), 488 Callinfo: msgs.Messages, 489 } 490 // Process approval 491 result, err = api.UI.ApproveTx(&req) 492 if err != nil { 493 return nil, err 494 } 495 if !result.Approved { 496 return nil, ErrRequestDenied 497 } 498 // Log changes made by the UI to the signing-request 499 logDiff(&req, &result) 500 var ( 501 acc accounts.Account 502 wallet accounts.Wallet 503 ) 504 acc = accounts.Account{Address: result.Transaction.From.Address()} 505 wallet, err = api.am.Find(acc) 506 if err != nil { 507 return nil, err 508 } 509 // Convert fields into a real transaction 510 var unsignedTx = result.Transaction.toTransaction() 511 // Get the password for the transaction 512 pw, err := api.lookupOrQueryPassword(acc.Address, "Account password", 513 fmt.Sprintf("Please enter the password for account %s", acc.Address.String())) 514 if err != nil { 515 return nil, err 516 } 517 // The one to sign is the one that was returned from the UI 518 signedTx, err := wallet.SignTxWithPassphrase(acc, pw, unsignedTx, api.chainID) 519 if err != nil { 520 api.UI.ShowError(err.Error()) 521 return nil, err 522 } 523 524 rlpdata, err := rlp.EncodeToBytes(signedTx) 525 response := ethapi.SignTransactionResult{Raw: rlpdata, Tx: signedTx} 526 527 // Finally, send the signed tx to the UI 528 api.UI.OnApprovedTx(response) 529 // ...and to the external caller 530 return &response, nil 531 532 } 533 534 // Returns the external api version. This method does not require user acceptance. Available methods are 535 // available via enumeration anyway, and this info does not contain user-specific data 536 func (api *SignerAPI) Version(ctx context.Context) (string, error) { 537 return ExternalAPIVersion, nil 538 }