github.com/status-im/status-go@v1.1.0/services/web3provider/api.go (about)

     1  package web3provider
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  
     7  	"github.com/ethereum/go-ethereum/common/hexutil"
     8  	"github.com/ethereum/go-ethereum/log"
     9  	signercore "github.com/ethereum/go-ethereum/signer/core/apitypes"
    10  	"github.com/status-im/status-go/account"
    11  	"github.com/status-im/status-go/eth-node/types"
    12  	"github.com/status-im/status-go/services/typeddata"
    13  	"github.com/status-im/status-go/transactions"
    14  )
    15  
    16  const Web3SendAsyncReadOnly = "web3-send-async-read-only"
    17  const RequestAPI = "api-request"
    18  
    19  const Web3SendAsyncCallback = "web3-send-async-callback"
    20  const ResponseAPI = "api-response"
    21  const Web3ResponseError = "web3-response-error"
    22  
    23  const PermissionWeb3 = "web3"
    24  const PermissionContactCode = "contact-code"
    25  const PermissionUnknown = "unknown"
    26  
    27  const ethCoinbase = "eth_coinbase"
    28  
    29  var ErrorInvalidAPIRequest = errors.New("invalid API request")
    30  var ErrorUnknownPermission = errors.New("unknown permission")
    31  
    32  var authMethods = []string{
    33  	"eth_accounts",
    34  	"eth_coinbase",
    35  	"eth_sendTransaction",
    36  	"eth_sign",
    37  	"keycard_signTypedData",
    38  	"eth_signTypedData",
    39  	"eth_signTypedData_v3",
    40  	"personal_sign",
    41  }
    42  
    43  var signMethods = []string{
    44  	"eth_sign",
    45  	"personal_sign",
    46  	"eth_signTypedData",
    47  	"eth_signTypedData_v3",
    48  	"eth_signTypedData_v4",
    49  }
    50  
    51  var accMethods = []string{
    52  	"eth_accounts",
    53  	"eth_coinbase",
    54  }
    55  
    56  func NewAPI(s *Service) *API {
    57  	return &API{
    58  		s: s,
    59  	}
    60  }
    61  
    62  // API is class with methods available over RPC.
    63  type API struct {
    64  	s *Service
    65  }
    66  
    67  type ETHPayload struct {
    68  	ID       interface{}   `json:"id,omitempty"`
    69  	JSONRPC  string        `json:"jsonrpc"`
    70  	From     string        `json:"from"`
    71  	Method   string        `json:"method"`
    72  	Params   []interface{} `json:"params"`
    73  	Password string        `json:"password,omitempty"`
    74  	ChainID  uint64        `json:"chainId,omitempty"`
    75  }
    76  
    77  type JSONRPCResponse struct {
    78  	ID      interface{} `json:"id,omitempty"`
    79  	JSONRPC string      `json:"jsonrpc"`
    80  	Result  interface{} `json:"result"`
    81  }
    82  type Web3SendAsyncReadOnlyRequest struct {
    83  	Title     string      `json:"title,omitempty"`
    84  	MessageID interface{} `json:"messageId"`
    85  	Payload   ETHPayload  `json:"payload"`
    86  	Hostname  string      `json:"hostname"`
    87  	Address   string      `json:"address,omitempty"`
    88  }
    89  
    90  type Web3SendAsyncReadOnlyError struct {
    91  	Code    uint   `json:"code"`
    92  	Message string `json:"message,omitempty"`
    93  }
    94  
    95  type Web3SendAsyncReadOnlyResponse struct {
    96  	ProviderResponse
    97  
    98  	MessageID interface{} `json:"messageId"`
    99  	Error     interface{} `json:"error,omitempty"`
   100  	Result    interface{} `json:"result,omitempty"`
   101  }
   102  
   103  type APIRequest struct {
   104  	MessageID  interface{} `json:"messageId,omitempty"`
   105  	Address    string      `json:"address,omitempty"`
   106  	Hostname   string      `json:"hostname"`
   107  	Permission string      `json:"permission"`
   108  }
   109  
   110  type APIResponse struct {
   111  	ProviderResponse
   112  
   113  	MessageID  interface{} `json:"messageId,omitempty"`
   114  	Permission string      `json:"permission"`
   115  	Data       interface{} `json:"data,omitempty"`
   116  	IsAllowed  bool        `json:"isAllowed"`
   117  }
   118  
   119  type ProviderResponse struct {
   120  	ResponseType string `json:"type"`
   121  }
   122  
   123  func (api *API) ProcessRequest(requestType string, payload json.RawMessage) (interface{}, error) {
   124  	switch requestType {
   125  	case RequestAPI:
   126  		var request APIRequest
   127  		if err := json.Unmarshal([]byte(payload), &request); err != nil {
   128  			return nil, err
   129  		}
   130  		return api.ProcessAPIRequest(request)
   131  	case Web3SendAsyncReadOnly:
   132  		var request Web3SendAsyncReadOnlyRequest
   133  		if err := json.Unmarshal(payload, &request); err != nil {
   134  			return nil, err
   135  		}
   136  		return api.ProcessWeb3ReadOnlyRequest(request)
   137  	default:
   138  		return nil, errors.New("invalid request type")
   139  	}
   140  }
   141  
   142  func contains(item string, elems []string) bool {
   143  	for _, x := range elems {
   144  		if x == item {
   145  			return true
   146  		}
   147  	}
   148  	return false
   149  }
   150  
   151  // web3Call returns a response from a read-only eth RPC method
   152  func (api *API) web3Call(request Web3SendAsyncReadOnlyRequest) (*Web3SendAsyncReadOnlyResponse, error) {
   153  	var rpcResult interface{}
   154  	var errMsg interface{}
   155  
   156  	if request.Payload.Method == "personal_ecRecover" {
   157  		data, err := hexutil.Decode(request.Payload.Params[0].(string))
   158  		if err != nil {
   159  			return nil, err
   160  		}
   161  		sig, err := hexutil.Decode(request.Payload.Params[1].(string))
   162  		if err != nil {
   163  			return nil, err
   164  		}
   165  
   166  		addr, err := api.EcRecover(data, sig)
   167  		if err != nil {
   168  			return nil, err
   169  		}
   170  		rpcResult = JSONRPCResponse{
   171  			JSONRPC: "2.0",
   172  			ID:      request.Payload.ID,
   173  			Result:  addr.String(),
   174  		}
   175  	} else {
   176  		ethPayload, err := json.Marshal(request.Payload)
   177  		if err != nil {
   178  			return nil, err
   179  		}
   180  
   181  		response := api.s.rpcClient.CallRaw(string(ethPayload))
   182  		if response == "" {
   183  			errMsg = Web3ResponseError
   184  		}
   185  		rpcResult = json.RawMessage(response)
   186  	}
   187  
   188  	return &Web3SendAsyncReadOnlyResponse{
   189  		ProviderResponse: ProviderResponse{
   190  			ResponseType: Web3SendAsyncCallback,
   191  		},
   192  		MessageID: request.MessageID,
   193  		Error:     errMsg,
   194  		Result:    rpcResult,
   195  	}, nil
   196  }
   197  
   198  func (api *API) web3NoPermission(request Web3SendAsyncReadOnlyRequest) (*Web3SendAsyncReadOnlyResponse, error) {
   199  	return &Web3SendAsyncReadOnlyResponse{
   200  		ProviderResponse: ProviderResponse{
   201  			ResponseType: Web3SendAsyncCallback,
   202  		},
   203  		MessageID: request.MessageID,
   204  		Error: Web3SendAsyncReadOnlyError{
   205  			Code:    4100,
   206  			Message: "The requested method and/or account has not been authorized by the user.",
   207  		},
   208  	}, nil
   209  }
   210  
   211  func (api *API) web3AccResponse(request Web3SendAsyncReadOnlyRequest) (*Web3SendAsyncReadOnlyResponse, error) {
   212  	dappsAddress, err := api.s.accountsDB.GetDappsAddress()
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  
   217  	var result interface{}
   218  	if request.Payload.Method == ethCoinbase {
   219  		result = dappsAddress
   220  	} else {
   221  		result = []types.Address{dappsAddress}
   222  	}
   223  
   224  	return &Web3SendAsyncReadOnlyResponse{
   225  		ProviderResponse: ProviderResponse{
   226  			ResponseType: Web3SendAsyncCallback,
   227  		},
   228  		MessageID: request.MessageID,
   229  		Result: JSONRPCResponse{
   230  			JSONRPC: "2.0",
   231  			ID:      request.Payload.ID,
   232  			Result:  result,
   233  		},
   234  	}, nil
   235  }
   236  
   237  func (api *API) getVerifiedWalletAccount(address, password string) (*account.SelectedExtKey, error) {
   238  	exists, err := api.s.accountsDB.AddressExists(types.HexToAddress(address))
   239  	if err != nil {
   240  		log.Error("failed to query db for a given address", "address", address, "error", err)
   241  		return nil, err
   242  	}
   243  
   244  	if !exists {
   245  		log.Error("failed to get a selected account", "err", transactions.ErrInvalidTxSender)
   246  		return nil, transactions.ErrAccountDoesntExist
   247  	}
   248  
   249  	key, err := api.s.accountsManager.VerifyAccountPassword(api.s.config.KeyStoreDir, address, password)
   250  	if err != nil {
   251  		log.Error("failed to verify account", "account", address, "error", err)
   252  		return nil, err
   253  	}
   254  
   255  	return &account.SelectedExtKey{
   256  		Address:    key.Address,
   257  		AccountKey: key,
   258  	}, nil
   259  }
   260  
   261  func (api *API) web3SignatureResponse(request Web3SendAsyncReadOnlyRequest) (*Web3SendAsyncReadOnlyResponse, error) {
   262  	var err error
   263  	var signature types.HexBytes
   264  	if request.Payload.Method == "eth_signTypedData" || request.Payload.Method == "eth_signTypedData_v3" {
   265  		raw := json.RawMessage(request.Payload.Params[1].(string))
   266  		var data typeddata.TypedData
   267  		err = json.Unmarshal(raw, &data)
   268  		if err == nil {
   269  			signature, err = api.signTypedData(data, request.Payload.From, request.Payload.Password)
   270  		}
   271  	} else if request.Payload.Method == "eth_signTypedData_v4" {
   272  		signature, err = api.signTypedDataV4(request.Payload.Params[1].(signercore.TypedData), request.Payload.From, request.Payload.Password)
   273  	} else {
   274  		signature, err = api.signMessage(request.Payload.Params[0], request.Payload.From, request.Payload.Password)
   275  	}
   276  
   277  	if err != nil {
   278  		log.Error("could not sign message", "err", err)
   279  		return &Web3SendAsyncReadOnlyResponse{
   280  			ProviderResponse: ProviderResponse{
   281  				ResponseType: Web3SendAsyncCallback,
   282  			},
   283  			MessageID: request.MessageID,
   284  			Error: Web3SendAsyncReadOnlyError{
   285  				Code:    4100,
   286  				Message: err.Error(),
   287  			},
   288  		}, nil
   289  	}
   290  
   291  	return &Web3SendAsyncReadOnlyResponse{
   292  		ProviderResponse: ProviderResponse{
   293  			ResponseType: Web3SendAsyncCallback,
   294  		},
   295  		MessageID: request.MessageID,
   296  		Result: JSONRPCResponse{
   297  			JSONRPC: "2.0",
   298  			ID:      request.Payload.ID,
   299  			Result:  signature,
   300  		},
   301  	}, nil
   302  }
   303  
   304  func (api *API) ProcessWeb3ReadOnlyRequest(request Web3SendAsyncReadOnlyRequest) (*Web3SendAsyncReadOnlyResponse, error) {
   305  	hasPermission, err := api.s.permissionsDB.HasPermission(request.Hostname, request.Address, PermissionWeb3)
   306  	if err != nil {
   307  		return nil, err
   308  	}
   309  
   310  	if contains(request.Payload.Method, authMethods) && !hasPermission {
   311  		return api.web3NoPermission(request)
   312  	}
   313  
   314  	if contains(request.Payload.Method, accMethods) {
   315  		return api.web3AccResponse(request)
   316  	} else if contains(request.Payload.Method, signMethods) {
   317  		return api.web3SignatureResponse(request)
   318  	} else if request.Payload.Method == "eth_sendTransaction" {
   319  		jsonString, err := json.Marshal(request.Payload.Params[0])
   320  		if err != nil {
   321  			return nil, err
   322  		}
   323  
   324  		var trxArgs transactions.SendTxArgs
   325  		if err := json.Unmarshal(jsonString, &trxArgs); err != nil {
   326  			return nil, err
   327  		}
   328  
   329  		hash, err := api.sendTransaction(request.Payload.ChainID, trxArgs, request.Payload.Password, Web3SendAsyncReadOnly)
   330  		if err != nil {
   331  			log.Error("could not send transaction message", "err", err)
   332  			return &Web3SendAsyncReadOnlyResponse{
   333  				ProviderResponse: ProviderResponse{
   334  					ResponseType: Web3SendAsyncCallback,
   335  				},
   336  				MessageID: request.MessageID,
   337  				Error:     Web3ResponseError,
   338  			}, nil
   339  		}
   340  
   341  		return &Web3SendAsyncReadOnlyResponse{
   342  			ProviderResponse: ProviderResponse{
   343  				ResponseType: Web3SendAsyncCallback,
   344  			},
   345  			MessageID: request.MessageID,
   346  			Result: JSONRPCResponse{
   347  				JSONRPC: "2.0",
   348  				ID:      request.Payload.ID,
   349  				Result:  hash,
   350  			},
   351  		}, nil
   352  	} else {
   353  		return api.web3Call(request)
   354  	}
   355  }
   356  
   357  func (api *API) ProcessAPIRequest(request APIRequest) (*APIResponse, error) {
   358  	if request.Permission == "" {
   359  		return nil, ErrorInvalidAPIRequest
   360  	}
   361  	hasPermission, err := api.s.permissionsDB.HasPermission(request.Hostname, request.Address, request.Permission)
   362  	if err != nil {
   363  		return nil, err
   364  	}
   365  
   366  	if !hasPermission {
   367  		// Not allowed
   368  		return &APIResponse{
   369  			ProviderResponse: ProviderResponse{
   370  				ResponseType: ResponseAPI,
   371  			},
   372  			Permission: request.Permission,
   373  			MessageID:  request.MessageID,
   374  			IsAllowed:  false,
   375  		}, nil
   376  	}
   377  	var data interface{}
   378  	switch request.Permission {
   379  	case PermissionWeb3:
   380  		dappsAddress, err := api.s.accountsDB.GetDappsAddress()
   381  		if err != nil {
   382  			return nil, err
   383  		}
   384  		response := make([]interface{}, 1)
   385  		response[0] = dappsAddress
   386  		data = response
   387  	case PermissionContactCode:
   388  		pubKey, err := api.s.accountsDB.GetPublicKey()
   389  		if err != nil {
   390  			return nil, err
   391  		}
   392  		data = pubKey
   393  	default:
   394  		return nil, ErrorUnknownPermission
   395  	}
   396  	return &APIResponse{
   397  		ProviderResponse: ProviderResponse{
   398  			ResponseType: ResponseAPI,
   399  		},
   400  		Permission: request.Permission,
   401  		MessageID:  request.MessageID,
   402  		Data:       data,
   403  		IsAllowed:  true,
   404  	}, nil
   405  }