status-go/services/personal/api.go

122 lines
3.6 KiB
Go

package personal
import (
"context"
"errors"
"strings"
"time"
"github.com/status-im/status-go/geth/account"
"github.com/status-im/status-go/geth/params"
"github.com/status-im/status-go/geth/rpc"
"github.com/status-im/status-go/sign"
)
var (
// ErrInvalidPersonalSignAccount is returned when the account passed to
// personal_sign isn't equal to the currently selected account.
ErrInvalidPersonalSignAccount = errors.New("invalid account as only the selected one can generate a signature")
// ErrSignInvalidNumberOfParameters is returned when the number of parameters for personal_sign
// is not valid.
ErrSignInvalidNumberOfParameters = errors.New("invalid number of parameters for personal_sign (2 or 3 expected)")
)
type metadata struct {
Data interface{} `json:"data"`
Address string `json:"account"`
}
func newMetadata(rpcParams []interface{}) (*metadata, error) {
// personal_sign can be called with the following parameters
// 1) data to sign
// 2) account
// 3) (optional) password
// here, we always ignore (3) because we send a confirmation for the password to UI
if len(rpcParams) < 2 || len(rpcParams) > 3 {
return nil, ErrSignInvalidNumberOfParameters
}
data := rpcParams[0]
address := rpcParams[1].(string)
return &metadata{data, address}, nil
}
// PublicAPI represents a set of APIs from the `web3.personal` namespace.
type PublicAPI struct {
pendingSignRequests *sign.PendingRequests
rpcClient *rpc.Client
rpcTimeout time.Duration
}
// NewAPI creates an instance of the personal API.
func NewAPI(pendingSignRequests *sign.PendingRequests) *PublicAPI {
return &PublicAPI{
pendingSignRequests: pendingSignRequests,
}
}
// SetRPC sets RPC params (client and timeout) for the API calls.
func (api *PublicAPI) SetRPC(rpcClient *rpc.Client, timeout time.Duration) {
api.rpcClient = rpcClient
api.rpcTimeout = timeout
}
// Recover is an implementation of `personal_ecRecover` or `web3.personal.ecRecover` API
func (api *PublicAPI) Recover(context context.Context, rpcParams ...interface{}) (interface{}, error) {
var response interface{}
err := api.rpcClient.CallContextIgnoringLocalHandlers(
context, &response, params.PersonalRecoverMethodName, rpcParams...)
return response, err
}
// Sign is an implementation of `personal_sign` or `web3.personal.sign` API
func (api *PublicAPI) Sign(context context.Context, rpcParams ...interface{}) (interface{}, error) {
metadata, err := newMetadata(rpcParams)
if err != nil {
return nil, err
}
req, err := api.pendingSignRequests.Add(context, params.PersonalSignMethodName, metadata, api.completeFunc(context, *metadata))
if err != nil {
return nil, err
}
result := api.pendingSignRequests.Wait(req.ID, api.rpcTimeout)
return result.Response, result.Error
}
func (api *PublicAPI) completeFunc(context context.Context, metadata metadata) sign.CompleteFunc {
return func(acc *account.SelectedExtKey, password string) (response sign.Response, err error) {
response = sign.EmptyResponse
err = api.validateAccount(metadata, acc)
if err != nil {
return
}
err = api.rpcClient.CallContextIgnoringLocalHandlers(
context,
&response,
params.PersonalSignMethodName,
metadata.Data, metadata.Address, password)
return
}
}
// make sure that only account which created the tx can complete it
func (api *PublicAPI) validateAccount(metadata metadata, selectedAccount *account.SelectedExtKey) error {
if selectedAccount == nil {
return account.ErrNoAccountSelected
}
// case-insensitive string comparison
if !strings.EqualFold(metadata.Address, selectedAccount.Address.Hex()) {
return ErrInvalidPersonalSignAccount
}
return nil
}