405
vendor/github.com/status-im/status-go/services/web3provider/api.go
generated
vendored
Normal file
405
vendor/github.com/status-im/status-go/services/web3provider/api.go
generated
vendored
Normal file
@@ -0,0 +1,405 @@
|
||||
package web3provider
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
signercore "github.com/ethereum/go-ethereum/signer/core/apitypes"
|
||||
"github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/services/typeddata"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
)
|
||||
|
||||
const Web3SendAsyncReadOnly = "web3-send-async-read-only"
|
||||
const RequestAPI = "api-request"
|
||||
|
||||
const Web3SendAsyncCallback = "web3-send-async-callback"
|
||||
const ResponseAPI = "api-response"
|
||||
const Web3ResponseError = "web3-response-error"
|
||||
|
||||
const PermissionWeb3 = "web3"
|
||||
const PermissionContactCode = "contact-code"
|
||||
const PermissionUnknown = "unknown"
|
||||
|
||||
const ethCoinbase = "eth_coinbase"
|
||||
|
||||
var ErrorInvalidAPIRequest = errors.New("invalid API request")
|
||||
var ErrorUnknownPermission = errors.New("unknown permission")
|
||||
|
||||
var authMethods = []string{
|
||||
"eth_accounts",
|
||||
"eth_coinbase",
|
||||
"eth_sendTransaction",
|
||||
"eth_sign",
|
||||
"keycard_signTypedData",
|
||||
"eth_signTypedData",
|
||||
"eth_signTypedData_v3",
|
||||
"personal_sign",
|
||||
}
|
||||
|
||||
var signMethods = []string{
|
||||
"eth_sign",
|
||||
"personal_sign",
|
||||
"eth_signTypedData",
|
||||
"eth_signTypedData_v3",
|
||||
"eth_signTypedData_v4",
|
||||
}
|
||||
|
||||
var accMethods = []string{
|
||||
"eth_accounts",
|
||||
"eth_coinbase",
|
||||
}
|
||||
|
||||
func NewAPI(s *Service) *API {
|
||||
return &API{
|
||||
s: s,
|
||||
}
|
||||
}
|
||||
|
||||
// API is class with methods available over RPC.
|
||||
type API struct {
|
||||
s *Service
|
||||
}
|
||||
|
||||
type ETHPayload struct {
|
||||
ID interface{} `json:"id,omitempty"`
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
From string `json:"from"`
|
||||
Method string `json:"method"`
|
||||
Params []interface{} `json:"params"`
|
||||
Password string `json:"password,omitempty"`
|
||||
ChainID uint64 `json:"chainId,omitempty"`
|
||||
}
|
||||
|
||||
type JSONRPCResponse struct {
|
||||
ID interface{} `json:"id,omitempty"`
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
Result interface{} `json:"result"`
|
||||
}
|
||||
type Web3SendAsyncReadOnlyRequest struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
MessageID interface{} `json:"messageId"`
|
||||
Payload ETHPayload `json:"payload"`
|
||||
Hostname string `json:"hostname"`
|
||||
Address string `json:"address,omitempty"`
|
||||
}
|
||||
|
||||
type Web3SendAsyncReadOnlyError struct {
|
||||
Code uint `json:"code"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
type Web3SendAsyncReadOnlyResponse struct {
|
||||
ProviderResponse
|
||||
|
||||
MessageID interface{} `json:"messageId"`
|
||||
Error interface{} `json:"error,omitempty"`
|
||||
Result interface{} `json:"result,omitempty"`
|
||||
}
|
||||
|
||||
type APIRequest struct {
|
||||
MessageID interface{} `json:"messageId,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
Hostname string `json:"hostname"`
|
||||
Permission string `json:"permission"`
|
||||
}
|
||||
|
||||
type APIResponse struct {
|
||||
ProviderResponse
|
||||
|
||||
MessageID interface{} `json:"messageId,omitempty"`
|
||||
Permission string `json:"permission"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
IsAllowed bool `json:"isAllowed"`
|
||||
}
|
||||
|
||||
type ProviderResponse struct {
|
||||
ResponseType string `json:"type"`
|
||||
}
|
||||
|
||||
func (api *API) ProcessRequest(requestType string, payload json.RawMessage) (interface{}, error) {
|
||||
switch requestType {
|
||||
case RequestAPI:
|
||||
var request APIRequest
|
||||
if err := json.Unmarshal([]byte(payload), &request); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return api.ProcessAPIRequest(request)
|
||||
case Web3SendAsyncReadOnly:
|
||||
var request Web3SendAsyncReadOnlyRequest
|
||||
if err := json.Unmarshal(payload, &request); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return api.ProcessWeb3ReadOnlyRequest(request)
|
||||
default:
|
||||
return nil, errors.New("invalid request type")
|
||||
}
|
||||
}
|
||||
|
||||
func contains(item string, elems []string) bool {
|
||||
for _, x := range elems {
|
||||
if x == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// web3Call returns a response from a read-only eth RPC method
|
||||
func (api *API) web3Call(request Web3SendAsyncReadOnlyRequest) (*Web3SendAsyncReadOnlyResponse, error) {
|
||||
var rpcResult interface{}
|
||||
var errMsg interface{}
|
||||
|
||||
if request.Payload.Method == "personal_ecRecover" {
|
||||
data, err := hexutil.Decode(request.Payload.Params[0].(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig, err := hexutil.Decode(request.Payload.Params[1].(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr, err := api.EcRecover(data, sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rpcResult = JSONRPCResponse{
|
||||
JSONRPC: "2.0",
|
||||
ID: request.Payload.ID,
|
||||
Result: addr.String(),
|
||||
}
|
||||
} else {
|
||||
ethPayload, err := json.Marshal(request.Payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := api.s.rpcClient.CallRaw(string(ethPayload))
|
||||
if response == "" {
|
||||
errMsg = Web3ResponseError
|
||||
}
|
||||
rpcResult = json.RawMessage(response)
|
||||
}
|
||||
|
||||
return &Web3SendAsyncReadOnlyResponse{
|
||||
ProviderResponse: ProviderResponse{
|
||||
ResponseType: Web3SendAsyncCallback,
|
||||
},
|
||||
MessageID: request.MessageID,
|
||||
Error: errMsg,
|
||||
Result: rpcResult,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *API) web3NoPermission(request Web3SendAsyncReadOnlyRequest) (*Web3SendAsyncReadOnlyResponse, error) {
|
||||
return &Web3SendAsyncReadOnlyResponse{
|
||||
ProviderResponse: ProviderResponse{
|
||||
ResponseType: Web3SendAsyncCallback,
|
||||
},
|
||||
MessageID: request.MessageID,
|
||||
Error: Web3SendAsyncReadOnlyError{
|
||||
Code: 4100,
|
||||
Message: "The requested method and/or account has not been authorized by the user.",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *API) web3AccResponse(request Web3SendAsyncReadOnlyRequest) (*Web3SendAsyncReadOnlyResponse, error) {
|
||||
dappsAddress, err := api.s.accountsDB.GetDappsAddress()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result interface{}
|
||||
if request.Payload.Method == ethCoinbase {
|
||||
result = dappsAddress
|
||||
} else {
|
||||
result = []types.Address{dappsAddress}
|
||||
}
|
||||
|
||||
return &Web3SendAsyncReadOnlyResponse{
|
||||
ProviderResponse: ProviderResponse{
|
||||
ResponseType: Web3SendAsyncCallback,
|
||||
},
|
||||
MessageID: request.MessageID,
|
||||
Result: JSONRPCResponse{
|
||||
JSONRPC: "2.0",
|
||||
ID: request.Payload.ID,
|
||||
Result: result,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *API) getVerifiedWalletAccount(address, password string) (*account.SelectedExtKey, error) {
|
||||
exists, err := api.s.accountsDB.AddressExists(types.HexToAddress(address))
|
||||
if err != nil {
|
||||
log.Error("failed to query db for a given address", "address", address, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
log.Error("failed to get a selected account", "err", transactions.ErrInvalidTxSender)
|
||||
return nil, transactions.ErrAccountDoesntExist
|
||||
}
|
||||
|
||||
key, err := api.s.accountsManager.VerifyAccountPassword(api.s.config.KeyStoreDir, address, password)
|
||||
if err != nil {
|
||||
log.Error("failed to verify account", "account", address, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &account.SelectedExtKey{
|
||||
Address: key.Address,
|
||||
AccountKey: key,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *API) web3SignatureResponse(request Web3SendAsyncReadOnlyRequest) (*Web3SendAsyncReadOnlyResponse, error) {
|
||||
var err error
|
||||
var signature types.HexBytes
|
||||
if request.Payload.Method == "eth_signTypedData" || request.Payload.Method == "eth_signTypedData_v3" {
|
||||
raw := json.RawMessage(request.Payload.Params[1].(string))
|
||||
var data typeddata.TypedData
|
||||
err = json.Unmarshal(raw, &data)
|
||||
if err == nil {
|
||||
signature, err = api.signTypedData(data, request.Payload.From, request.Payload.Password)
|
||||
}
|
||||
} else if request.Payload.Method == "eth_signTypedData_v4" {
|
||||
signature, err = api.signTypedDataV4(request.Payload.Params[1].(signercore.TypedData), request.Payload.From, request.Payload.Password)
|
||||
} else {
|
||||
signature, err = api.signMessage(request.Payload.Params[0], request.Payload.From, request.Payload.Password)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Error("could not sign message", "err", err)
|
||||
return &Web3SendAsyncReadOnlyResponse{
|
||||
ProviderResponse: ProviderResponse{
|
||||
ResponseType: Web3SendAsyncCallback,
|
||||
},
|
||||
MessageID: request.MessageID,
|
||||
Error: Web3SendAsyncReadOnlyError{
|
||||
Code: 4100,
|
||||
Message: err.Error(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &Web3SendAsyncReadOnlyResponse{
|
||||
ProviderResponse: ProviderResponse{
|
||||
ResponseType: Web3SendAsyncCallback,
|
||||
},
|
||||
MessageID: request.MessageID,
|
||||
Result: JSONRPCResponse{
|
||||
JSONRPC: "2.0",
|
||||
ID: request.Payload.ID,
|
||||
Result: signature,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *API) ProcessWeb3ReadOnlyRequest(request Web3SendAsyncReadOnlyRequest) (*Web3SendAsyncReadOnlyResponse, error) {
|
||||
hasPermission, err := api.s.permissionsDB.HasPermission(request.Hostname, request.Address, PermissionWeb3)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if contains(request.Payload.Method, authMethods) && !hasPermission {
|
||||
return api.web3NoPermission(request)
|
||||
}
|
||||
|
||||
if contains(request.Payload.Method, accMethods) {
|
||||
return api.web3AccResponse(request)
|
||||
} else if contains(request.Payload.Method, signMethods) {
|
||||
return api.web3SignatureResponse(request)
|
||||
} else if request.Payload.Method == "eth_sendTransaction" {
|
||||
jsonString, err := json.Marshal(request.Payload.Params[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var trxArgs transactions.SendTxArgs
|
||||
if err := json.Unmarshal(jsonString, &trxArgs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hash, err := api.sendTransaction(request.Payload.ChainID, trxArgs, request.Payload.Password, Web3SendAsyncReadOnly)
|
||||
if err != nil {
|
||||
log.Error("could not send transaction message", "err", err)
|
||||
return &Web3SendAsyncReadOnlyResponse{
|
||||
ProviderResponse: ProviderResponse{
|
||||
ResponseType: Web3SendAsyncCallback,
|
||||
},
|
||||
MessageID: request.MessageID,
|
||||
Error: Web3ResponseError,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &Web3SendAsyncReadOnlyResponse{
|
||||
ProviderResponse: ProviderResponse{
|
||||
ResponseType: Web3SendAsyncCallback,
|
||||
},
|
||||
MessageID: request.MessageID,
|
||||
Result: JSONRPCResponse{
|
||||
JSONRPC: "2.0",
|
||||
ID: request.Payload.ID,
|
||||
Result: hash,
|
||||
},
|
||||
}, nil
|
||||
} else {
|
||||
return api.web3Call(request)
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) ProcessAPIRequest(request APIRequest) (*APIResponse, error) {
|
||||
if request.Permission == "" {
|
||||
return nil, ErrorInvalidAPIRequest
|
||||
}
|
||||
hasPermission, err := api.s.permissionsDB.HasPermission(request.Hostname, request.Address, request.Permission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !hasPermission {
|
||||
// Not allowed
|
||||
return &APIResponse{
|
||||
ProviderResponse: ProviderResponse{
|
||||
ResponseType: ResponseAPI,
|
||||
},
|
||||
Permission: request.Permission,
|
||||
MessageID: request.MessageID,
|
||||
IsAllowed: false,
|
||||
}, nil
|
||||
}
|
||||
var data interface{}
|
||||
switch request.Permission {
|
||||
case PermissionWeb3:
|
||||
dappsAddress, err := api.s.accountsDB.GetDappsAddress()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response := make([]interface{}, 1)
|
||||
response[0] = dappsAddress
|
||||
data = response
|
||||
case PermissionContactCode:
|
||||
pubKey, err := api.s.accountsDB.GetPublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = pubKey
|
||||
default:
|
||||
return nil, ErrorUnknownPermission
|
||||
}
|
||||
return &APIResponse{
|
||||
ProviderResponse: ProviderResponse{
|
||||
ResponseType: ResponseAPI,
|
||||
},
|
||||
Permission: request.Permission,
|
||||
MessageID: request.MessageID,
|
||||
Data: data,
|
||||
IsAllowed: true,
|
||||
}, nil
|
||||
}
|
||||
62
vendor/github.com/status-im/status-go/services/web3provider/service.go
generated
vendored
Normal file
62
vendor/github.com/status-im/status-go/services/web3provider/service.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
package web3provider
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
||||
|
||||
"github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
"github.com/status-im/status-go/services/permissions"
|
||||
"github.com/status-im/status-go/services/rpcfilters"
|
||||
)
|
||||
|
||||
func NewService(appDB *sql.DB, accountsDB *accounts.Database, rpcClient *rpc.Client, config *params.NodeConfig, accountsManager *account.GethManager, rpcFiltersSrvc *rpcfilters.Service, transactor *transactions.Transactor) *Service {
|
||||
return &Service{
|
||||
permissionsDB: permissions.NewDB(appDB),
|
||||
accountsDB: accountsDB,
|
||||
rpcClient: rpcClient,
|
||||
rpcFiltersSrvc: rpcFiltersSrvc,
|
||||
config: config,
|
||||
accountsManager: accountsManager,
|
||||
transactor: transactor,
|
||||
}
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
permissionsDB *permissions.Database
|
||||
accountsDB *accounts.Database
|
||||
rpcClient *rpc.Client
|
||||
rpcFiltersSrvc *rpcfilters.Service
|
||||
accountsManager *account.GethManager
|
||||
config *params.NodeConfig
|
||||
transactor *transactions.Transactor
|
||||
}
|
||||
|
||||
func (s *Service) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) APIs() []gethrpc.API {
|
||||
return []gethrpc.API{
|
||||
{
|
||||
Namespace: "provider",
|
||||
Version: "0.1.0",
|
||||
Service: NewAPI(s),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return nil
|
||||
}
|
||||
111
vendor/github.com/status-im/status-go/services/web3provider/signature.go
generated
vendored
Normal file
111
vendor/github.com/status-im/status-go/services/web3provider/signature.go
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
package web3provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
|
||||
signercore "github.com/ethereum/go-ethereum/signer/core/apitypes"
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/services/rpcfilters"
|
||||
"github.com/status-im/status-go/services/typeddata"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
)
|
||||
|
||||
// signMessage checks the pwd vs the selected account and signs a message
|
||||
func (api *API) signMessage(data interface{}, address string, password string) (types.HexBytes, error) {
|
||||
account, err := api.getVerifiedWalletAccount(address, password)
|
||||
if err != nil {
|
||||
return types.HexBytes{}, err
|
||||
}
|
||||
|
||||
var dBytes []byte
|
||||
switch d := data.(type) {
|
||||
case string:
|
||||
dBytes = []byte(d)
|
||||
case []byte:
|
||||
dBytes = d
|
||||
case byte:
|
||||
dBytes = []byte{d}
|
||||
}
|
||||
|
||||
hash := crypto.TextHash(dBytes)
|
||||
|
||||
sig, err := crypto.Sign(hash, account.AccountKey.PrivateKey)
|
||||
if err != nil {
|
||||
return types.HexBytes{}, err
|
||||
}
|
||||
|
||||
sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
||||
|
||||
return types.HexBytes(sig), err
|
||||
}
|
||||
|
||||
// signTypedData accepts data and password. Gets verified account and signs typed data.
|
||||
func (api *API) signTypedData(typed typeddata.TypedData, address string, password string) (types.HexBytes, error) {
|
||||
account, err := api.getVerifiedWalletAccount(address, password)
|
||||
if err != nil {
|
||||
return types.HexBytes{}, err
|
||||
}
|
||||
chain := new(big.Int).SetUint64(api.s.config.NetworkID)
|
||||
sig, err := typeddata.Sign(typed, account.AccountKey.PrivateKey, chain)
|
||||
if err != nil {
|
||||
return types.HexBytes{}, err
|
||||
}
|
||||
return types.HexBytes(sig), err
|
||||
}
|
||||
|
||||
// signTypedDataV4 accepts data and password. Gets verified account and signs typed data.
|
||||
func (api *API) signTypedDataV4(typed signercore.TypedData, address string, password string) (types.HexBytes, error) {
|
||||
account, err := api.getVerifiedWalletAccount(address, password)
|
||||
if err != nil {
|
||||
return types.HexBytes{}, err
|
||||
}
|
||||
chain := new(big.Int).SetUint64(api.s.config.NetworkID)
|
||||
sig, err := typeddata.SignTypedDataV4(typed, account.AccountKey.PrivateKey, chain)
|
||||
if err != nil {
|
||||
return types.HexBytes{}, err
|
||||
}
|
||||
return types.HexBytes(sig), err
|
||||
}
|
||||
|
||||
// SendTransaction creates a new transaction and waits until it's complete.
|
||||
func (api *API) sendTransaction(chainID uint64, sendArgs transactions.SendTxArgs, password string, requestType string) (hash types.Hash, err error) {
|
||||
verifiedAccount, err := api.getVerifiedWalletAccount(sendArgs.From.String(), password)
|
||||
if err != nil {
|
||||
return hash, err
|
||||
}
|
||||
|
||||
hash, err = api.s.transactor.SendTransactionWithChainID(chainID, sendArgs, verifiedAccount)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
go api.s.rpcFiltersSrvc.TriggerTransactionSentToUpstreamEvent(&rpcfilters.PendingTxInfo{
|
||||
Hash: common.Hash(hash),
|
||||
Type: requestType,
|
||||
From: common.Address(sendArgs.From),
|
||||
ChainID: chainID,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (api *API) EcRecover(data hexutil.Bytes, sig hexutil.Bytes) (types.Address, error) {
|
||||
if len(sig) != 65 {
|
||||
return types.Address{}, fmt.Errorf("signature must be 65 bytes long")
|
||||
}
|
||||
if sig[64] != 27 && sig[64] != 28 {
|
||||
return types.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)")
|
||||
}
|
||||
sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1
|
||||
hash := crypto.TextHash(data)
|
||||
rpk, err := crypto.SigToPub(hash, sig)
|
||||
if err != nil {
|
||||
return types.Address{}, err
|
||||
}
|
||||
return crypto.PubkeyToAddress(*rpk), nil
|
||||
}
|
||||
Reference in New Issue
Block a user