mirror of
https://github.com/status-im/status-go.git
synced 2025-01-30 16:38:21 +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"
|
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
||||||
signercore "github.com/ethereum/go-ethereum/signer/core/apitypes"
|
signercore "github.com/ethereum/go-ethereum/signer/core/apitypes"
|
||||||
"github.com/status-im/status-go/account"
|
"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/eth-node/types"
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
"github.com/status-im/status-go/rpc/network"
|
"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)
|
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) {
|
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)
|
account, err := api.getVerifiedWalletAccount(address, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.HexBytes{}, err
|
return types.HexBytes{}, err
|
||||||
@ -874,6 +887,22 @@ func (api *API) SignTypedDataV4(typedJson string, address string, password strin
|
|||||||
return types.HexBytes(sig), err
|
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 {
|
func (api *API) RestartWalletReloadTimer(ctx context.Context) error {
|
||||||
return api.s.reader.Restart()
|
return api.s.reader.Restart()
|
||||||
}
|
}
|
||||||
|
@ -21,3 +21,11 @@ func TestAPI_GetWalletConnectActiveSessions(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 0, len(sessions))
|
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
|
package walletconnect
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"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/multiaccounts/accounts"
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
|
"github.com/status-im/status-go/services/typeddata"
|
||||||
"github.com/status-im/status-go/services/wallet/walletevent"
|
"github.com/status-im/status-go/services/wallet/walletevent"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -247,3 +253,35 @@ func caip10Accounts(accounts []*accounts.Account, chains []uint64) []string {
|
|||||||
}
|
}
|
||||||
return addresses
|
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
|
package walletconnect
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"encoding/json"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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/eth-node/types"
|
||||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func getSessionJSONFor(chains []int, expiry int) string {
|
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].Name, dapps[0].Name)
|
||||||
assert.Equal(t, sessions[0].IconURL, dapps[0].IconURL)
|
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