github.com/bearnetworkchain/go-bearnetwork@v1.10.19-0.20220604150648-d63890c2e42b/signer/core/api.go (about) 1 // Copyright 2018 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 core 18 19 import ( 20 "context" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "math/big" 25 "os" 26 "reflect" 27 28 "github.com/bearnetworkchain/go-bearnetwork/accounts" 29 "github.com/bearnetworkchain/go-bearnetwork/accounts/keystore" 30 "github.com/bearnetworkchain/go-bearnetwork/accounts/scwallet" 31 "github.com/bearnetworkchain/go-bearnetwork/accounts/usbwallet" 32 "github.com/bearnetworkchain/go-bearnetwork/common" 33 "github.com/bearnetworkchain/go-bearnetwork/common/hexutil" 34 "github.com/bearnetworkchain/go-bearnetwork/internal/ethapi" 35 "github.com/bearnetworkchain/go-bearnetwork/log" 36 "github.com/bearnetworkchain/go-bearnetwork/rpc" 37 "github.com/bearnetworkchain/go-bearnetwork/signer/core/apitypes" 38 "github.com/bearnetworkchain/go-bearnetwork/signer/storage" 39 ) 40 41 const ( 42 // numberOfAccountsToDerive For hardware wallets, the number of accounts to derive 43 numberOfAccountsToDerive = 10 44 // ExternalAPIVersion -- see extapi_changelog.md 45 ExternalAPIVersion = "6.1.0" 46 // InternalAPIVersion -- see intapi_changelog.md 47 InternalAPIVersion = "7.0.1" 48 ) 49 50 // ExternalAPI defines the external API through which signing requests are made. 51 type ExternalAPI interface { 52 // List available accounts 53 List(ctx context.Context) ([]common.Address, error) 54 // New request to create a new account 55 New(ctx context.Context) (common.Address, error) 56 // SignTransaction request to sign the specified transaction 57 SignTransaction(ctx context.Context, args apitypes.SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) 58 // SignData - request to sign the given data (plus prefix) 59 SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) 60 // SignTypedData - request to sign the given structured data (plus prefix) 61 SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data apitypes.TypedData) (hexutil.Bytes, error) 62 // EcRecover - recover public key from given message and signature 63 EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) 64 // Version info about the APIs 65 Version(ctx context.Context) (string, error) 66 // SignGnosisSafeTransaction signs/confirms a gnosis-safe multisig transaction 67 SignGnosisSafeTx(ctx context.Context, signerAddress common.MixedcaseAddress, gnosisTx GnosisSafeTx, methodSelector *string) (*GnosisSafeTx, error) 68 } 69 70 // UIClientAPI specifies what method a UI needs to implement to be able to be used as a 71 // UI for the signer 72 type UIClientAPI interface { 73 // ApproveTx prompt the user for confirmation to request to sign Transaction 74 ApproveTx(request *SignTxRequest) (SignTxResponse, error) 75 // ApproveSignData prompt the user for confirmation to request to sign data 76 ApproveSignData(request *SignDataRequest) (SignDataResponse, error) 77 // ApproveListing prompt the user for confirmation to list accounts 78 // the list of accounts to list can be modified by the UI 79 ApproveListing(request *ListRequest) (ListResponse, error) 80 // ApproveNewAccount prompt the user for confirmation to create new Account, and reveal to caller 81 ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) 82 // ShowError displays error message to user 83 ShowError(message string) 84 // ShowInfo displays info message to user 85 ShowInfo(message string) 86 // OnApprovedTx notifies the UI about a transaction having been successfully signed. 87 // This method can be used by a UI to keep track of e.g. how much has been sent to a particular recipient. 88 OnApprovedTx(tx ethapi.SignTransactionResult) 89 // OnSignerStartup is invoked when the signer boots, and tells the UI info about external API location and version 90 // information 91 OnSignerStartup(info StartupInfo) 92 // OnInputRequired is invoked when clef requires user input, for example master password or 93 // pin-code for unlocking hardware wallets 94 OnInputRequired(info UserInputRequest) (UserInputResponse, error) 95 // RegisterUIServer tells the UI to use the given UIServerAPI for ui->clef communication 96 RegisterUIServer(api *UIServerAPI) 97 } 98 99 // Validator defines the methods required to validate a transaction against some 100 // sanity defaults as well as any underlying 4byte method database. 101 // 102 // Use fourbyte.Database as an implementation. It is separated out of this package 103 // to allow pieces of the signer package to be used without having to load the 104 // 7MB embedded 4byte dump. 105 type Validator interface { 106 // ValidateTransaction does a number of checks on the supplied transaction, and 107 // returns either a list of warnings, or an error (indicating that the transaction 108 // should be immediately rejected). 109 ValidateTransaction(selector *string, tx *apitypes.SendTxArgs) (*apitypes.ValidationMessages, error) 110 } 111 112 // SignerAPI defines the actual implementation of ExternalAPI 113 type SignerAPI struct { 114 chainID *big.Int 115 am *accounts.Manager 116 UI UIClientAPI 117 validator Validator 118 rejectMode bool 119 credentials storage.Storage 120 } 121 122 // Metadata about a request 123 type Metadata struct { 124 Remote string `json:"remote"` 125 Local string `json:"local"` 126 Scheme string `json:"scheme"` 127 UserAgent string `json:"User-Agent"` 128 Origin string `json:"Origin"` 129 } 130 131 func StartClefAccountManager(ksLocation string, nousb, lightKDF bool, scpath string) *accounts.Manager { 132 var ( 133 backends []accounts.Backend 134 n, p = keystore.StandardScryptN, keystore.StandardScryptP 135 ) 136 if lightKDF { 137 n, p = keystore.LightScryptN, keystore.LightScryptP 138 } 139 // support password based accounts 140 if len(ksLocation) > 0 { 141 backends = append(backends, keystore.NewKeyStore(ksLocation, n, p)) 142 } 143 if !nousb { 144 // Start a USB hub for Ledger hardware wallets 145 if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil { 146 log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err)) 147 } else { 148 backends = append(backends, ledgerhub) 149 log.Debug("Ledger support enabled") 150 } 151 // Start a USB hub for Trezor hardware wallets (HID version) 152 if trezorhub, err := usbwallet.NewTrezorHubWithHID(); err != nil { 153 log.Warn(fmt.Sprintf("Failed to start HID Trezor hub, disabling: %v", err)) 154 } else { 155 backends = append(backends, trezorhub) 156 log.Debug("Trezor support enabled via HID") 157 } 158 // Start a USB hub for Trezor hardware wallets (WebUSB version) 159 if trezorhub, err := usbwallet.NewTrezorHubWithWebUSB(); err != nil { 160 log.Warn(fmt.Sprintf("Failed to start WebUSB Trezor hub, disabling: %v", err)) 161 } else { 162 backends = append(backends, trezorhub) 163 log.Debug("Trezor support enabled via WebUSB") 164 } 165 } 166 167 // Start a smart card hub 168 if len(scpath) > 0 { 169 // Sanity check that the smartcard path is valid 170 fi, err := os.Stat(scpath) 171 if err != nil { 172 log.Info("Smartcard socket file missing, disabling", "err", err) 173 } else { 174 if fi.Mode()&os.ModeType != os.ModeSocket { 175 log.Error("Invalid smartcard socket file type", "path", scpath, "type", fi.Mode().String()) 176 } else { 177 if schub, err := scwallet.NewHub(scpath, scwallet.Scheme, ksLocation); err != nil { 178 log.Warn(fmt.Sprintf("Failed to start smart card hub, disabling: %v", err)) 179 } else { 180 backends = append(backends, schub) 181 } 182 } 183 } 184 } 185 186 // Clef doesn't allow insecure http account unlock. 187 return accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: false}, backends...) 188 } 189 190 // MetadataFromContext extracts Metadata from a given context.Context 191 func MetadataFromContext(ctx context.Context) Metadata { 192 info := rpc.PeerInfoFromContext(ctx) 193 194 m := Metadata{"NA", "NA", "NA", "", ""} // batman 195 196 if info.Transport != "" { 197 if info.Transport == "http" { 198 m.Scheme = info.HTTP.Version 199 } 200 m.Scheme = info.Transport 201 } 202 if info.RemoteAddr != "" { 203 m.Remote = info.RemoteAddr 204 } 205 if info.HTTP.Host != "" { 206 m.Local = info.HTTP.Host 207 } 208 m.Origin = info.HTTP.Origin 209 m.UserAgent = info.HTTP.UserAgent 210 return m 211 } 212 213 // String implements Stringer interface 214 func (m Metadata) String() string { 215 s, err := json.Marshal(m) 216 if err == nil { 217 return string(s) 218 } 219 return err.Error() 220 } 221 222 // types for the requests/response types between signer and UI 223 type ( 224 // SignTxRequest contains info about a Transaction to sign 225 SignTxRequest struct { 226 Transaction apitypes.SendTxArgs `json:"transaction"` 227 Callinfo []apitypes.ValidationInfo `json:"call_info"` 228 Meta Metadata `json:"meta"` 229 } 230 // SignTxResponse result from SignTxRequest 231 SignTxResponse struct { 232 //The UI may make changes to the TX 233 Transaction apitypes.SendTxArgs `json:"transaction"` 234 Approved bool `json:"approved"` 235 } 236 SignDataRequest struct { 237 ContentType string `json:"content_type"` 238 Address common.MixedcaseAddress `json:"address"` 239 Rawdata []byte `json:"raw_data"` 240 Messages []*apitypes.NameValueType `json:"messages"` 241 Callinfo []apitypes.ValidationInfo `json:"call_info"` 242 Hash hexutil.Bytes `json:"hash"` 243 Meta Metadata `json:"meta"` 244 } 245 SignDataResponse struct { 246 Approved bool `json:"approved"` 247 } 248 NewAccountRequest struct { 249 Meta Metadata `json:"meta"` 250 } 251 NewAccountResponse struct { 252 Approved bool `json:"approved"` 253 } 254 ListRequest struct { 255 Accounts []accounts.Account `json:"accounts"` 256 Meta Metadata `json:"meta"` 257 } 258 ListResponse struct { 259 Accounts []accounts.Account `json:"accounts"` 260 } 261 Message struct { 262 Text string `json:"text"` 263 } 264 StartupInfo struct { 265 Info map[string]interface{} `json:"info"` 266 } 267 UserInputRequest struct { 268 Title string `json:"title"` 269 Prompt string `json:"prompt"` 270 IsPassword bool `json:"isPassword"` 271 } 272 UserInputResponse struct { 273 Text string `json:"text"` 274 } 275 ) 276 277 var ErrRequestDenied = errors.New("request denied") 278 279 // NewSignerAPI creates a new API that can be used for Account management. 280 // ksLocation specifies the directory where to store the password protected private 281 // key that is generated when a new Account is created. 282 // noUSB disables USB support that is required to support hardware devices such as 283 // ledger and trezor. 284 func NewSignerAPI(am *accounts.Manager, chainID int64, noUSB bool, ui UIClientAPI, validator Validator, advancedMode bool, credentials storage.Storage) *SignerAPI { 285 if advancedMode { 286 log.Info("Clef is in advanced mode: will warn instead of reject") 287 } 288 signer := &SignerAPI{big.NewInt(chainID), am, ui, validator, !advancedMode, credentials} 289 if !noUSB { 290 signer.startUSBListener() 291 } 292 return signer 293 } 294 func (api *SignerAPI) openTrezor(url accounts.URL) { 295 resp, err := api.UI.OnInputRequired(UserInputRequest{ 296 Prompt: "Pin required to open Trezor wallet\n" + 297 "Look at the device for number positions\n\n" + 298 "7 | 8 | 9\n" + 299 "--+---+--\n" + 300 "4 | 5 | 6\n" + 301 "--+---+--\n" + 302 "1 | 2 | 3\n\n", 303 IsPassword: true, 304 Title: "Trezor unlock", 305 }) 306 if err != nil { 307 log.Warn("failed getting trezor pin", "err", err) 308 return 309 } 310 // We're using the URL instead of the pointer to the 311 // Wallet -- perhaps it is not actually present anymore 312 w, err := api.am.Wallet(url.String()) 313 if err != nil { 314 log.Warn("wallet unavailable", "url", url) 315 return 316 } 317 err = w.Open(resp.Text) 318 if err != nil { 319 log.Warn("failed to open wallet", "wallet", url, "err", err) 320 return 321 } 322 323 } 324 325 // startUSBListener starts a listener for USB events, for hardware wallet interaction 326 func (api *SignerAPI) startUSBListener() { 327 eventCh := make(chan accounts.WalletEvent, 16) 328 am := api.am 329 am.Subscribe(eventCh) 330 // Open any wallets already attached 331 for _, wallet := range am.Wallets() { 332 if err := wallet.Open(""); err != nil { 333 log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err) 334 if err == usbwallet.ErrTrezorPINNeeded { 335 go api.openTrezor(wallet.URL()) 336 } 337 } 338 } 339 go api.derivationLoop(eventCh) 340 } 341 342 // derivationLoop listens for wallet events 343 func (api *SignerAPI) derivationLoop(events chan accounts.WalletEvent) { 344 // Listen for wallet event till termination 345 for event := range events { 346 switch event.Kind { 347 case accounts.WalletArrived: 348 if err := event.Wallet.Open(""); err != nil { 349 log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err) 350 if err == usbwallet.ErrTrezorPINNeeded { 351 go api.openTrezor(event.Wallet.URL()) 352 } 353 } 354 case accounts.WalletOpened: 355 status, _ := event.Wallet.Status() 356 log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status) 357 var derive = func(limit int, next func() accounts.DerivationPath) { 358 // Derive first N accounts, hardcoded for now 359 for i := 0; i < limit; i++ { 360 path := next() 361 if acc, err := event.Wallet.Derive(path, true); err != nil { 362 log.Warn("Account derivation failed", "error", err) 363 } else { 364 log.Info("Derived account", "address", acc.Address, "path", path) 365 } 366 } 367 } 368 log.Info("Deriving default paths") 369 derive(numberOfAccountsToDerive, accounts.DefaultIterator(accounts.DefaultBaseDerivationPath)) 370 if event.Wallet.URL().Scheme == "ledger" { 371 log.Info("Deriving ledger legacy paths") 372 derive(numberOfAccountsToDerive, accounts.DefaultIterator(accounts.LegacyLedgerBaseDerivationPath)) 373 log.Info("Deriving ledger live paths") 374 // For ledger live, since it's based off the same (DefaultBaseDerivationPath) 375 // as one we've already used, we need to step it forward one step to avoid 376 // hitting the same path again 377 nextFn := accounts.LedgerLiveIterator(accounts.DefaultBaseDerivationPath) 378 nextFn() 379 derive(numberOfAccountsToDerive, nextFn) 380 } 381 case accounts.WalletDropped: 382 log.Info("Old wallet dropped", "url", event.Wallet.URL()) 383 event.Wallet.Close() 384 } 385 } 386 } 387 388 // List returns the set of wallet this signer manages. Each wallet can contain 389 // multiple accounts. 390 func (api *SignerAPI) List(ctx context.Context) ([]common.Address, error) { 391 var accs = make([]accounts.Account, 0) 392 // accs is initialized as empty list, not nil. We use 'nil' to signal 393 // rejection, as opposed to an empty list. 394 for _, wallet := range api.am.Wallets() { 395 accs = append(accs, wallet.Accounts()...) 396 } 397 result, err := api.UI.ApproveListing(&ListRequest{Accounts: accs, Meta: MetadataFromContext(ctx)}) 398 if err != nil { 399 return nil, err 400 } 401 if result.Accounts == nil { 402 return nil, ErrRequestDenied 403 } 404 addresses := make([]common.Address, 0) 405 for _, acc := range result.Accounts { 406 addresses = append(addresses, acc.Address) 407 } 408 return addresses, nil 409 } 410 411 // New creates a new password protected Account. The private key is protected with 412 // the given password. Users are responsible to backup the private key that is stored 413 // in the keystore location thas was specified when this API was created. 414 func (api *SignerAPI) New(ctx context.Context) (common.Address, error) { 415 if be := api.am.Backends(keystore.KeyStoreType); len(be) == 0 { 416 return common.Address{}, errors.New("password based accounts not supported") 417 } 418 if resp, err := api.UI.ApproveNewAccount(&NewAccountRequest{MetadataFromContext(ctx)}); err != nil { 419 return common.Address{}, err 420 } else if !resp.Approved { 421 return common.Address{}, ErrRequestDenied 422 } 423 return api.newAccount() 424 } 425 426 // newAccount is the internal method to create a new account. It should be used 427 // _after_ user-approval has been obtained 428 func (api *SignerAPI) newAccount() (common.Address, error) { 429 be := api.am.Backends(keystore.KeyStoreType) 430 if len(be) == 0 { 431 return common.Address{}, errors.New("password based accounts not supported") 432 } 433 // Three retries to get a valid password 434 for i := 0; i < 3; i++ { 435 resp, err := api.UI.OnInputRequired(UserInputRequest{ 436 "New account password", 437 fmt.Sprintf("Please enter a password for the new account to be created (attempt %d of 3)", i), 438 true}) 439 if err != nil { 440 log.Warn("error obtaining password", "attempt", i, "error", err) 441 continue 442 } 443 if pwErr := ValidatePasswordFormat(resp.Text); pwErr != nil { 444 api.UI.ShowError(fmt.Sprintf("Account creation attempt #%d failed due to password requirements: %v", i+1, pwErr)) 445 } else { 446 // No error 447 acc, err := be[0].(*keystore.KeyStore).NewAccount(resp.Text) 448 log.Info("Your new key was generated", "address", acc.Address) 449 log.Warn("Please backup your key file!", "path", acc.URL.Path) 450 log.Warn("Please remember your password!") 451 return acc.Address, err 452 } 453 } 454 // Otherwise fail, with generic error message 455 return common.Address{}, errors.New("account creation failed") 456 } 457 458 // logDiff logs the difference between the incoming (original) transaction and the one returned from the signer. 459 // it also returns 'true' if the transaction was modified, to make it possible to configure the signer not to allow 460 // UI-modifications to requests 461 func logDiff(original *SignTxRequest, new *SignTxResponse) bool { 462 var intPtrModified = func(a, b *hexutil.Big) bool { 463 aBig := (*big.Int)(a) 464 bBig := (*big.Int)(b) 465 if aBig != nil && bBig != nil { 466 return aBig.Cmp(bBig) != 0 467 } 468 // One or both of them are nil 469 return a != b 470 } 471 472 modified := false 473 if f0, f1 := original.Transaction.From, new.Transaction.From; !reflect.DeepEqual(f0, f1) { 474 log.Info("Sender-account changed by UI", "was", f0, "is", f1) 475 modified = true 476 } 477 if t0, t1 := original.Transaction.To, new.Transaction.To; !reflect.DeepEqual(t0, t1) { 478 log.Info("Recipient-account changed by UI", "was", t0, "is", t1) 479 modified = true 480 } 481 if g0, g1 := original.Transaction.Gas, new.Transaction.Gas; g0 != g1 { 482 modified = true 483 log.Info("Gas changed by UI", "was", g0, "is", g1) 484 } 485 if a, b := original.Transaction.GasPrice, new.Transaction.GasPrice; intPtrModified(a, b) { 486 log.Info("GasPrice changed by UI", "was", a, "is", b) 487 modified = true 488 } 489 if a, b := original.Transaction.MaxPriorityFeePerGas, new.Transaction.MaxPriorityFeePerGas; intPtrModified(a, b) { 490 log.Info("maxPriorityFeePerGas changed by UI", "was", a, "is", b) 491 modified = true 492 } 493 if a, b := original.Transaction.MaxFeePerGas, new.Transaction.MaxFeePerGas; intPtrModified(a, b) { 494 log.Info("maxFeePerGas changed by UI", "was", a, "is", b) 495 modified = true 496 } 497 if v0, v1 := big.Int(original.Transaction.Value), big.Int(new.Transaction.Value); v0.Cmp(&v1) != 0 { 498 modified = true 499 log.Info("Value changed by UI", "was", v0, "is", v1) 500 } 501 if d0, d1 := original.Transaction.Data, new.Transaction.Data; d0 != d1 { 502 d0s := "" 503 d1s := "" 504 if d0 != nil { 505 d0s = hexutil.Encode(*d0) 506 } 507 if d1 != nil { 508 d1s = hexutil.Encode(*d1) 509 } 510 if d1s != d0s { 511 modified = true 512 log.Info("Data changed by UI", "was", d0s, "is", d1s) 513 } 514 } 515 if n0, n1 := original.Transaction.Nonce, new.Transaction.Nonce; n0 != n1 { 516 modified = true 517 log.Info("Nonce changed by UI", "was", n0, "is", n1) 518 } 519 return modified 520 } 521 522 func (api *SignerAPI) lookupPassword(address common.Address) (string, error) { 523 return api.credentials.Get(address.Hex()) 524 } 525 526 func (api *SignerAPI) lookupOrQueryPassword(address common.Address, title, prompt string) (string, error) { 527 // Look up the password and return if available 528 if pw, err := api.lookupPassword(address); err == nil { 529 return pw, nil 530 } 531 // Password unavailable, request it from the user 532 pwResp, err := api.UI.OnInputRequired(UserInputRequest{title, prompt, true}) 533 if err != nil { 534 log.Warn("error obtaining password", "error", err) 535 // We'll not forward the error here, in case the error contains info about the response from the UI, 536 // which could leak the password if it was malformed json or something 537 return "", errors.New("internal error") 538 } 539 return pwResp.Text, nil 540 } 541 542 // SignTransaction signs the given Transaction and returns it both as json and rlp-encoded form 543 func (api *SignerAPI) SignTransaction(ctx context.Context, args apitypes.SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) { 544 var ( 545 err error 546 result SignTxResponse 547 ) 548 msgs, err := api.validator.ValidateTransaction(methodSelector, &args) 549 if err != nil { 550 return nil, err 551 } 552 // If we are in 'rejectMode', then reject rather than show the user warnings 553 if api.rejectMode { 554 if err := msgs.GetWarnings(); err != nil { 555 return nil, err 556 } 557 } 558 if args.ChainID != nil { 559 requestedChainId := (*big.Int)(args.ChainID) 560 if api.chainID.Cmp(requestedChainId) != 0 { 561 log.Error("Signing request with wrong chain id", "requested", requestedChainId, "configured", api.chainID) 562 return nil, fmt.Errorf("requested chainid %d does not match the configuration of the signer", 563 requestedChainId) 564 } 565 } 566 req := SignTxRequest{ 567 Transaction: args, 568 Meta: MetadataFromContext(ctx), 569 Callinfo: msgs.Messages, 570 } 571 // Process approval 572 result, err = api.UI.ApproveTx(&req) 573 if err != nil { 574 return nil, err 575 } 576 if !result.Approved { 577 return nil, ErrRequestDenied 578 } 579 // Log changes made by the UI to the signing-request 580 logDiff(&req, &result) 581 var ( 582 acc accounts.Account 583 wallet accounts.Wallet 584 ) 585 acc = accounts.Account{Address: result.Transaction.From.Address()} 586 wallet, err = api.am.Find(acc) 587 if err != nil { 588 return nil, err 589 } 590 // Convert fields into a real transaction 591 var unsignedTx = result.Transaction.ToTransaction() 592 // Get the password for the transaction 593 pw, err := api.lookupOrQueryPassword(acc.Address, "Account password", 594 fmt.Sprintf("Please enter the password for account %s", acc.Address.String())) 595 if err != nil { 596 return nil, err 597 } 598 // The one to sign is the one that was returned from the UI 599 signedTx, err := wallet.SignTxWithPassphrase(acc, pw, unsignedTx, api.chainID) 600 if err != nil { 601 api.UI.ShowError(err.Error()) 602 return nil, err 603 } 604 605 data, err := signedTx.MarshalBinary() 606 if err != nil { 607 return nil, err 608 } 609 response := ethapi.SignTransactionResult{Raw: data, Tx: signedTx} 610 611 // Finally, send the signed tx to the UI 612 api.UI.OnApprovedTx(response) 613 // ...and to the external caller 614 return &response, nil 615 616 } 617 618 func (api *SignerAPI) SignGnosisSafeTx(ctx context.Context, signerAddress common.MixedcaseAddress, gnosisTx GnosisSafeTx, methodSelector *string) (*GnosisSafeTx, error) { 619 // Do the usual validations, but on the last-stage transaction 620 args := gnosisTx.ArgsForValidation() 621 msgs, err := api.validator.ValidateTransaction(methodSelector, args) 622 if err != nil { 623 return nil, err 624 } 625 // If we are in 'rejectMode', then reject rather than show the user warnings 626 if api.rejectMode { 627 if err := msgs.GetWarnings(); err != nil { 628 return nil, err 629 } 630 } 631 typedData := gnosisTx.ToTypedData() 632 signature, preimage, err := api.signTypedData(ctx, signerAddress, typedData, msgs) 633 if err != nil { 634 return nil, err 635 } 636 checkSummedSender, _ := common.NewMixedcaseAddressFromString(signerAddress.Address().Hex()) 637 638 gnosisTx.Signature = signature 639 gnosisTx.SafeTxHash = common.BytesToHash(preimage) 640 gnosisTx.Sender = *checkSummedSender // Must be checksumed to be accepted by relay 641 642 return &gnosisTx, nil 643 } 644 645 // Returns the external api version. This method does not require user acceptance. Available methods are 646 // available via enumeration anyway, and this info does not contain user-specific data 647 func (api *SignerAPI) Version(ctx context.Context) (string, error) { 648 return ExternalAPIVersion, nil 649 }