From 9579c717e91d3f02c736cb14f784ac0038aead9b Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 16 May 2019 08:41:49 +0200 Subject: [PATCH] core/signer, clef: improve ui-test flow, fix errors in uint handling for eip-712 --- cmd/clef/main.go | 37 +- common/math/big.go | 7 + signer/core/api.go | 1 - signer/core/signed_data.go | 95 +-- signer/core/signed_data_test.go | 541 ++---------------- signer/core/testdata/README.md | 5 + signer/core/testdata/arrays-1.json | 60 ++ signer/core/testdata/custom_arraytype.json | 54 ++ signer/core/testdata/eip712.json | 76 +++ .../testdata/expfail_arraytype_overload.json | 67 +++ .../core/testdata/expfail_datamismatch_1.json | 64 +++ signer/core/testdata/expfail_extradata-1.json | 76 +++ signer/core/testdata/expfail_extradata-2.json | 77 +++ .../testdata/expfail_malformeddomainkeys.json | 64 +++ .../testdata/expfail_nonexistant_type.json | 64 +++ .../core/testdata/expfail_toolargeuint.json | 38 ++ .../core/testdata/expfail_toolargeuint2.json | 38 ++ .../testdata/expfail_unconvertiblefloat.json | 38 ++ .../testdata/expfail_unconvertiblefloat2.json | 38 ++ 19 files changed, 913 insertions(+), 527 deletions(-) create mode 100644 signer/core/testdata/README.md create mode 100644 signer/core/testdata/arrays-1.json create mode 100644 signer/core/testdata/custom_arraytype.json create mode 100644 signer/core/testdata/eip712.json create mode 100644 signer/core/testdata/expfail_arraytype_overload.json create mode 100644 signer/core/testdata/expfail_datamismatch_1.json create mode 100644 signer/core/testdata/expfail_extradata-1.json create mode 100644 signer/core/testdata/expfail_extradata-2.json create mode 100644 signer/core/testdata/expfail_malformeddomainkeys.json create mode 100644 signer/core/testdata/expfail_nonexistant_type.json create mode 100644 signer/core/testdata/expfail_toolargeuint.json create mode 100644 signer/core/testdata/expfail_toolargeuint2.json create mode 100644 signer/core/testdata/expfail_unconvertiblefloat.json create mode 100644 signer/core/testdata/expfail_unconvertiblefloat2.json diff --git a/cmd/clef/main.go b/cmd/clef/main.go index fecfcafaff..02498cc22a 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -33,6 +33,7 @@ import ( "path/filepath" "runtime" "strings" + "time" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" @@ -638,6 +639,10 @@ func testExternalUI(api *core.SignerAPI) { errs := make([]string, 0) a := common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef") + addErr := func(errStr string) { + log.Info("Test error", "error", errStr) + errs = append(errs, errStr) + } queryUser := func(q string) string { resp, err := api.UI.OnInputRequired(core.UserInputRequest{ @@ -645,36 +650,39 @@ func testExternalUI(api *core.SignerAPI) { Prompt: q, }) if err != nil { - errs = append(errs, err.Error()) + addErr(err.Error()) } return resp.Text } expectResponse := func(testcase, question, expect string) { if got := queryUser(question); got != expect { - errs = append(errs, fmt.Sprintf("%s: got %v, expected %v", testcase, got, expect)) + addErr(fmt.Sprintf("%s: got %v, expected %v", testcase, got, expect)) } } expectApprove := func(testcase string, err error) { if err == nil || err == accounts.ErrUnknownAccount { return } - errs = append(errs, fmt.Sprintf("%v: expected no error, got %v", testcase, err.Error())) + addErr(fmt.Sprintf("%v: expected no error, got %v", testcase, err.Error())) } expectDeny := func(testcase string, err error) { if err == nil || err != core.ErrRequestDenied { - errs = append(errs, fmt.Sprintf("%v: expected ErrRequestDenied, got %v", testcase, err)) + addErr(fmt.Sprintf("%v: expected ErrRequestDenied, got %v", testcase, err)) } } - - // Test display of info and error + var delay = 1 * time.Second + //Test display of info and error { api.UI.ShowInfo("If you see this message, enter 'yes' to next question") + time.Sleep(delay) expectResponse("showinfo", "Did you see the message? [yes/no]", "yes") api.UI.ShowError("If you see this message, enter 'yes' to the next question") + time.Sleep(delay) expectResponse("showerror", "Did you see the message? [yes/no]", "yes") } { // Sign data test - clique header api.UI.ShowInfo("Please approve the next request for signing a clique header") + time.Sleep(delay) cliqueHeader := types.Header{ common.HexToHash("0000H45H"), common.HexToHash("0000H45H"), @@ -700,14 +708,27 @@ func testExternalUI(api *core.SignerAPI) { _, err = api.SignData(ctx, accounts.MimetypeClique, *addr, hexutil.Encode(cliqueRlp)) expectApprove("signdata - clique header", err) } + { // Sign data test - typed data + api.UI.ShowInfo("Please approve the next request for signing EIP-712 typed data") + time.Sleep(delay) + addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899") + data := `{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Person":[{"name":"name","type":"string"},{"name":"test","type":"uint8"},{"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":"1","verifyingContract":"0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"},"message":{"from":{"name":"Cow","test":"3","wallet":"0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},"to":{"name":"Bob","wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB","test":"2"},"contents":"Hello, Bob!"}}` + //_, err := api.SignData(ctx, accounts.MimetypeTypedData, *addr, hexutil.Encode([]byte(data))) + var typedData core.TypedData + err := json.Unmarshal([]byte(data), &typedData) + _, err = api.SignTypedData(ctx, *addr, typedData) + expectApprove("sign 712 typed data", err) + } { // Sign data test - plain text api.UI.ShowInfo("Please approve the next request for signing text") + time.Sleep(delay) addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899") _, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world"))) expectApprove("signdata - text", err) } { // Sign data test - plain text reject api.UI.ShowInfo("Please deny the next request for signing text") + time.Sleep(delay) addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899") _, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world"))) expectDeny("signdata - text", err) @@ -715,6 +736,7 @@ func testExternalUI(api *core.SignerAPI) { { // Sign transaction api.UI.ShowInfo("Please reject next transaction") + time.Sleep(delay) data := hexutil.Bytes([]byte{}) to := common.NewMixedcaseAddress(a) tx := core.SendTxArgs{ @@ -733,16 +755,19 @@ func testExternalUI(api *core.SignerAPI) { } { // Listing api.UI.ShowInfo("Please reject listing-request") + time.Sleep(delay) _, err := api.List(ctx) expectDeny("list", err) } { // Import api.UI.ShowInfo("Please reject new account-request") + time.Sleep(delay) _, err := api.New(ctx) expectDeny("newaccount", err) } { // Metadata api.UI.ShowInfo("Please check if you see the Origin in next listing (approve or deny)") + time.Sleep(delay) api.List(context.WithValue(ctx, "Origin", "origin.com")) expectResponse("metadata - origin", "Did you see origin (origin.com)? [yes/no] ", "yes") } diff --git a/common/math/big.go b/common/math/big.go index 9d2e7946d1..d31c59af10 100644 --- a/common/math/big.go +++ b/common/math/big.go @@ -42,6 +42,13 @@ const ( // HexOrDecimal256 marshals big.Int as hex or decimal. type HexOrDecimal256 big.Int +// NewHexOrDecimal256 creates a new HexOrDecimal256 +func NewHexOrDecimal256(x int64) *HexOrDecimal256 { + b := big.NewInt(x) + h := HexOrDecimal256(*b) + return &h +} + // UnmarshalText implements encoding.TextUnmarshaler. func (i *HexOrDecimal256) UnmarshalText(input []byte) error { bigint, ok := ParseBig256(string(input)) diff --git a/signer/core/api.go b/signer/core/api.go index 783aaece46..73a94634e0 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -482,7 +482,6 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth return nil, err } } - req := SignTxRequest{ Transaction: args, Meta: MetadataFromContext(ctx), diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 9dfd7b3f69..fb81955dd6 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -23,6 +23,7 @@ import ( "fmt" "math/big" "mime" + "reflect" "regexp" "sort" "strconv" @@ -109,11 +110,11 @@ type TypePriority struct { type TypedDataMessage = map[string]interface{} type TypedDataDomain struct { - Name string `json:"name"` - Version string `json:"version"` - ChainId *big.Int `json:"chainId"` - VerifyingContract string `json:"verifyingContract"` - Salt string `json:"salt"` + Name string `json:"name"` + Version string `json:"version"` + ChainId *math.HexOrDecimal256 `json:"chainId"` + VerifyingContract string `json:"verifyingContract"` + Salt string `json:"salt"` } var typedDataReferenceTypeRegexp = regexp.MustCompile(`^[A-Z](\w*)(\[\])?$`) @@ -476,10 +477,54 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter return buffer.Bytes(), nil } +func parseIntegerType(encType string, encValue interface{}) (*big.Int, error) { + length := 0 + if encType == "int" || encType == "uint" { + length = 256 + } else { + lengthStr := "" + if strings.HasPrefix(encType, "uint") { + lengthStr = strings.TrimPrefix(encType, "uint") + } else { + lengthStr = strings.TrimPrefix(encType, "int") + } + atoiSize, err := strconv.Atoi(lengthStr) + if err != nil { + return nil, fmt.Errorf("invalid size on integer: %v", lengthStr) + } + length = atoiSize + } + var b *big.Int + switch v := encValue.(type) { + case *math.HexOrDecimal256: + b = (*big.Int)(v) + case string: + var hexIntValue math.HexOrDecimal256 + if err := hexIntValue.UnmarshalText([]byte(v)); err != nil { + return nil, err + } + b = (*big.Int)(&hexIntValue) + case float64: + // JSON parses non-strings as float64. Fail if we cannot + // convert it losslessly + if float64(int64(v)) == v { + b = big.NewInt(int64(v)) + } else { + return nil, fmt.Errorf("invalid float value %v for type %v", v, encType) + } + } + if b == nil { + return nil, fmt.Errorf("invalid integer value %v/%v for type %v", encValue, reflect.TypeOf(encValue), encType) + } + if b.BitLen() > length { + return nil, fmt.Errorf("integer larger than '%v'", encType) + } + return b, nil +} + // EncodePrimitiveValue deals with the primitive values found // while searching through the typed data func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interface{}, depth int) ([]byte, error) { - switch encType { case "address": stringValue, ok := encValue.(string) @@ -527,30 +572,11 @@ func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interf } } if strings.HasPrefix(encType, "int") || strings.HasPrefix(encType, "uint") { - length := 0 - if encType == "int" || encType == "uint" { - length = 256 - } else { - lengthStr := "" - if strings.HasPrefix(encType, "uint") { - lengthStr = strings.TrimPrefix(encType, "uint") - } else { - lengthStr = strings.TrimPrefix(encType, "int") - } - atoiSize, err := strconv.Atoi(lengthStr) - if err != nil { - return nil, fmt.Errorf("invalid size on integer: %v", lengthStr) - } - length = atoiSize + b, err := parseIntegerType(encType, encValue) + if err != nil { + return nil, err } - bigIntValue, ok := encValue.(*big.Int) - if bigIntValue.BitLen() > length { - return nil, fmt.Errorf("integer larger than '%v'", encType) - } - if !ok { - return nil, dataMismatchError(encType, encValue) - } - return abi.U256(bigIntValue), nil + return abi.U256(b), nil } return nil, fmt.Errorf("unrecognized type '%s'", encType) @@ -728,8 +754,11 @@ func formatPrimitiveValue(encType string, encValue interface{}) string { if strings.HasPrefix(encType, "bytes") { return fmt.Sprintf("%s", encValue) } else if strings.HasPrefix(encType, "uint") || strings.HasPrefix(encType, "int") { - bigIntValue, _ := encValue.(*big.Int) - return fmt.Sprintf("%d (0x%x)", bigIntValue, bigIntValue) + b, err := parseIntegerType(encType, encValue) + if err != nil { + return fmt.Sprintf("ERROR: %v", err) + } + return fmt.Sprintf("%d (0x%x)", b, b) } return "NA" } @@ -767,7 +796,7 @@ func (t Types) validate() error { return fmt.Errorf("type '%s' cannot reference itself", typeObj.Type) } if typeObj.isReferenceType() { - if _, exist := t[typeObj.Type]; !exist { + if _, exist := t[typeObj.typeName()]; !exist { return fmt.Errorf("reference type '%s' is undefined", typeObj.Type) } if !typedDataReferenceTypeRegexp.MatchString(typeObj.Type) { @@ -895,7 +924,7 @@ func isPrimitiveTypeValid(primitiveType string) bool { // validate checks if the given domain is valid, i.e. contains at least // the minimum viable keys and values func (domain *TypedDataDomain) validate() error { - if domain.ChainId == big.NewInt(0) { + if domain.ChainId == nil { return errors.New("chainId must be specified according to EIP-155") } diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index f7d1994227..bf5298b2b0 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -20,12 +20,16 @@ import ( "context" "encoding/json" "fmt" - "math/big" + "io/ioutil" + "path" + "strings" "testing" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/signer/core" ) @@ -128,7 +132,7 @@ var jsonTypedData = ` "domain": { "name": "Ether Mail", "version": "1", - "chainId": 1, + "chainId": "1", "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" }, "message": { @@ -151,7 +155,7 @@ const primaryType = "Mail" var domainStandard = core.TypedDataDomain{ "Ether Mail", "1", - big.NewInt(1), + math.NewHexOrDecimal256(1), "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", "", } @@ -241,7 +245,6 @@ func TestDomainChainId(t *testing.T) { if _, ok := withoutChainID.Domain.Map()["chainId"]; ok { t.Errorf("Expected the chainId key to not be present in the domain map") } - withChainID := core.TypedData{ Types: core.Types{ "EIP712Domain": []core.Type{ @@ -251,7 +254,7 @@ func TestDomainChainId(t *testing.T) { }, Domain: core.TypedDataDomain{ Name: "test", - ChainId: big.NewInt(1), + ChainId: math.NewHexOrDecimal256(1), }, } @@ -310,488 +313,6 @@ func TestEncodeData(t *testing.T) { } } -func TestMalformedDomainkeys(t *testing.T) { - // Verifies that malformed domain keys are properly caught: - //{ - // "name": "Ether Mail", - // "version": "1", - // "chainId": 1, - // "vxerifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - //} - jsonTypedData := ` - { - "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": 1, - "vxerifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "from": { - "name": "Cow", - "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to": { - "name": "Bob", - "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents": "Hello, Bob!" - } - } -` - var malformedDomainTypedData core.TypedData - err := json.Unmarshal([]byte(jsonTypedData), &malformedDomainTypedData) - if err != nil { - t.Fatalf("unmarshalling failed '%v'", err) - } - _, err = malformedDomainTypedData.HashStruct("EIP712Domain", malformedDomainTypedData.Domain.Map()) - if err == nil || err.Error() != "provided data '' doesn't match type 'address'" { - t.Errorf("Expected `provided data '' doesn't match type 'address'`, got '%v'", err) - } -} - -func TestMalformedTypesAndExtradata(t *testing.T) { - // Verifies several quirks - // 1. Using dynamic types and only validating the prefix: - //{ - // "name": "chainId", - // "type": "uint256 ... and now for something completely different" - //} - // 2. Extra data in message: - //{ - // "blahonga": "zonk bonk" - //} - jsonTypedData := ` - { - "types": { - "EIP712Domain": [ - { - "name": "name", - "type": "string" - }, - { - "name": "version", - "type": "string" - }, - { - "name": "chainId", - "type": "uint256 ... and now for something completely different" - }, - { - "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": 1, - "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "from": { - "name": "Cow", - "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to": { - "name": "Bob", - "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents": "Hello, Bob!" - } - } -` - var malformedTypedData core.TypedData - err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) - if err != nil { - t.Fatalf("unmarshalling failed '%v'", err) - } - - malformedTypedData.Types["EIP712Domain"][2].Type = "uint256" - malformedTypedData.Message["blahonga"] = "zonk bonk" - _, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message) - if err == nil || err.Error() != "there is extra data provided in the message" { - t.Errorf("Expected `there is extra data provided in the message`, got '%v'", err) - } -} - -func TestTypeMismatch(t *testing.T) { - // Verifies that: - // 1. Mismatches between the given type and data, i.e. `Person` and - // the data item is a string, are properly caught: - //{ - // "name": "contents", - // "type": "Person" - //}, - //{ - // "contents": "Hello, Bob!" <-- string not "Person" - //} - // 2. Nonexistent types are properly caught: - //{ - // "name": "contents", - // "type": "Blahonga" - //} - jsonTypedData := ` - { - "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": "Person" - } - ] - }, - "primaryType": "Mail", - "domain": { - "name": "Ether Mail", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "from": { - "name": "Cow", - "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to": { - "name": "Bob", - "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents": "Hello, Bob!" - } - } -` - var mismatchTypedData core.TypedData - err := json.Unmarshal([]byte(jsonTypedData), &mismatchTypedData) - if err != nil { - t.Fatalf("unmarshalling failed '%v'", err) - } - _, err = mismatchTypedData.HashStruct(mismatchTypedData.PrimaryType, mismatchTypedData.Message) - if err.Error() != "provided data 'Hello, Bob!' doesn't match type 'Person'" { - t.Errorf("Expected `provided data 'Hello, Bob!' doesn't match type 'Person'`, got '%v'", err) - } - - mismatchTypedData.Types["Mail"][2].Type = "Blahonga" - _, err = mismatchTypedData.HashStruct(mismatchTypedData.PrimaryType, mismatchTypedData.Message) - if err == nil || err.Error() != "reference type 'Blahonga' is undefined" { - t.Fatalf("Expected `reference type 'Blahonga' is undefined`, got '%v'", err) - } -} - -func TestTypeOverflow(t *testing.T) { - // Verifies data that doesn't fit into it: - //{ - // "test": 65536 <-- test defined as uint8 - //} - var overflowTypedData core.TypedData - err := json.Unmarshal([]byte(jsonTypedData), &overflowTypedData) - if err != nil { - t.Fatalf("unmarshalling failed '%v'", err) - } - // Set test to something outside uint8 - (overflowTypedData.Message["from"]).(map[string]interface{})["test"] = big.NewInt(65536) - - _, err = overflowTypedData.HashStruct(overflowTypedData.PrimaryType, overflowTypedData.Message) - if err == nil || err.Error() != "integer larger than 'uint8'" { - t.Fatalf("Expected `integer larger than 'uint8'`, got '%v'", err) - } - - (overflowTypedData.Message["from"]).(map[string]interface{})["test"] = big.NewInt(3) - (overflowTypedData.Message["to"]).(map[string]interface{})["test"] = big.NewInt(4) - - _, err = overflowTypedData.HashStruct(overflowTypedData.PrimaryType, overflowTypedData.Message) - if err != nil { - t.Fatalf("Expected no err, got '%v'", err) - } -} - -func TestArray(t *testing.T) { - // Makes sure that arrays work fine - //{ - // "type": "address[]" - //}, - //{ - // "type": "string[]" - //}, - //{ - // "type": "uint16[]", - //} - - jsonTypedData := ` - { - "types": { - "EIP712Domain": [ - { - "name": "name", - "type": "string" - }, - { - "name": "version", - "type": "string" - }, - { - "name": "chainId", - "type": "uint256" - }, - { - "name": "verifyingContract", - "type": "address" - } - ], - "Foo": [ - { - "name": "bar", - "type": "address[]" - } - ] - }, - "primaryType": "Foo", - "domain": { - "name": "Lorem", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "bar": [ - "0x0000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000002", - "0x0000000000000000000000000000000000000003" - ] - } - } - ` - var arrayTypedData core.TypedData - err := json.Unmarshal([]byte(jsonTypedData), &arrayTypedData) - if err != nil { - t.Fatalf("unmarshalling failed '%v'", err) - } - _, err = arrayTypedData.HashStruct(arrayTypedData.PrimaryType, arrayTypedData.Message) - if err != nil { - t.Fatalf("Expected no err, got '%v'", err) - } - - // Change array to string - arrayTypedData.Types["Foo"][0].Type = "string[]" - arrayTypedData.Message["bar"] = []interface{}{ - "lorem", - "ipsum", - "dolores", - } - _, err = arrayTypedData.HashStruct(arrayTypedData.PrimaryType, arrayTypedData.Message) - if err != nil { - t.Fatalf("Expected no err, got '%v'", err) - } - - // Change array to uint - arrayTypedData.Types["Foo"][0].Type = "uint[]" - arrayTypedData.Message["bar"] = []interface{}{ - big.NewInt(1955), - big.NewInt(108), - big.NewInt(44010), - } - _, err = arrayTypedData.HashStruct(arrayTypedData.PrimaryType, arrayTypedData.Message) - if err != nil { - t.Fatalf("Expected no err, got '%v'", err) - } - - // Should not work with fixed-size arrays - arrayTypedData.Types["Foo"][0].Type = "uint[3]" - _, err = arrayTypedData.HashStruct(arrayTypedData.PrimaryType, arrayTypedData.Message) - if err == nil || err.Error() != "unknown type 'uint[3]'" { - t.Fatalf("Expected `unknown type 'uint[3]'`, got '%v'", err) - } -} - -func TestCustomTypeAsArray(t *testing.T) { - var jsonTypedData = ` - { - "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" - } - ], - "Person[]": [ - { - "name": "baz", - "type": "string" - } - ], - "Mail": [ - { - "name": "from", - "type": "Person" - }, - { - "name": "to", - "type": "Person[]" - }, - { - "name": "contents", - "type": "string" - } - ] - }, - "primaryType": "Mail", - "domain": { - "name": "Ether Mail", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "from": { - "name": "Cow", - "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to": {"baz": "foo"}, - "contents": "Hello, Bob!" - } - } - -` - var malformedTypedData core.TypedData - err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData) - if err != nil { - t.Fatalf("unmarshalling failed '%v'", err) - } - _, err = malformedTypedData.HashStruct("EIP712Domain", malformedTypedData.Domain.Map()) - if err != nil { - t.Errorf("Expected no error, got '%v'", err) - } -} - func TestFormatter(t *testing.T) { var d core.TypedData err := json.Unmarshal([]byte(jsonTypedData), &d) @@ -806,3 +327,49 @@ func TestFormatter(t *testing.T) { j, _ := json.Marshal(formatted) fmt.Printf("'%v'\n", string(j)) } + +func sign(typedData core.TypedData) ([]byte, []byte, error) { + domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) + if err != nil { + return nil, nil, err + } + typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) + if err != nil { + return nil, nil, err + } + rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash))) + sighash := crypto.Keccak256(rawData) + return typedDataHash, sighash, nil +} + +func TestJsonFiles(t *testing.T) { + testfiles, err := ioutil.ReadDir("testdata/") + if err != nil { + t.Fatalf("failed reading files: %v", err) + } + for i, fInfo := range testfiles { + if !strings.HasSuffix(fInfo.Name(), "json") { + continue + } + expectedFailure := strings.HasPrefix(fInfo.Name(), "expfail") + data, err := ioutil.ReadFile(path.Join("testdata", fInfo.Name())) + if err != nil { + t.Errorf("Failed to read file %v: %v", fInfo.Name(), err) + continue + } + var typedData core.TypedData + err = json.Unmarshal([]byte(data), &typedData) + if err != nil { + t.Errorf("Test %d, file %v, json unmarshalling failed: %v", i, fInfo.Name(), err) + continue + } + _, _, err = sign(typedData) + fmt.Printf("Error %v\n", err) + if err != nil && !expectedFailure { + t.Errorf("Test %d failed, file %v: %v", i, fInfo.Name(), err) + } + if expectedFailure && err == nil { + t.Errorf("Test %d succeeded (expected failure), file %v: %v", i, fInfo.Name(), err) + } + } +} diff --git a/signer/core/testdata/README.md b/signer/core/testdata/README.md new file mode 100644 index 0000000000..f425450a06 --- /dev/null +++ b/signer/core/testdata/README.md @@ -0,0 +1,5 @@ +### EIP 712 tests + +These tests are json files which are converted into eip-712 typed data. +All files are expected to be proper json, and tests will fail if they are not. +Files that begin with `expfail' are expected to not pass the hashstruct construction. diff --git a/signer/core/testdata/arrays-1.json b/signer/core/testdata/arrays-1.json new file mode 100644 index 0000000000..fea82b42c6 --- /dev/null +++ b/signer/core/testdata/arrays-1.json @@ -0,0 +1,60 @@ +{ + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Foo": [ + { + "name": "addys", + "type": "address[]" + }, + { + "name": "stringies", + "type": "string[]" + }, + { + "name": "inties", + "type": "uint[]" + } + ] + }, + "primaryType": "Foo", + "domain": { + "name": "Lorem", + "version": "1", + "chainId": "1", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "addys": [ + "0x0000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000003" + ], + "stringies": [ + "lorem", + "ipsum", + "dolores" + ], + "inties": [ + "0x0000000000000000000000000000000000000001", + "3", + 4.0 + ] + } +} diff --git a/signer/core/testdata/custom_arraytype.json b/signer/core/testdata/custom_arraytype.json new file mode 100644 index 0000000000..078de88c22 --- /dev/null +++ b/signer/core/testdata/custom_arraytype.json @@ -0,0 +1,54 @@ +{ + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Person": [ + { + "name": "name", + "type": "string" + } + ], + "Mail": [ + { + "name": "from", + "type": "Person" + }, + { + "name": "to", + "type": "Person[]" + }, + { + "name": "contents", + "type": "string" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": "1", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { "name": "Cow"}, + "to": [{ "name": "Moose"},{ "name": "Goose"}], + "contents": "Hello, Bob!" + } +} diff --git a/signer/core/testdata/eip712.json b/signer/core/testdata/eip712.json new file mode 100644 index 0000000000..7b1cb8ae2d --- /dev/null +++ b/signer/core/testdata/eip712.json @@ -0,0 +1,76 @@ +{ + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Person": [ + { + "name": "name", + "type": "string" + }, + { + "name": "test", + "type": "uint8" + }, + { + "name": "test2", + "type": "uint8" + }, + { + "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": "1", + "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "test": "3", + "test2": 5.0, + "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "test": "0", + "test2": 5, + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } +} diff --git a/signer/core/testdata/expfail_arraytype_overload.json b/signer/core/testdata/expfail_arraytype_overload.json new file mode 100644 index 0000000000..786487f100 --- /dev/null +++ b/signer/core/testdata/expfail_arraytype_overload.json @@ -0,0 +1,67 @@ +{ + "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" + } + ], + "Person[]": [ + { + "name": "baz", + "type": "string" + } + ], + "Mail": [ + { + "name": "from", + "type": "Person" + }, + { + "name": "to", + "type": "Person[]" + }, + { + "name": "contents", + "type": "string" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": "1", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": {"baz": "foo"}, + "contents": "Hello, Bob!" + } +} diff --git a/signer/core/testdata/expfail_datamismatch_1.json b/signer/core/testdata/expfail_datamismatch_1.json new file mode 100644 index 0000000000..d19d470d1e --- /dev/null +++ b/signer/core/testdata/expfail_datamismatch_1.json @@ -0,0 +1,64 @@ +{ + "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": "Person" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": "1", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } +} diff --git a/signer/core/testdata/expfail_extradata-1.json b/signer/core/testdata/expfail_extradata-1.json new file mode 100644 index 0000000000..fd704209bc --- /dev/null +++ b/signer/core/testdata/expfail_extradata-1.json @@ -0,0 +1,76 @@ +{ + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256 ... and now for something completely different" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Person": [ + { + "name": "name", + "type": "string" + }, + { + "name": "test", + "type": "uint8" + }, + { + "name": "test2", + "type": "uint8" + }, + { + "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": "1", + "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "test": "3", + "test2": 5.0, + "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "test": "0", + "test2": 5, + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } +} diff --git a/signer/core/testdata/expfail_extradata-2.json b/signer/core/testdata/expfail_extradata-2.json new file mode 100644 index 0000000000..10f91c23af --- /dev/null +++ b/signer/core/testdata/expfail_extradata-2.json @@ -0,0 +1,77 @@ +{ + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Person": [ + { + "name": "name", + "type": "string" + }, + { + "name": "test", + "type": "uint8" + }, + { + "name": "test2", + "type": "uint8" + }, + { + "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": "1", + "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "blahonga": "zonk bonk", + "from": { + "name": "Cow", + "test": "3", + "test2": 5.0, + "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "test": "0", + "test2": 5, + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } +} diff --git a/signer/core/testdata/expfail_malformeddomainkeys.json b/signer/core/testdata/expfail_malformeddomainkeys.json new file mode 100644 index 0000000000..354b3cc859 --- /dev/null +++ b/signer/core/testdata/expfail_malformeddomainkeys.json @@ -0,0 +1,64 @@ +{ + "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": "1", + "vFAILFAILerifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } +} diff --git a/signer/core/testdata/expfail_nonexistant_type.json b/signer/core/testdata/expfail_nonexistant_type.json new file mode 100644 index 0000000000..d06bc20b9f --- /dev/null +++ b/signer/core/testdata/expfail_nonexistant_type.json @@ -0,0 +1,64 @@ +{ + "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": "Blahonga" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": "1", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } +} diff --git a/signer/core/testdata/expfail_toolargeuint.json b/signer/core/testdata/expfail_toolargeuint.json new file mode 100644 index 0000000000..9854b65b17 --- /dev/null +++ b/signer/core/testdata/expfail_toolargeuint.json @@ -0,0 +1,38 @@ +{ + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Mail": [ + { + "name": "test", + "type": "uint8" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": "1", + "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "test":"257" + } +} diff --git a/signer/core/testdata/expfail_toolargeuint2.json b/signer/core/testdata/expfail_toolargeuint2.json new file mode 100644 index 0000000000..c63ed41f9c --- /dev/null +++ b/signer/core/testdata/expfail_toolargeuint2.json @@ -0,0 +1,38 @@ +{ + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Mail": [ + { + "name": "test", + "type": "uint8" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": "1", + "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "test":257 + } +} diff --git a/signer/core/testdata/expfail_unconvertiblefloat.json b/signer/core/testdata/expfail_unconvertiblefloat.json new file mode 100644 index 0000000000..8229a333ca --- /dev/null +++ b/signer/core/testdata/expfail_unconvertiblefloat.json @@ -0,0 +1,38 @@ +{ + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Mail": [ + { + "name": "test", + "type": "uint8" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": "1", + "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "test":"255.3" + } +} diff --git a/signer/core/testdata/expfail_unconvertiblefloat2.json b/signer/core/testdata/expfail_unconvertiblefloat2.json new file mode 100644 index 0000000000..59e6d38d24 --- /dev/null +++ b/signer/core/testdata/expfail_unconvertiblefloat2.json @@ -0,0 +1,38 @@ +{ + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Mail": [ + { + "name": "test", + "type": "uint8" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": "1", + "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "test": 255.3 + } +}