feat(browser connect)_: Implementing signTypedData_V4

This commit bundles the personal sign and the signTypedData_V4 sign request in the same command.
The only difference between these two requests is the order of address and challenge in the params array.

What has changed:
1. PersonalSign.. has been renamed to Sign
2. `getPersonalSignParams` renamed to `getSignParams` and implements the parsing for both personal sign and signTypedData_V4
This commit is contained in:
Alex Jbanca 2024-11-19 14:12:08 +02:00
parent 43f355a391
commit e953cb6c95
No known key found for this signature in database
GPG Key ID: 6004079575C21C5D
9 changed files with 339 additions and 190 deletions

View File

@ -30,7 +30,11 @@ func NewAPI(s *Service) *API {
Db: s.db, Db: s.db,
ClientHandler: c, ClientHandler: c,
}) })
r.Register("personal_sign", &commands.PersonalSignCommand{ r.Register("personal_sign", &commands.SignCommand{
Db: s.db,
ClientHandler: c,
})
r.Register("eth_signTypedData_v4", &commands.SignCommand{
Db: s.db, Db: s.db,
ClientHandler: c, ClientHandler: c,
}) })
@ -144,10 +148,10 @@ func (api *API) SendTransactionRejected(args commands.RejectedArgs) error {
return api.c.SendTransactionRejected(args) return api.c.SendTransactionRejected(args)
} }
func (api *API) PersonalSignAccepted(args commands.PersonalSignAcceptedArgs) error { func (api *API) SignAccepted(args commands.SignAcceptedArgs) error {
return api.c.PersonalSignAccepted(args) return api.c.SignAccepted(args)
} }
func (api *API) PersonalSignRejected(args commands.RejectedArgs) error { func (api *API) SignRejected(args commands.RejectedArgs) error {
return api.c.PersonalSignRejected(args) return api.c.SignRejected(args)
} }

View File

@ -22,7 +22,7 @@ var (
ErrEmptyAccountsShared = fmt.Errorf("empty accounts were shared by wallet") ErrEmptyAccountsShared = fmt.Errorf("empty accounts were shared by wallet")
ErrRequestAccountsRejectedByUser = fmt.Errorf("request accounts was rejected by user") ErrRequestAccountsRejectedByUser = fmt.Errorf("request accounts was rejected by user")
ErrSendTransactionRejectedByUser = fmt.Errorf("send transaction was rejected by user") ErrSendTransactionRejectedByUser = fmt.Errorf("send transaction was rejected by user")
ErrPersonalSignRejectedByUser = fmt.Errorf("personal sign was rejected by user") ErrSignRejectedByUser = fmt.Errorf("sign was rejected by user")
ErrEmptyRequestID = fmt.Errorf("empty requestID") ErrEmptyRequestID = fmt.Errorf("empty requestID")
ErrAnotherConnectorOperationIsAwaitingFor = fmt.Errorf("another connector operation is awaiting for user input") ErrAnotherConnectorOperationIsAwaitingFor = fmt.Errorf("another connector operation is awaiting for user input")
ErrEmptyUrl = fmt.Errorf("empty URL") ErrEmptyUrl = fmt.Errorf("empty URL")
@ -34,7 +34,7 @@ type MessageType int
const ( const (
RequestAccountsAccepted MessageType = iota RequestAccountsAccepted MessageType = iota
SendTransactionAccepted SendTransactionAccepted
PersonalSignAccepted SignAccepted
Rejected Rejected
) )
@ -203,14 +203,14 @@ func (c *ClientSideHandler) SendTransactionRejected(args RejectedArgs) error {
return nil return nil
} }
func (c *ClientSideHandler) RequestPersonalSign(dApp signal.ConnectorDApp, challenge, address string) (string, error) { func (c *ClientSideHandler) RequestSign(dApp signal.ConnectorDApp, challenge, address string, method string) (string, error) {
if !c.setRequestRunning() { if !c.setRequestRunning() {
return "", ErrAnotherConnectorOperationIsAwaitingFor return "", ErrAnotherConnectorOperationIsAwaitingFor
} }
defer c.clearRequestRunning() defer c.clearRequestRunning()
requestID := c.generateRequestID(dApp) requestID := c.generateRequestID(dApp)
signal.SendConnectorPersonalSign(dApp, requestID, challenge, address) signal.SendConnectorSign(dApp, requestID, challenge, address, method)
timeout := time.After(WalletResponseMaxInterval) timeout := time.After(WalletResponseMaxInterval)
@ -218,15 +218,15 @@ func (c *ClientSideHandler) RequestPersonalSign(dApp signal.ConnectorDApp, chall
select { select {
case msg := <-c.responseChannel: case msg := <-c.responseChannel:
switch msg.Type { switch msg.Type {
case PersonalSignAccepted: case SignAccepted:
response := msg.Data.(PersonalSignAcceptedArgs) response := msg.Data.(SignAcceptedArgs)
if response.RequestID == requestID { if response.RequestID == requestID {
return response.Signature, nil return response.Signature, nil
} }
case Rejected: case Rejected:
response := msg.Data.(RejectedArgs) response := msg.Data.(RejectedArgs)
if response.RequestID == requestID { if response.RequestID == requestID {
return "", ErrPersonalSignRejectedByUser return "", ErrSignRejectedByUser
} }
} }
case <-timeout: case <-timeout:
@ -235,16 +235,16 @@ func (c *ClientSideHandler) RequestPersonalSign(dApp signal.ConnectorDApp, chall
} }
} }
func (c *ClientSideHandler) PersonalSignAccepted(args PersonalSignAcceptedArgs) error { func (c *ClientSideHandler) SignAccepted(args SignAcceptedArgs) error {
if args.RequestID == "" { if args.RequestID == "" {
return ErrEmptyRequestID return ErrEmptyRequestID
} }
c.responseChannel <- Message{Type: PersonalSignAccepted, Data: args} c.responseChannel <- Message{Type: SignAccepted, Data: args}
return nil return nil
} }
func (c *ClientSideHandler) PersonalSignRejected(args RejectedArgs) error { func (c *ClientSideHandler) SignRejected(args RejectedArgs) error {
if args.RequestID == "" { if args.RequestID == "" {
return ErrEmptyRequestID return ErrEmptyRequestID
} }

View File

@ -1,153 +0,0 @@
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) {
return ConstructRPCRequest("personal_sign", []interface{}{challenge, address}, &dApp)
}
func TestFailToPersonalSignWithMissingDAppFields(t *testing.T) {
state, close := setupCommand(t, Method_PersonalSign)
t.Cleanup(close)
// Missing DApp fields
request, err := ConstructRPCRequest("personal_sign", []interface{}{}, nil)
assert.NoError(t, err)
result, err := state.cmd.Execute(state.ctx, request)
assert.Equal(t, ErrRequestMissingDAppData, err)
assert.Empty(t, result)
}
func TestFailToPersonalSignForUnpermittedDApp(t *testing.T) {
state, close := setupCommand(t, Method_PersonalSign)
t.Cleanup(close)
request, err := preparePersonalSignRequest(testDAppData,
"0x506c65617365207369676e2074686973206d65737361676520746f20636f6e6669726d20796f7572206964656e746974792e",
"0x4B0897b0513FdBeEc7C469D9aF4fA6C0752aBea7",
)
assert.NoError(t, err)
result, err := state.cmd.Execute(state.ctx, request)
assert.Equal(t, ErrDAppIsNotPermittedByUser, err)
assert.Empty(t, result)
}
func TestFailToPersonalSignWithoutParams(t *testing.T) {
state, close := setupCommand(t, Method_PersonalSign)
t.Cleanup(close)
request, err := ConstructRPCRequest("personal_sign", nil, &testDAppData)
assert.NoError(t, err)
result, err := state.cmd.Execute(state.ctx, request)
assert.Equal(t, ErrEmptyRPCParams, err)
assert.Empty(t, result)
}
func TestFailToPersonalSignWithSignalTimout(t *testing.T) {
state, close := setupCommand(t, Method_PersonalSign)
t.Cleanup(close)
err := PersistDAppData(state.walletDb, 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 = state.cmd.Execute(state.ctx, request)
assert.Equal(t, ErrWalletResponseTimeout, err)
WalletResponseMaxInterval = backupWalletResponseMaxInterval
}
func TestPersonalSignWithSignalAccepted(t *testing.T) {
state, close := setupCommand(t, Method_PersonalSign)
t.Cleanup(close)
fakedSignature := "0x051"
err := PersistDAppData(state.walletDb, 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 = state.handler.PersonalSignAccepted(PersonalSignAcceptedArgs{
Signature: fakedSignature,
RequestID: ev.RequestID,
})
assert.NoError(t, err)
}
}))
t.Cleanup(signal.ResetMobileSignalHandler)
response, err := state.cmd.Execute(state.ctx, request)
assert.NoError(t, err)
assert.Equal(t, response, fakedSignature)
}
func TestPersonalSignWithSignalRejected(t *testing.T) {
state, close := setupCommand(t, Method_PersonalSign)
t.Cleanup(close)
err := PersistDAppData(state.walletDb, 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 = state.handler.PersonalSignRejected(RejectedArgs{
RequestID: ev.RequestID,
})
assert.NoError(t, err)
}
}))
t.Cleanup(signal.ResetMobileSignalHandler)
_, err = state.cmd.Execute(state.ctx, request)
assert.Equal(t, ErrPersonalSignRejectedByUser, err)
}

View File

@ -17,6 +17,7 @@ const (
Method_EthRequestAccounts = "eth_requestAccounts" Method_EthRequestAccounts = "eth_requestAccounts"
Method_EthChainId = "eth_chainId" Method_EthChainId = "eth_chainId"
Method_PersonalSign = "personal_sign" Method_PersonalSign = "personal_sign"
Method_SignTypedDataV4 = "eth_signTypedData_v4"
Method_EthSendTransaction = "eth_sendTransaction" Method_EthSendTransaction = "eth_sendTransaction"
Method_RequestPermissions = "wallet_requestPermissions" Method_RequestPermissions = "wallet_requestPermissions"
Method_RevokePermissions = "wallet_revokePermissions" Method_RevokePermissions = "wallet_revokePermissions"
@ -56,7 +57,7 @@ type SendTransactionAcceptedArgs struct {
Hash types.Hash `json:"hash"` Hash types.Hash `json:"hash"`
} }
type PersonalSignAcceptedArgs struct { type SignAcceptedArgs struct {
RequestID string `json:"requestId"` RequestID string `json:"requestId"`
Signature string `json:"signature"` Signature string `json:"signature"`
} }
@ -79,9 +80,9 @@ type ClientSideHandlerInterface interface {
SendTransactionAccepted(args SendTransactionAcceptedArgs) error SendTransactionAccepted(args SendTransactionAcceptedArgs) error
SendTransactionRejected(args RejectedArgs) error SendTransactionRejected(args RejectedArgs) error
RequestPersonalSign(dApp signal.ConnectorDApp, challenge, address string) (string, error) RequestSign(dApp signal.ConnectorDApp, challenge, address string, method string) (string, error)
PersonalSignAccepted(args PersonalSignAcceptedArgs) error SignAccepted(args SignAcceptedArgs) error
PersonalSignRejected(args RejectedArgs) error SignRejected(args RejectedArgs) error
} }
type NetworkManagerInterface interface { type NetworkManagerInterface interface {

View File

@ -12,19 +12,25 @@ import (
var ( var (
ErrInvalidParamsStructure = errors.New("invalid params structure") ErrInvalidParamsStructure = errors.New("invalid params structure")
ErrInvalidMethod = errors.New("invalid method")
) )
type PersonalSignCommand struct { type SignCommand struct {
Db *sql.DB Db *sql.DB
ClientHandler ClientSideHandlerInterface ClientHandler ClientSideHandlerInterface
} }
type PersonalSignParams struct { type SignParams struct {
Challenge string `json:"challenge"` Challenge string `json:"challenge"`
Address string `json:"address"` Address string `json:"address"`
Method string `json:"method"`
} }
func (r *RPCRequest) getPersonalSignParams() (*PersonalSignParams, error) { func (r *RPCRequest) getSignParams() (*SignParams, error) {
if r.Method != Method_PersonalSign && r.Method != Method_SignTypedDataV4 {
return nil, ErrInvalidMethod
}
if r.Params == nil || len(r.Params) == 0 { if r.Params == nil || len(r.Params) == 0 {
return nil, ErrEmptyRPCParams return nil, ErrEmptyRPCParams
} }
@ -33,31 +39,40 @@ func (r *RPCRequest) getPersonalSignParams() (*PersonalSignParams, error) {
return nil, ErrInvalidParamsStructure return nil, ErrInvalidParamsStructure
} }
challengeIndex := 0
addressIndex := 1
if r.Method == Method_SignTypedDataV4 {
challengeIndex = 1
addressIndex = 0
}
// Extract the Challenge and Address fields from paramsArray // Extract the Challenge and Address fields from paramsArray
challenge, ok := r.Params[0].(string) challenge, ok := r.Params[challengeIndex].(string)
if !ok { if !ok {
return nil, fmt.Errorf("missing or invalid 'challenge' field") return nil, fmt.Errorf("missing or invalid 'challenge' field")
} }
address, ok := r.Params[1].(string) address, ok := r.Params[addressIndex].(string)
if !ok { if !ok {
return nil, fmt.Errorf("missing or invalid 'address' field") return nil, fmt.Errorf("missing or invalid 'address' field")
} }
// Create and return the PersonalSignParams // Create and return the PersonalSignParams
return &PersonalSignParams{ return &SignParams{
Challenge: challenge, Challenge: challenge,
Address: address, Address: address,
Method: r.Method,
}, nil }, nil
} }
func (c *PersonalSignCommand) Execute(ctx context.Context, request RPCRequest) (interface{}, error) { func (c *SignCommand) Execute(ctx context.Context, request RPCRequest) (interface{}, error) {
err := request.Validate() err := request.Validate()
if err != nil { if err != nil {
return "", err return "", err
} }
params, err := request.getPersonalSignParams() params, err := request.getSignParams()
if err != nil { if err != nil {
return "", err return "", err
} }
@ -71,9 +86,9 @@ func (c *PersonalSignCommand) Execute(ctx context.Context, request RPCRequest) (
return "", ErrDAppIsNotPermittedByUser return "", ErrDAppIsNotPermittedByUser
} }
return c.ClientHandler.RequestPersonalSign(signal.ConnectorDApp{ return c.ClientHandler.RequestSign(signal.ConnectorDApp{
URL: request.URL, URL: request.URL,
Name: request.Name, Name: request.Name,
IconURL: request.IconURL, IconURL: request.IconURL,
}, params.Challenge, params.Address) }, params.Challenge, params.Address, params.Method)
} }

View File

@ -0,0 +1,275 @@
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) {
return ConstructRPCRequest("personal_sign", []interface{}{challenge, address}, &dApp)
}
func prepareTypedDataV4SignRequest(dApp signal.ConnectorDApp, challenge, address string) (RPCRequest, error) {
return ConstructRPCRequest("eth_signTypedData_v4", []interface{}{address, challenge}, &dApp)
}
func TestFailToPersonalSignWithMissingDAppFields(t *testing.T) {
state, close := setupCommand(t, Method_PersonalSign)
t.Cleanup(close)
// Missing DApp fields
request, err := ConstructRPCRequest("personal_sign", []interface{}{}, nil)
assert.NoError(t, err)
result, err := state.cmd.Execute(state.ctx, request)
assert.Equal(t, ErrRequestMissingDAppData, err)
assert.Empty(t, result)
}
func TestFailToPersonalSignForUnpermittedDApp(t *testing.T) {
state, close := setupCommand(t, Method_PersonalSign)
t.Cleanup(close)
request, err := preparePersonalSignRequest(testDAppData,
"0x506c65617365207369676e2074686973206d65737361676520746f20636f6e6669726d20796f7572206964656e746974792e",
"0x4B0897b0513FdBeEc7C469D9aF4fA6C0752aBea7",
)
assert.NoError(t, err)
result, err := state.cmd.Execute(state.ctx, request)
assert.Equal(t, ErrDAppIsNotPermittedByUser, err)
assert.Empty(t, result)
}
func TestFailToPersonalSignWithoutParams(t *testing.T) {
state, close := setupCommand(t, Method_PersonalSign)
t.Cleanup(close)
request, err := ConstructRPCRequest("personal_sign", nil, &testDAppData)
assert.NoError(t, err)
result, err := state.cmd.Execute(state.ctx, request)
assert.Equal(t, ErrEmptyRPCParams, err)
assert.Empty(t, result)
}
func TestFailToPersonalSignWithSignalTimout(t *testing.T) {
state, close := setupCommand(t, Method_PersonalSign)
t.Cleanup(close)
err := PersistDAppData(state.walletDb, 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 = state.cmd.Execute(state.ctx, request)
assert.Equal(t, ErrWalletResponseTimeout, err)
WalletResponseMaxInterval = backupWalletResponseMaxInterval
}
func TestPersonalSignWithSignalAccepted(t *testing.T) {
state, close := setupCommand(t, Method_PersonalSign)
t.Cleanup(close)
fakedSignature := "0x051"
err := PersistDAppData(state.walletDb, 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.EventConnectorSign:
var ev signal.ConnectorSignSignal
err := json.Unmarshal(evt.Event, &ev)
assert.NoError(t, err)
assert.Equal(t, ev.Challenge, challenge)
assert.Equal(t, ev.Address, address)
err = state.handler.SignAccepted(SignAcceptedArgs{
Signature: fakedSignature,
RequestID: ev.RequestID,
})
assert.NoError(t, err)
}
}))
t.Cleanup(signal.ResetMobileSignalHandler)
response, err := state.cmd.Execute(state.ctx, request)
assert.NoError(t, err)
assert.Equal(t, response, fakedSignature)
}
func TestPersonalSignWithSignalRejected(t *testing.T) {
state, close := setupCommand(t, Method_PersonalSign)
t.Cleanup(close)
err := PersistDAppData(state.walletDb, 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.EventConnectorSign:
var ev signal.ConnectorSignSignal
err := json.Unmarshal(evt.Event, &ev)
assert.NoError(t, err)
err = state.handler.SignRejected(RejectedArgs{
RequestID: ev.RequestID,
})
assert.NoError(t, err)
}
}))
t.Cleanup(signal.ResetMobileSignalHandler)
_, err = state.cmd.Execute(state.ctx, request)
assert.Equal(t, ErrSignRejectedByUser, err)
}
func TestTypedDataV4SignRequestWithSignalAccepted(t *testing.T) {
state, close := setupCommand(t, Method_SignTypedDataV4)
t.Cleanup(close)
fakedSignature := "0x051"
err := PersistDAppData(state.walletDb, testDAppData, types.Address{0x01}, uint64(0x1))
assert.NoError(t, err)
challenge := "{\"domain\":{\"chainId\":\"1\",\"name\":\"Ether Mail\",\"verifyingContract\":\"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC\",\"version\":\"1\"},\"message\":{\"contents\":\"Hello, Bob!\",\"from\":{\"name\":\"Cow\",\"wallets\":[\"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826\",\"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF\"]},\"to\":[{\"name\":\"Bob\",\"wallets\":[\"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB\",\"0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57\",\"0xB0B0b0b0b0b0B000000000000000000000000000\"]}],\"attachment\":\"0x\"},\"primaryType\":\"Mail\",\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"Group\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"members\",\"type\":\"Person[]\"}],\"Mail\":[{\"name\":\"from\",\"type\":\"Person\"},{\"name\":\"to\",\"type\":\"Person[]\"},{\"name\":\"contents\",\"type\":\"string\"},{\"name\":\"attachment\",\"type\":\"bytes\"}],\"Person\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"wallets\",\"type\":\"address[]\"}]}}"
address := "0x4B0897b0513FdBeEc7C469D9aF4fA6C0752aBea7"
request, err := prepareTypedDataV4SignRequest(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.EventConnectorSign:
var ev signal.ConnectorSignSignal
err := json.Unmarshal(evt.Event, &ev)
assert.NoError(t, err)
assert.Equal(t, ev.Challenge, challenge)
assert.Equal(t, ev.Address, address)
err = state.handler.SignAccepted(SignAcceptedArgs{
Signature: fakedSignature,
RequestID: ev.RequestID,
})
assert.NoError(t, err)
}
}))
t.Cleanup(signal.ResetMobileSignalHandler)
response, err := state.cmd.Execute(state.ctx, request)
assert.NoError(t, err)
assert.Equal(t, response, fakedSignature)
}
func TestTypedDataV4SignRequestWithSignalRejected(t *testing.T) {
state, close := setupCommand(t, Method_SignTypedDataV4)
t.Cleanup(close)
err := PersistDAppData(state.walletDb, testDAppData, types.Address{0x01}, uint64(0x1))
assert.NoError(t, err)
challenge := "{\"domain\":{\"chainId\":\"1\",\"name\":\"Ether Mail\",\"verifyingContract\":\"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC\",\"version\":\"1\"},\"message\":{\"contents\":\"Hello, Bob!\",\"from\":{\"name\":\"Cow\",\"wallets\":[\"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826\",\"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF\"]},\"to\":[{\"name\":\"Bob\",\"wallets\":[\"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB\",\"0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57\",\"0xB0B0b0b0b0b0B000000000000000000000000000\"]}],\"attachment\":\"0x\"},\"primaryType\":\"Mail\",\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"Group\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"members\",\"type\":\"Person[]\"}],\"Mail\":[{\"name\":\"from\",\"type\":\"Person\"},{\"name\":\"to\",\"type\":\"Person[]\"},{\"name\":\"contents\",\"type\":\"string\"},{\"name\":\"attachment\",\"type\":\"bytes\"}],\"Person\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"wallets\",\"type\":\"address[]\"}]}}"
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.EventConnectorSign:
var ev signal.ConnectorSignSignal
err := json.Unmarshal(evt.Event, &ev)
assert.NoError(t, err)
err = state.handler.SignRejected(RejectedArgs{
RequestID: ev.RequestID,
})
assert.NoError(t, err)
}
}))
t.Cleanup(signal.ResetMobileSignalHandler)
_, err = state.cmd.Execute(state.ctx, request)
assert.Equal(t, ErrSignRejectedByUser, err)
}
func TestUnsupportedSignMethod(t *testing.T) {
state, close := setupCommand(t, Method_PersonalSign)
t.Cleanup(close)
err := PersistDAppData(state.walletDb, testDAppData, types.Address{0x01}, uint64(0x1))
assert.NoError(t, err)
challenge := "0x506c65617365207369676e2074686973206d65737361676520746f20636f6e6669726d20796f7572206964656e746974792e"
address := "0x4B0897b0513FdBeEc7C469D9aF4fA6C0752aBea7"
request, err := preparePersonalSignRequest(testDAppData, challenge, address)
assert.NoError(t, err)
request.Method = "eth_signTypedData"
fakedSignature := "0x051"
signal.SetMobileSignalHandler(signal.MobileSignalHandler(func(s []byte) {
var evt EventType
err := json.Unmarshal(s, &evt)
assert.NoError(t, err)
switch evt.Type {
case signal.EventConnectorSign:
var ev signal.ConnectorSignSignal
err := json.Unmarshal(evt.Event, &ev)
assert.NoError(t, err)
assert.Equal(t, ev.Challenge, challenge)
assert.Equal(t, ev.Address, address)
err = state.handler.SignAccepted(SignAcceptedArgs{
Signature: fakedSignature,
RequestID: ev.RequestID,
})
assert.NoError(t, err)
}
}))
t.Cleanup(signal.ResetMobileSignalHandler)
response, err := state.cmd.Execute(state.ctx, request)
assert.Equal(t, ErrInvalidMethod, err)
assert.Equal(t, response, "")
}

View File

@ -102,7 +102,12 @@ func setupCommand(t *testing.T, method string) (state testState, close func()) {
NetworkManager: networkManager, NetworkManager: networkManager,
} }
case Method_PersonalSign: case Method_PersonalSign:
state.cmd = &PersonalSignCommand{ state.cmd = &SignCommand{
Db: state.walletDb,
ClientHandler: state.handler,
}
case Method_SignTypedDataV4:
state.cmd = &SignCommand{
Db: state.walletDb, Db: state.walletDb,
ClientHandler: state.handler, ClientHandler: state.handler,
} }

View File

@ -60,12 +60,12 @@ func TestRequestAccountsSwitchChainAndSendTransactionFlow(t *testing.T) {
Hash: expectedHash, Hash: expectedHash,
}) })
assert.NoError(t, err) assert.NoError(t, err)
case signal.EventConnectorPersonalSign: case signal.EventConnectorSign:
var ev signal.ConnectorPersonalSignSignal var ev signal.ConnectorSignSignal
err := json.Unmarshal(evt.Event, &ev) err := json.Unmarshal(evt.Event, &ev)
assert.NoError(t, err) assert.NoError(t, err)
err = state.api.PersonalSignAccepted(commands.PersonalSignAcceptedArgs{ err = state.api.SignAccepted(commands.SignAcceptedArgs{
RequestID: ev.RequestID, RequestID: ev.RequestID,
Signature: expectedSignature, Signature: expectedSignature,
}) })

View File

@ -7,7 +7,7 @@ import (
const ( const (
EventConnectorSendRequestAccounts = "connector.sendRequestAccounts" EventConnectorSendRequestAccounts = "connector.sendRequestAccounts"
EventConnectorSendTransaction = "connector.sendTransaction" EventConnectorSendTransaction = "connector.sendTransaction"
EventConnectorPersonalSign = "connector.personalSign" EventConnectorSign = "connector.sign"
EventConnectorDAppPermissionGranted = "connector.dAppPermissionGranted" EventConnectorDAppPermissionGranted = "connector.dAppPermissionGranted"
EventConnectorDAppPermissionRevoked = "connector.dAppPermissionRevoked" EventConnectorDAppPermissionRevoked = "connector.dAppPermissionRevoked"
EventConnectorDAppChainIdSwitched = "connector.dAppChainIdSwitched" EventConnectorDAppChainIdSwitched = "connector.dAppChainIdSwitched"
@ -39,11 +39,12 @@ type ConnectorSendDappPermissionGrantedSignal struct {
SharedAccount types.Address `json:"sharedAccount"` SharedAccount types.Address `json:"sharedAccount"`
} }
type ConnectorPersonalSignSignal struct { type ConnectorSignSignal struct {
ConnectorDApp ConnectorDApp
RequestID string `json:"requestId"` RequestID string `json:"requestId"`
Challenge string `json:"challenge"` Challenge string `json:"challenge"`
Address string `json:"address"` Address string `json:"address"`
Method string `json:"method"`
} }
type ConnectorDAppChainIdSwitchedSignal struct { type ConnectorDAppChainIdSwitchedSignal struct {
@ -67,12 +68,13 @@ func SendConnectorSendTransaction(dApp ConnectorDApp, chainID uint64, txArgs str
}) })
} }
func SendConnectorPersonalSign(dApp ConnectorDApp, requestID, challenge, address string) { func SendConnectorSign(dApp ConnectorDApp, requestID, challenge, address string, method string) {
send(EventConnectorPersonalSign, ConnectorPersonalSignSignal{ send(EventConnectorSign, ConnectorSignSignal{
ConnectorDApp: dApp, ConnectorDApp: dApp,
RequestID: requestID, RequestID: requestID,
Challenge: challenge, Challenge: challenge,
Address: address, Address: address,
Method: method,
}) })
} }