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 }