feat(connector)_: impl `personal_sign` endpoint (#5681)
This commit is contained in:
parent
c08dedb77d
commit
be9ba7604b
|
@ -23,10 +23,15 @@ func NewAPI(s *Service) *API {
|
|||
r := NewCommandRegistry()
|
||||
c := commands.NewClientSideHandler()
|
||||
|
||||
// Transactions and signing
|
||||
r.Register("eth_sendTransaction", &commands.SendTransactionCommand{
|
||||
Db: s.db,
|
||||
ClientHandler: c,
|
||||
})
|
||||
r.Register("personal_sign", &commands.PersonalSignCommand{
|
||||
Db: s.db,
|
||||
ClientHandler: c,
|
||||
})
|
||||
|
||||
// Accounts query and dapp permissions
|
||||
// NOTE: Some dApps expect same behavior for both eth_accounts and eth_requestAccounts
|
||||
|
@ -126,3 +131,11 @@ func (api *API) SendTransactionAccepted(args commands.SendTransactionAcceptedArg
|
|||
func (api *API) SendTransactionRejected(args commands.RejectedArgs) error {
|
||||
return api.c.SendTransactionRejected(args)
|
||||
}
|
||||
|
||||
func (api *API) PersonalSignAccepted(args commands.PersonalSignAcceptedArgs) error {
|
||||
return api.c.PersonalSignAccepted(args)
|
||||
}
|
||||
|
||||
func (api *API) PersonalSignRejected(args commands.RejectedArgs) error {
|
||||
return api.c.PersonalSignRejected(args)
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ var (
|
|||
ErrEmptyAccountsShared = fmt.Errorf("empty accounts were shared by wallet")
|
||||
ErrRequestAccountsRejectedByUser = fmt.Errorf("request accounts was rejected by user")
|
||||
ErrSendTransactionRejectedByUser = fmt.Errorf("send transaction was rejected by user")
|
||||
ErrPersonalSignRejectedByUser = fmt.Errorf("personal sign was rejected by user")
|
||||
ErrEmptyRequestID = fmt.Errorf("empty requestID")
|
||||
ErrAnotherConnectorOperationIsAwaitingFor = fmt.Errorf("another connector operation is awaiting for user input")
|
||||
)
|
||||
|
@ -29,6 +30,7 @@ type MessageType int
|
|||
const (
|
||||
RequestAccountsAccepted MessageType = iota
|
||||
SendTransactionAccepted
|
||||
PersonalSignAccepted
|
||||
Rejected
|
||||
)
|
||||
|
||||
|
@ -95,6 +97,20 @@ func (c *ClientSideHandler) RequestShareAccountForDApp(dApp signal.ConnectorDApp
|
|||
}
|
||||
}
|
||||
|
||||
func (c *ClientSideHandler) RequestAccountsAccepted(args RequestAccountsAcceptedArgs) error {
|
||||
c.responseChannel <- Message{Type: RequestAccountsAccepted, Data: args}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClientSideHandler) RequestAccountsRejected(args RejectedArgs) error {
|
||||
if args.RequestID == "" {
|
||||
return ErrEmptyRequestID
|
||||
}
|
||||
|
||||
c.responseChannel <- Message{Type: Rejected, Data: args}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClientSideHandler) RequestSendTransaction(dApp signal.ConnectorDApp, chainID uint64, txArgs *transactions.SendTxArgs) (types.Hash, error) {
|
||||
if !c.setRequestRunning() {
|
||||
return types.Hash{}, ErrAnotherConnectorOperationIsAwaitingFor
|
||||
|
@ -132,20 +148,6 @@ func (c *ClientSideHandler) RequestSendTransaction(dApp signal.ConnectorDApp, ch
|
|||
}
|
||||
}
|
||||
|
||||
func (c *ClientSideHandler) RequestAccountsAccepted(args RequestAccountsAcceptedArgs) error {
|
||||
c.responseChannel <- Message{Type: RequestAccountsAccepted, Data: args}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClientSideHandler) RequestAccountsRejected(args RejectedArgs) error {
|
||||
if args.RequestID == "" {
|
||||
return ErrEmptyRequestID
|
||||
}
|
||||
|
||||
c.responseChannel <- Message{Type: Rejected, Data: args}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClientSideHandler) SendTransactionAccepted(args SendTransactionAcceptedArgs) error {
|
||||
if args.RequestID == "" {
|
||||
return ErrEmptyRequestID
|
||||
|
@ -163,3 +165,53 @@ func (c *ClientSideHandler) SendTransactionRejected(args RejectedArgs) error {
|
|||
c.responseChannel <- Message{Type: Rejected, Data: args}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClientSideHandler) RequestPersonalSign(dApp signal.ConnectorDApp, challenge, address string) (string, error) {
|
||||
if !c.setRequestRunning() {
|
||||
return "", ErrAnotherConnectorOperationIsAwaitingFor
|
||||
}
|
||||
defer c.clearRequestRunning()
|
||||
|
||||
requestID := c.generateRequestID(dApp)
|
||||
signal.SendConnectorPersonalSign(dApp, requestID, challenge, address)
|
||||
|
||||
timeout := time.After(WalletResponseMaxInterval)
|
||||
|
||||
for {
|
||||
select {
|
||||
case msg := <-c.responseChannel:
|
||||
switch msg.Type {
|
||||
case PersonalSignAccepted:
|
||||
response := msg.Data.(PersonalSignAcceptedArgs)
|
||||
if response.RequestID == requestID {
|
||||
return response.Signature, nil
|
||||
}
|
||||
case Rejected:
|
||||
response := msg.Data.(RejectedArgs)
|
||||
if response.RequestID == requestID {
|
||||
return "", ErrPersonalSignRejectedByUser
|
||||
}
|
||||
}
|
||||
case <-timeout:
|
||||
return "", ErrWalletResponseTimeout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ClientSideHandler) PersonalSignAccepted(args PersonalSignAcceptedArgs) error {
|
||||
if args.RequestID == "" {
|
||||
return ErrEmptyRequestID
|
||||
}
|
||||
|
||||
c.responseChannel <- Message{Type: PersonalSignAccepted, Data: args}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClientSideHandler) PersonalSignRejected(args RejectedArgs) error {
|
||||
if args.RequestID == "" {
|
||||
return ErrEmptyRequestID
|
||||
}
|
||||
|
||||
c.responseChannel <- Message{Type: Rejected, Data: args}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
persistence "github.com/status-im/status-go/services/connector/database"
|
||||
"github.com/status-im/status-go/signal"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidParamsStructure = errors.New("invalid params structure")
|
||||
)
|
||||
|
||||
type PersonalSignCommand struct {
|
||||
Db *sql.DB
|
||||
ClientHandler ClientSideHandlerInterface
|
||||
}
|
||||
|
||||
type PersonalSignParams struct {
|
||||
Challenge string `json:"challenge"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
func (r *RPCRequest) getPersonalSignParams() (*PersonalSignParams, error) {
|
||||
if r.Params == nil || len(r.Params) == 0 {
|
||||
return nil, ErrEmptyRPCParams
|
||||
}
|
||||
|
||||
paramMap, ok := r.Params[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, ErrInvalidParamsStructure
|
||||
}
|
||||
|
||||
// Extract the Challenge and Address fields from paramMap
|
||||
challenge, ok := paramMap["challenge"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing or invalid 'challenge' field")
|
||||
}
|
||||
|
||||
address, ok := paramMap["address"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing or invalid 'address' field")
|
||||
}
|
||||
|
||||
// Create and return the PersonalSignParams
|
||||
return &PersonalSignParams{
|
||||
Challenge: challenge,
|
||||
Address: address,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *PersonalSignCommand) Execute(request RPCRequest) (interface{}, error) {
|
||||
err := request.Validate()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
params, err := request.getPersonalSignParams()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
dApp, err := persistence.SelectDAppByUrl(c.Db, request.URL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if dApp == nil {
|
||||
return "", ErrDAppIsNotPermittedByUser
|
||||
}
|
||||
|
||||
return c.ClientHandler.RequestPersonalSign(signal.ConnectorDApp{
|
||||
URL: request.URL,
|
||||
Name: request.Name,
|
||||
IconURL: request.IconURL,
|
||||
}, params.Challenge, params.Address)
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/signal"
|
||||
)
|
||||
|
||||
func preparePersonalSignRequest(dApp signal.ConnectorDApp, challenge, address string) (RPCRequest, error) {
|
||||
params := map[string]interface{}{
|
||||
"challenge": challenge,
|
||||
"address": address,
|
||||
}
|
||||
|
||||
return ConstructRPCRequest("personal_sign", []interface{}{params}, &dApp)
|
||||
}
|
||||
|
||||
func TestFailToPersonalSignWithMissingDAppFields(t *testing.T) {
|
||||
db, close := SetupTestDB(t)
|
||||
defer close()
|
||||
|
||||
cmd := &PersonalSignCommand{Db: db}
|
||||
|
||||
// Missing DApp fields
|
||||
request, err := ConstructRPCRequest("personal_sign", []interface{}{}, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
result, err := cmd.Execute(request)
|
||||
assert.Equal(t, ErrRequestMissingDAppData, err)
|
||||
assert.Empty(t, result)
|
||||
}
|
||||
|
||||
func TestFailToPersonalSignForUnpermittedDApp(t *testing.T) {
|
||||
db, close := SetupTestDB(t)
|
||||
defer close()
|
||||
|
||||
cmd := &PersonalSignCommand{Db: db}
|
||||
|
||||
request, err := preparePersonalSignRequest(testDAppData,
|
||||
"0x506c65617365207369676e2074686973206d65737361676520746f20636f6e6669726d20796f7572206964656e746974792e",
|
||||
"0x4B0897b0513FdBeEc7C469D9aF4fA6C0752aBea7",
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
|
||||
result, err := cmd.Execute(request)
|
||||
assert.Equal(t, ErrDAppIsNotPermittedByUser, err)
|
||||
assert.Empty(t, result)
|
||||
}
|
||||
|
||||
func TestFailToPersonalSignWithoutParams(t *testing.T) {
|
||||
db, close := SetupTestDB(t)
|
||||
defer close()
|
||||
|
||||
cmd := &PersonalSignCommand{Db: db}
|
||||
|
||||
request, err := ConstructRPCRequest("personal_sign", nil, &testDAppData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
result, err := cmd.Execute(request)
|
||||
assert.Equal(t, ErrEmptyRPCParams, err)
|
||||
assert.Empty(t, result)
|
||||
}
|
||||
|
||||
func TestFailToPersonalSignWithSignalTimout(t *testing.T) {
|
||||
db, close := SetupTestDB(t)
|
||||
defer close()
|
||||
|
||||
clientHandler := NewClientSideHandler()
|
||||
|
||||
cmd := &PersonalSignCommand{
|
||||
Db: db,
|
||||
ClientHandler: clientHandler,
|
||||
}
|
||||
|
||||
err := PersistDAppData(db, testDAppData, types.Address{0x01}, uint64(0x1))
|
||||
assert.NoError(t, err)
|
||||
|
||||
request, err := preparePersonalSignRequest(testDAppData,
|
||||
"0x506c65617365207369676e2074686973206d65737361676520746f20636f6e6669726d20796f7572206964656e746974792e",
|
||||
"0x4B0897b0513FdBeEc7C469D9aF4fA6C0752aBea7",
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
|
||||
backupWalletResponseMaxInterval := WalletResponseMaxInterval
|
||||
WalletResponseMaxInterval = 1 * time.Millisecond
|
||||
|
||||
_, err = cmd.Execute(request)
|
||||
assert.Equal(t, ErrWalletResponseTimeout, err)
|
||||
WalletResponseMaxInterval = backupWalletResponseMaxInterval
|
||||
}
|
||||
|
||||
func TestPersonalSignWithSignalAccepted(t *testing.T) {
|
||||
db, close := SetupTestDB(t)
|
||||
defer close()
|
||||
|
||||
fakedSignature := "0x051"
|
||||
|
||||
clientHandler := NewClientSideHandler()
|
||||
|
||||
cmd := &PersonalSignCommand{
|
||||
Db: db,
|
||||
ClientHandler: clientHandler,
|
||||
}
|
||||
|
||||
err := PersistDAppData(db, testDAppData, types.Address{0x01}, uint64(0x1))
|
||||
assert.NoError(t, err)
|
||||
|
||||
challenge := "0x506c65617365207369676e2074686973206d65737361676520746f20636f6e6669726d20796f7572206964656e746974792e"
|
||||
address := "0x4B0897b0513FdBeEc7C469D9aF4fA6C0752aBea7"
|
||||
request, err := preparePersonalSignRequest(testDAppData, challenge, address)
|
||||
assert.NoError(t, err)
|
||||
|
||||
signal.SetMobileSignalHandler(signal.MobileSignalHandler(func(s []byte) {
|
||||
var evt EventType
|
||||
err := json.Unmarshal(s, &evt)
|
||||
assert.NoError(t, err)
|
||||
|
||||
switch evt.Type {
|
||||
case signal.EventConnectorPersonalSign:
|
||||
var ev signal.ConnectorPersonalSignSignal
|
||||
err := json.Unmarshal(evt.Event, &ev)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ev.Challenge, challenge)
|
||||
assert.Equal(t, ev.Address, address)
|
||||
|
||||
err = clientHandler.PersonalSignAccepted(PersonalSignAcceptedArgs{
|
||||
Signature: fakedSignature,
|
||||
RequestID: ev.RequestID,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}))
|
||||
|
||||
response, err := cmd.Execute(request)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, response, fakedSignature)
|
||||
}
|
||||
|
||||
func TestPersonalSignWithSignalRejected(t *testing.T) {
|
||||
db, close := SetupTestDB(t)
|
||||
defer close()
|
||||
|
||||
clientHandler := NewClientSideHandler()
|
||||
|
||||
cmd := &PersonalSignCommand{
|
||||
Db: db,
|
||||
ClientHandler: clientHandler,
|
||||
}
|
||||
|
||||
err := PersistDAppData(db, testDAppData, types.Address{0x01}, uint64(0x1))
|
||||
assert.NoError(t, err)
|
||||
|
||||
challenge := "0x506c65617365207369676e2074686973206d65737361676520746f20636f6e6669726d20796f7572206964656e746974792e"
|
||||
address := "0x4B0897b0513FdBeEc7C469D9aF4fA6C0752aBea7"
|
||||
request, err := preparePersonalSignRequest(testDAppData, challenge, address)
|
||||
assert.NoError(t, err)
|
||||
|
||||
signal.SetMobileSignalHandler(signal.MobileSignalHandler(func(s []byte) {
|
||||
var evt EventType
|
||||
err := json.Unmarshal(s, &evt)
|
||||
assert.NoError(t, err)
|
||||
|
||||
switch evt.Type {
|
||||
case signal.EventConnectorPersonalSign:
|
||||
var ev signal.ConnectorPersonalSignSignal
|
||||
err := json.Unmarshal(evt.Event, &ev)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = clientHandler.PersonalSignRejected(RejectedArgs{
|
||||
RequestID: ev.RequestID,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}))
|
||||
|
||||
_, err = cmd.Execute(request)
|
||||
assert.Equal(t, ErrPersonalSignRejectedByUser, err)
|
||||
}
|
|
@ -43,18 +43,27 @@ type SendTransactionAcceptedArgs struct {
|
|||
Hash types.Hash `json:"hash"`
|
||||
}
|
||||
|
||||
type PersonalSignAcceptedArgs struct {
|
||||
RequestID string `json:"requestId"`
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
type RejectedArgs struct {
|
||||
RequestID string `json:"requestId"`
|
||||
}
|
||||
|
||||
type ClientSideHandlerInterface interface {
|
||||
RequestShareAccountForDApp(dApp signal.ConnectorDApp) (types.Address, uint64, error)
|
||||
RequestSendTransaction(dApp signal.ConnectorDApp, chainID uint64, txArgs *transactions.SendTxArgs) (types.Hash, error)
|
||||
|
||||
RequestAccountsAccepted(args RequestAccountsAcceptedArgs) error
|
||||
RequestAccountsRejected(args RejectedArgs) error
|
||||
|
||||
RequestSendTransaction(dApp signal.ConnectorDApp, chainID uint64, txArgs *transactions.SendTxArgs) (types.Hash, error)
|
||||
SendTransactionAccepted(args SendTransactionAcceptedArgs) error
|
||||
SendTransactionRejected(args RejectedArgs) error
|
||||
|
||||
RequestPersonalSign(dApp signal.ConnectorDApp, challenge, address string) (string, error)
|
||||
PersonalSignAccepted(args PersonalSignAcceptedArgs) error
|
||||
PersonalSignRejected(args RejectedArgs) error
|
||||
}
|
||||
|
||||
type NetworkManagerInterface interface {
|
||||
|
|
|
@ -38,6 +38,7 @@ func TestRequestAccountsSwitchChainAndSendTransactionFlow(t *testing.T) {
|
|||
|
||||
accountAddress := types.BytesToAddress(types.FromHex("0x6d0aa2a774b74bb1d36f97700315adf962c69fcg"))
|
||||
expectedHash := types.BytesToHash(types.FromHex("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"))
|
||||
expectedSignature := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
|
||||
|
||||
dAppPermissionRevoked := false
|
||||
dAppPermissionGranted := false
|
||||
|
@ -73,6 +74,16 @@ func TestRequestAccountsSwitchChainAndSendTransactionFlow(t *testing.T) {
|
|||
Hash: expectedHash,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
case signal.EventConnectorPersonalSign:
|
||||
var ev signal.ConnectorPersonalSignSignal
|
||||
err := json.Unmarshal(evt.Event, &ev)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = api.PersonalSignAccepted(commands.PersonalSignAcceptedArgs{
|
||||
RequestID: ev.RequestID,
|
||||
Signature: expectedSignature,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}))
|
||||
|
||||
|
@ -110,6 +121,12 @@ func TestRequestAccountsSwitchChainAndSendTransactionFlow(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedHash.Hex(), response)
|
||||
|
||||
// Personal sign
|
||||
request = "{\"method\": \"personal_sign\", \"params\":[{\"challenge\": \"0x506c65617365207369676e2074686973206d65737361676520746f20636f6e6669726d20796f7572206964656e746974792e\",\"address\":\"0x4B0897b0513FdBeEc7C469D9aF4fA6C0752aBea7\"}], \"url\": \"http://testDAppURL123\", \"name\": \"testDAppName\", \"iconUrl\": \"http://testDAppIconUrl\" }"
|
||||
response, err = api.CallRPC(request)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedSignature, response)
|
||||
|
||||
// Revoke permissions
|
||||
request = "{\"method\": \"wallet_revokePermissions\", \"params\": [], \"url\": \"http://testDAppURL123\", \"name\": \"testDAppName\", \"iconUrl\": \"http://testDAppIconUrl\" }"
|
||||
_, err = api.CallRPC(request)
|
||||
|
|
|
@ -3,6 +3,7 @@ package signal
|
|||
const (
|
||||
EventConnectorSendRequestAccounts = "connector.sendRequestAccounts"
|
||||
EventConnectorSendTransaction = "connector.sendTransaction"
|
||||
EventConnectorPersonalSign = "connector.personalSign"
|
||||
EventConnectorDAppPermissionGranted = "connector.dAppPermissionGranted"
|
||||
EventConnectorDAppPermissionRevoked = "connector.dAppPermissionRevoked"
|
||||
)
|
||||
|
@ -27,6 +28,13 @@ type ConnectorSendTransactionSignal struct {
|
|||
TxArgs string `json:"txArgs"`
|
||||
}
|
||||
|
||||
type ConnectorPersonalSignSignal struct {
|
||||
ConnectorDApp
|
||||
RequestID string `json:"requestId"`
|
||||
Challenge string `json:"challenge"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
func SendConnectorSendRequestAccounts(dApp ConnectorDApp, requestID string) {
|
||||
send(EventConnectorSendRequestAccounts, ConnectorSendRequestAccountsSignal{
|
||||
ConnectorDApp: dApp,
|
||||
|
@ -43,6 +51,15 @@ func SendConnectorSendTransaction(dApp ConnectorDApp, chainID uint64, txArgs str
|
|||
})
|
||||
}
|
||||
|
||||
func SendConnectorPersonalSign(dApp ConnectorDApp, requestID, challenge, address string) {
|
||||
send(EventConnectorPersonalSign, ConnectorPersonalSignSignal{
|
||||
ConnectorDApp: dApp,
|
||||
RequestID: requestID,
|
||||
Challenge: challenge,
|
||||
Address: address,
|
||||
})
|
||||
}
|
||||
|
||||
func SendConnectorDAppPermissionGranted(dApp ConnectorDApp) {
|
||||
send(EventConnectorDAppPermissionGranted, dApp)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue