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