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