mirror of
https://github.com/status-im/status-go.git
synced 2025-01-29 16:06:47 +00:00
feat(dapps)_: extend and improve sign
Add `wallet_SafeSignTypedDataForDApps` with support for `eth_signTypedData` and `eth_signTypedData_v4` Reject if the chain to sign doesn't matches the target chain for typed data signing Add `wallet_HashMessageForSigning` with to support hashing messages for signing in a safe way as per EIP-191 v45 and supporting to hash messages for signing on the client side (keycard) Deprecate `wallet_SignTypedDataV4`` Updates: #15361
This commit is contained in:
parent
3145ab05ff
commit
5336c47f1b
@ -16,6 +16,7 @@ import (
|
||||
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
||||
signercore "github.com/ethereum/go-ethereum/signer/core/apitypes"
|
||||
"github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/rpc/network"
|
||||
@ -853,8 +854,20 @@ func (api *API) GetWalletConnectDapps(ctx context.Context, validAtTimestamp int6
|
||||
return walletconnect.GetActiveDapps(api.s.db, validAtTimestamp, testChains)
|
||||
}
|
||||
|
||||
// signTypedDataV4 dApps use it to execute "eth_signTypedData_v4" requests
|
||||
// HashMessageEIP191 is used for hashing dApps requests for "personal_sign" and "eth_sign"
|
||||
// in a safe manner following the EIP-191 version 0x45 for signing on the client side.
|
||||
func (api *API) HashMessageEIP191(ctx context.Context, message types.HexBytes) types.Hash {
|
||||
log.Debug("wallet.api.HashMessageEIP191", "len(data)", len(message))
|
||||
safeMsg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(message), string(message))
|
||||
return crypto.Keccak256Hash([]byte(safeMsg))
|
||||
}
|
||||
|
||||
// SignTypedDataV4 dApps use it to execute "eth_signTypedData_v4" requests
|
||||
// the formatted typed data will be prefixed with \x19\x01 based on the EIP-712
|
||||
// @deprecated
|
||||
func (api *API) SignTypedDataV4(typedJson string, address string, password string) (types.HexBytes, error) {
|
||||
log.Debug("wallet.api.SignTypedDataV4", "len(typedJson)", len(typedJson), "address", address, "len(password)", len(password))
|
||||
|
||||
account, err := api.getVerifiedWalletAccount(address, password)
|
||||
if err != nil {
|
||||
return types.HexBytes{}, err
|
||||
@ -874,6 +887,22 @@ func (api *API) SignTypedDataV4(typedJson string, address string, password strin
|
||||
return types.HexBytes(sig), err
|
||||
}
|
||||
|
||||
// SafeSignTypedDataForDApps is used to execute requests for "eth_signTypedData"
|
||||
// if legacy is true else "eth_signTypedData_v4"
|
||||
// the formatted typed data won't be prefixed in case of legacy calls, as the
|
||||
// old dApps implementation expects
|
||||
// the chain is validate for both cases
|
||||
func (api *API) SafeSignTypedDataForDApps(typedJson string, address string, password string, chainID uint64, legacy bool) (types.HexBytes, error) {
|
||||
log.Debug("wallet.api.SafeSignTypedDataForDApps", "len(typedJson)", len(typedJson), "address", address, "len(password)", len(password), "chainID", chainID, "legacy", legacy)
|
||||
|
||||
account, err := api.getVerifiedWalletAccount(address, password)
|
||||
if err != nil {
|
||||
return types.HexBytes{}, err
|
||||
}
|
||||
|
||||
return walletconnect.SafeSignTypedDataForDApps(typedJson, account.AccountKey.PrivateKey, chainID, legacy)
|
||||
}
|
||||
|
||||
func (api *API) RestartWalletReloadTimer(ctx context.Context) error {
|
||||
return api.s.reader.Restart()
|
||||
}
|
||||
|
@ -21,3 +21,11 @@ func TestAPI_GetWalletConnectActiveSessions(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(sessions))
|
||||
}
|
||||
|
||||
// TestAPI_HashMessageEIP191
|
||||
func TestAPI_HashMessageEIP191(t *testing.T) {
|
||||
api := &API{}
|
||||
|
||||
res := api.HashMessageEIP191(context.Background(), []byte("test"))
|
||||
require.Equal(t, "0x4a5c5d454721bbbb25540c3317521e71c373ae36458f960d2ad46ef088110e95", res.String())
|
||||
}
|
||||
|
@ -1,18 +1,24 @@
|
||||
package walletconnect
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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/eth-node/types"
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/services/typeddata"
|
||||
"github.com/status-im/status-go/services/wallet/walletevent"
|
||||
)
|
||||
|
||||
@ -247,3 +253,35 @@ func caip10Accounts(accounts []*accounts.Account, chains []uint64) []string {
|
||||
}
|
||||
return addresses
|
||||
}
|
||||
|
||||
func SafeSignTypedDataForDApps(typedJson string, privateKey *ecdsa.PrivateKey, chainID uint64, legacy bool) (types.HexBytes, error) {
|
||||
// Parse the data for both legacy and non-legacy cases to validate the chain
|
||||
var typed typeddata.TypedData
|
||||
err := json.Unmarshal([]byte(typedJson), &typed)
|
||||
if err != nil {
|
||||
return types.HexBytes{}, err
|
||||
}
|
||||
|
||||
chain := new(big.Int).SetUint64(chainID)
|
||||
if err := typed.ValidateChainID(chain); err != nil {
|
||||
return types.HexBytes{}, err
|
||||
}
|
||||
|
||||
var sig hexutil.Bytes
|
||||
if legacy {
|
||||
sig, err = typeddata.Sign(typed, privateKey, chain)
|
||||
} else {
|
||||
var typedV4 signercore.TypedData
|
||||
err = json.Unmarshal([]byte(typedJson), &typedV4)
|
||||
if err != nil {
|
||||
return types.HexBytes{}, err
|
||||
}
|
||||
|
||||
sig, err = typeddata.SignTypedDataV4(typedV4, privateKey, chain)
|
||||
}
|
||||
if err != nil {
|
||||
return types.HexBytes{}, err
|
||||
}
|
||||
|
||||
return types.HexBytes(sig), err
|
||||
}
|
||||
|
@ -1,18 +1,20 @@
|
||||
package walletconnect
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"encoding/json"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
"github.com/status-im/status-go/params"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func getSessionJSONFor(chains []int, expiry int) string {
|
||||
@ -425,3 +427,130 @@ func Test_AddSession(t *testing.T) {
|
||||
assert.Equal(t, sessions[0].Name, dapps[0].Name)
|
||||
assert.Equal(t, sessions[0].IconURL, dapps[0].IconURL)
|
||||
}
|
||||
|
||||
func generateTypedDataJson(chainID int, skipField bool) string {
|
||||
optionalKeyValueField := ""
|
||||
if !skipField {
|
||||
optionalKeyValueField = `,"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"`
|
||||
}
|
||||
|
||||
typedData := `{
|
||||
"types": {
|
||||
"EIP712Domain": [
|
||||
{"name": "name", "type": "string"},
|
||||
{"name": "version", "type": "string"},
|
||||
{"name": "chainId", "type": "uint256"},
|
||||
{"name": "verifyingContract", "type": "address"}
|
||||
],
|
||||
"Person": [
|
||||
{"name": "name", "type": "string"},
|
||||
{"name": "wallet", "type": "address"}
|
||||
],
|
||||
"Mail": [
|
||||
{"name": "from", "type": "Person"},
|
||||
{"name": "to", "type": "Person"},
|
||||
{"name": "contents", "type": "string"}
|
||||
]
|
||||
},
|
||||
"primaryType": "Mail",
|
||||
"domain": {
|
||||
"name": "Ether Mail",
|
||||
"version": "1",
|
||||
"chainId": ` + strconv.Itoa(chainID) + `
|
||||
` + optionalKeyValueField + `
|
||||
},
|
||||
"message": {
|
||||
"from": {
|
||||
"name": "Cow",
|
||||
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
|
||||
},
|
||||
"to": {
|
||||
"name": "Bob",
|
||||
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
|
||||
},
|
||||
"contents": "Hello, Bob!"
|
||||
}
|
||||
}`
|
||||
return typedData
|
||||
}
|
||||
|
||||
func TestSafeSignTypedDataForDApps(t *testing.T) {
|
||||
// 0x4f1B9Ee595bF612480ADAF623Ec583f623ae802d
|
||||
privateKey, err := crypto.HexToECDSA("efe79ae971aa8bb612de9de7c65b9224ab1b6a69e6ec733ec92110f100c7244a")
|
||||
require.NoError(t, err)
|
||||
type args struct {
|
||||
typedJson string
|
||||
privateKey *ecdsa.PrivateKey
|
||||
chainID uint64
|
||||
legacy bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "sign_typed_data",
|
||||
args: args{
|
||||
typedJson: generateTypedDataJson(1, false),
|
||||
privateKey: privateKey,
|
||||
chainID: 1,
|
||||
legacy: false,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "sign_typed_data_legacy",
|
||||
args: args{
|
||||
typedJson: generateTypedDataJson(1, false),
|
||||
privateKey: privateKey,
|
||||
chainID: 1,
|
||||
legacy: true,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "sign_typed_data_invalid_json",
|
||||
args: args{
|
||||
typedJson: `{"invalid": "json"`,
|
||||
privateKey: privateKey,
|
||||
chainID: 1,
|
||||
legacy: false,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "sign_typed_data_invalid_chain_id",
|
||||
args: args{
|
||||
typedJson: generateTypedDataJson(1, false),
|
||||
privateKey: privateKey,
|
||||
chainID: 2,
|
||||
legacy: false,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "sign_typed_data_missing_field",
|
||||
args: args{
|
||||
typedJson: generateTypedDataJson(1, true),
|
||||
privateKey: privateKey,
|
||||
chainID: 1,
|
||||
legacy: false,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := SafeSignTypedDataForDApps(tt.args.typedJson, tt.args.privateKey, tt.args.chainID, tt.args.legacy)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("SafeSignTypedDataForDApps() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !tt.wantErr {
|
||||
require.NotEmpty(t, got)
|
||||
require.Len(t, got, 65)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user