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