github.com/daeglee/go-ethereum@v0.0.0-20190504220456-cad3e8d18e9b/signer/core/signed_data_test.go (about)

     1  // Copyright 2018 The go-ethereum Authors
     2  // This file is part of go-ethereum.
     3  //
     4  // go-ethereum is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // go-ethereum is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU General Public License
    15  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    16  //
    17  package core
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"math/big"
    24  	"testing"
    25  
    26  	"github.com/ethereum/go-ethereum/accounts/keystore"
    27  	"github.com/ethereum/go-ethereum/common"
    28  	"github.com/ethereum/go-ethereum/common/hexutil"
    29  )
    30  
    31  var typesStandard = Types{
    32  	"EIP712Domain": {
    33  		{
    34  			Name: "name",
    35  			Type: "string",
    36  		},
    37  		{
    38  			Name: "version",
    39  			Type: "string",
    40  		},
    41  		{
    42  			Name: "chainId",
    43  			Type: "uint256",
    44  		},
    45  		{
    46  			Name: "verifyingContract",
    47  			Type: "address",
    48  		},
    49  	},
    50  	"Person": {
    51  		{
    52  			Name: "name",
    53  			Type: "string",
    54  		},
    55  		{
    56  			Name: "wallet",
    57  			Type: "address",
    58  		},
    59  	},
    60  	"Mail": {
    61  		{
    62  			Name: "from",
    63  			Type: "Person",
    64  		},
    65  		{
    66  			Name: "to",
    67  			Type: "Person",
    68  		},
    69  		{
    70  			Name: "contents",
    71  			Type: "string",
    72  		},
    73  	},
    74  }
    75  
    76  var jsonTypedData = `
    77      {
    78        "types": {
    79          "EIP712Domain": [
    80            {
    81              "name": "name",
    82              "type": "string"
    83            },
    84            {
    85              "name": "version",
    86              "type": "string"
    87            },
    88            {
    89              "name": "chainId",
    90              "type": "uint256"
    91            },
    92            {
    93              "name": "verifyingContract",
    94              "type": "address"
    95            }
    96          ],
    97          "Person": [
    98            {
    99              "name": "name",
   100              "type": "string"
   101            },
   102            {
   103              "name": "test",
   104              "type": "uint8"
   105            },
   106            {
   107              "name": "wallet",
   108              "type": "address"
   109            }
   110          ],
   111          "Mail": [
   112            {
   113              "name": "from",
   114              "type": "Person"
   115            },
   116            {
   117              "name": "to",
   118              "type": "Person"
   119            },
   120            {
   121              "name": "contents",
   122              "type": "string"
   123            }
   124          ]
   125        },
   126        "primaryType": "Mail",
   127        "domain": {
   128          "name": "Ether Mail",
   129          "version": "1",
   130          "chainId": 1,
   131          "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
   132        },
   133        "message": {
   134          "from": {
   135            "name": "Cow",
   136  		  "test": 3,
   137            "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
   138          },
   139          "to": {
   140            "name": "Bob",
   141            "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
   142          },
   143          "contents": "Hello, Bob!"
   144        }
   145      }
   146  `
   147  
   148  const primaryType = "Mail"
   149  
   150  var domainStandard = TypedDataDomain{
   151  	"Ether Mail",
   152  	"1",
   153  	big.NewInt(1),
   154  	"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
   155  	"",
   156  }
   157  
   158  var messageStandard = map[string]interface{}{
   159  	"from": map[string]interface{}{
   160  		"name":   "Cow",
   161  		"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
   162  	},
   163  	"to": map[string]interface{}{
   164  		"name":   "Bob",
   165  		"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
   166  	},
   167  	"contents": "Hello, Bob!",
   168  }
   169  
   170  var typedData = TypedData{
   171  	Types:       typesStandard,
   172  	PrimaryType: primaryType,
   173  	Domain:      domainStandard,
   174  	Message:     messageStandard,
   175  }
   176  
   177  func TestSignData(t *testing.T) {
   178  	api, control := setup(t)
   179  	//Create two accounts
   180  	createAccount(control, api, t)
   181  	createAccount(control, api, t)
   182  	control.approveCh <- "1"
   183  	list, err := api.List(context.Background())
   184  	if err != nil {
   185  		t.Fatal(err)
   186  	}
   187  	a := common.NewMixedcaseAddress(list[0])
   188  
   189  	control.approveCh <- "Y"
   190  	control.inputCh <- "wrongpassword"
   191  	signature, err := api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
   192  	if signature != nil {
   193  		t.Errorf("Expected nil-data, got %x", signature)
   194  	}
   195  	if err != keystore.ErrDecrypt {
   196  		t.Errorf("Expected ErrLocked! '%v'", err)
   197  	}
   198  	control.approveCh <- "No way"
   199  	signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
   200  	if signature != nil {
   201  		t.Errorf("Expected nil-data, got %x", signature)
   202  	}
   203  	if err != ErrRequestDenied {
   204  		t.Errorf("Expected ErrRequestDenied! '%v'", err)
   205  	}
   206  	// text/plain
   207  	control.approveCh <- "Y"
   208  	control.inputCh <- "a_long_password"
   209  	signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
   210  	if err != nil {
   211  		t.Fatal(err)
   212  	}
   213  	if signature == nil || len(signature) != 65 {
   214  		t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
   215  	}
   216  	// data/typed
   217  	control.approveCh <- "Y"
   218  	control.inputCh <- "a_long_password"
   219  	signature, err = api.SignTypedData(context.Background(), a, typedData)
   220  	if err != nil {
   221  		t.Fatal(err)
   222  	}
   223  	if signature == nil || len(signature) != 65 {
   224  		t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
   225  	}
   226  }
   227  
   228  func TestHashStruct(t *testing.T) {
   229  	hash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
   230  	if err != nil {
   231  		t.Fatal(err)
   232  	}
   233  	mainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(hash))
   234  	if mainHash != "0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e" {
   235  		t.Errorf("Expected different hashStruct result (got %s)", mainHash)
   236  	}
   237  
   238  	hash, err = typedData.HashStruct("EIP712Domain", typedData.Domain.Map())
   239  	if err != nil {
   240  		t.Error(err)
   241  	}
   242  	domainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(hash))
   243  	if domainHash != "0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f" {
   244  		t.Errorf("Expected different domain hashStruct result (got %s)", domainHash)
   245  	}
   246  }
   247  
   248  func TestEncodeType(t *testing.T) {
   249  	domainTypeEncoding := string(typedData.EncodeType("EIP712Domain"))
   250  	if domainTypeEncoding != "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" {
   251  		t.Errorf("Expected different encodeType result (got %s)", domainTypeEncoding)
   252  	}
   253  
   254  	mailTypeEncoding := string(typedData.EncodeType(typedData.PrimaryType))
   255  	if mailTypeEncoding != "Mail(Person from,Person to,string contents)Person(string name,address wallet)" {
   256  		t.Errorf("Expected different encodeType result (got %s)", mailTypeEncoding)
   257  	}
   258  }
   259  
   260  func TestTypeHash(t *testing.T) {
   261  	mailTypeHash := fmt.Sprintf("0x%s", common.Bytes2Hex(typedData.TypeHash(typedData.PrimaryType)))
   262  	if mailTypeHash != "0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2" {
   263  		t.Errorf("Expected different typeHash result (got %s)", mailTypeHash)
   264  	}
   265  }
   266  
   267  func TestEncodeData(t *testing.T) {
   268  	hash, err := typedData.EncodeData(typedData.PrimaryType, typedData.Message, 0)
   269  	if err != nil {
   270  		t.Fatal(err)
   271  	}
   272  	dataEncoding := fmt.Sprintf("0x%s", common.Bytes2Hex(hash))
   273  	if dataEncoding != "0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8cd54f074a4af31b4411ff6a60c9719dbd559c221c8ac3492d9d872b041d703d1b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8" {
   274  		t.Errorf("Expected different encodeData result (got %s)", dataEncoding)
   275  	}
   276  }
   277  
   278  func TestMalformedDomainkeys(t *testing.T) {
   279  	// Verifies that malformed domain keys are properly caught:
   280  	//{
   281  	//	"name": "Ether Mail",
   282  	//	"version": "1",
   283  	//	"chainId": 1,
   284  	//	"vxerifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
   285  	//}
   286  	jsonTypedData := `
   287      {
   288        "types": {
   289          "EIP712Domain": [
   290            {
   291              "name": "name",
   292              "type": "string"
   293            },
   294            {
   295              "name": "version",
   296              "type": "string"
   297            },
   298            {
   299              "name": "chainId",
   300              "type": "uint256"
   301            },
   302            {
   303              "name": "verifyingContract",
   304              "type": "address"
   305            }
   306          ],
   307          "Person": [
   308            {
   309              "name": "name",
   310              "type": "string"
   311            },
   312            {
   313              "name": "wallet",
   314              "type": "address"
   315            }
   316          ],
   317          "Mail": [
   318            {
   319              "name": "from",
   320              "type": "Person"
   321            },
   322            {
   323              "name": "to",
   324              "type": "Person"
   325            },
   326            {
   327              "name": "contents",
   328              "type": "string"
   329            }
   330          ]
   331        },
   332        "primaryType": "Mail",
   333        "domain": {
   334          "name": "Ether Mail",
   335          "version": "1",
   336          "chainId": 1,
   337          "vxerifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
   338        },
   339        "message": {
   340          "from": {
   341            "name": "Cow",
   342            "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
   343          },
   344          "to": {
   345            "name": "Bob",
   346            "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
   347          },
   348          "contents": "Hello, Bob!"
   349        }
   350      }
   351  `
   352  	var malformedDomainTypedData TypedData
   353  	err := json.Unmarshal([]byte(jsonTypedData), &malformedDomainTypedData)
   354  	if err != nil {
   355  		t.Fatalf("unmarshalling failed '%v'", err)
   356  	}
   357  	_, err = malformedDomainTypedData.HashStruct("EIP712Domain", malformedDomainTypedData.Domain.Map())
   358  	if err == nil || err.Error() != "provided data '<nil>' doesn't match type 'address'" {
   359  		t.Errorf("Expected `provided data '<nil>' doesn't match type 'address'`, got '%v'", err)
   360  	}
   361  }
   362  
   363  func TestMalformedTypesAndExtradata(t *testing.T) {
   364  	// Verifies several quirks
   365  	// 1. Using dynamic types and only validating the prefix:
   366  	//{
   367  	//	"name": "chainId",
   368  	//	"type": "uint256 ... and now for something completely different"
   369  	//}
   370  	// 2. Extra data in message:
   371  	//{
   372  	//  "blahonga": "zonk bonk"
   373  	//}
   374  	jsonTypedData := `
   375      {
   376        "types": {
   377          "EIP712Domain": [
   378            {
   379              "name": "name",
   380              "type": "string"
   381            },
   382            {
   383              "name": "version",
   384              "type": "string"
   385            },
   386            {
   387              "name": "chainId",
   388              "type": "uint256 ... and now for something completely different"
   389            },
   390            {
   391              "name": "verifyingContract",
   392              "type": "address"
   393            }
   394          ],
   395          "Person": [
   396            {
   397              "name": "name",
   398              "type": "string"
   399            },
   400            {
   401              "name": "wallet",
   402              "type": "address"
   403            }
   404          ],
   405          "Mail": [
   406            {
   407              "name": "from",
   408              "type": "Person"
   409            },
   410            {
   411              "name": "to",
   412              "type": "Person"
   413            },
   414            {
   415              "name": "contents",
   416              "type": "string"
   417            }
   418          ]
   419        },
   420        "primaryType": "Mail",
   421        "domain": {
   422          "name": "Ether Mail",
   423          "version": "1",
   424          "chainId": 1,
   425          "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
   426        },
   427        "message": {
   428          "from": {
   429            "name": "Cow",
   430            "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
   431          },
   432          "to": {
   433            "name": "Bob",
   434            "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
   435          },
   436          "contents": "Hello, Bob!"
   437        }
   438      }
   439  `
   440  	var malformedTypedData TypedData
   441  	err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData)
   442  	if err != nil {
   443  		t.Fatalf("unmarshalling failed '%v'", err)
   444  	}
   445  
   446  	malformedTypedData.Types["EIP712Domain"][2].Type = "uint256"
   447  	malformedTypedData.Message["blahonga"] = "zonk bonk"
   448  	_, err = malformedTypedData.HashStruct(malformedTypedData.PrimaryType, malformedTypedData.Message)
   449  	if err == nil || err.Error() != "there is extra data provided in the message" {
   450  		t.Errorf("Expected `there is extra data provided in the message`, got '%v'", err)
   451  	}
   452  }
   453  
   454  func TestTypeMismatch(t *testing.T) {
   455  	// Verifies that:
   456  	// 1. Mismatches between the given type and data, i.e. `Person` and
   457  	// 		the data item is a string, are properly caught:
   458  	//{
   459  	//	"name": "contents",
   460  	//	"type": "Person"
   461  	//},
   462  	//{
   463  	//	"contents": "Hello, Bob!" <-- string not "Person"
   464  	//}
   465  	// 2. Nonexistent types are properly caught:
   466  	//{
   467  	//	"name": "contents",
   468  	//	"type": "Blahonga"
   469  	//}
   470  	jsonTypedData := `
   471      {
   472        "types": {
   473          "EIP712Domain": [
   474            {
   475              "name": "name",
   476              "type": "string"
   477            },
   478            {
   479              "name": "version",
   480              "type": "string"
   481            },
   482            {
   483              "name": "chainId",
   484              "type": "uint256"
   485            },
   486            {
   487              "name": "verifyingContract",
   488              "type": "address"
   489            }
   490          ],
   491          "Person": [
   492            {
   493              "name": "name",
   494              "type": "string"
   495            },
   496            {
   497              "name": "wallet",
   498              "type": "address"
   499            }
   500          ],
   501          "Mail": [
   502            {
   503              "name": "from",
   504              "type": "Person"
   505            },
   506            {
   507              "name": "to",
   508              "type": "Person"
   509            },
   510            {
   511              "name": "contents",
   512              "type": "Person"
   513            }
   514          ]
   515        },
   516        "primaryType": "Mail",
   517        "domain": {
   518          "name": "Ether Mail",
   519          "version": "1",
   520          "chainId": 1,
   521          "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
   522        },
   523        "message": {
   524          "from": {
   525            "name": "Cow",
   526            "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
   527          },
   528          "to": {
   529            "name": "Bob",
   530            "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
   531          },
   532          "contents": "Hello, Bob!"
   533        }
   534      }
   535  `
   536  	var mismatchTypedData TypedData
   537  	err := json.Unmarshal([]byte(jsonTypedData), &mismatchTypedData)
   538  	if err != nil {
   539  		t.Fatalf("unmarshalling failed '%v'", err)
   540  	}
   541  	_, err = mismatchTypedData.HashStruct(mismatchTypedData.PrimaryType, mismatchTypedData.Message)
   542  	if err.Error() != "provided data 'Hello, Bob!' doesn't match type 'Person'" {
   543  		t.Errorf("Expected `provided data 'Hello, Bob!' doesn't match type 'Person'`, got '%v'", err)
   544  	}
   545  
   546  	mismatchTypedData.Types["Mail"][2].Type = "Blahonga"
   547  	_, err = mismatchTypedData.HashStruct(mismatchTypedData.PrimaryType, mismatchTypedData.Message)
   548  	if err == nil || err.Error() != "reference type 'Blahonga' is undefined" {
   549  		t.Fatalf("Expected `reference type 'Blahonga' is undefined`, got '%v'", err)
   550  	}
   551  }
   552  
   553  func TestTypeOverflow(t *testing.T) {
   554  	// Verifies data that doesn't fit into it:
   555  	//{
   556  	//	"test": 65536 <-- test defined as uint8
   557  	//}
   558  	var overflowTypedData TypedData
   559  	err := json.Unmarshal([]byte(jsonTypedData), &overflowTypedData)
   560  	if err != nil {
   561  		t.Fatalf("unmarshalling failed '%v'", err)
   562  	}
   563  	// Set test to something outside uint8
   564  	(overflowTypedData.Message["from"]).(map[string]interface{})["test"] = big.NewInt(65536)
   565  
   566  	_, err = overflowTypedData.HashStruct(overflowTypedData.PrimaryType, overflowTypedData.Message)
   567  	if err == nil || err.Error() != "integer larger than 'uint8'" {
   568  		t.Fatalf("Expected `integer larger than 'uint8'`, got '%v'", err)
   569  	}
   570  
   571  	(overflowTypedData.Message["from"]).(map[string]interface{})["test"] = big.NewInt(3)
   572  	(overflowTypedData.Message["to"]).(map[string]interface{})["test"] = big.NewInt(4)
   573  
   574  	_, err = overflowTypedData.HashStruct(overflowTypedData.PrimaryType, overflowTypedData.Message)
   575  	if err != nil {
   576  		t.Fatalf("Expected no err, got '%v'", err)
   577  	}
   578  }
   579  
   580  func TestArray(t *testing.T) {
   581  	// Makes sure that arrays work fine
   582  	//{
   583  	//	"type": "address[]"
   584  	//},
   585  	//{
   586  	//	"type": "string[]"
   587  	//},
   588  	//{
   589  	//	"type": "uint16[]",
   590  	//}
   591  
   592  	jsonTypedData := `
   593  		{
   594  	      "types": {
   595  	        "EIP712Domain": [
   596  	          {
   597  	            "name": "name",
   598  	            "type": "string"
   599  	          },
   600  	          {
   601  	            "name": "version",
   602  	            "type": "string"
   603  	          },
   604  	          {
   605  	            "name": "chainId",
   606  	            "type": "uint256"
   607  	          },
   608  	          {
   609  	            "name": "verifyingContract",
   610  	            "type": "address"
   611  	          }
   612  	        ],
   613  	        "Foo": [
   614  	          {
   615  	            "name": "bar",
   616  	            "type": "address[]"
   617  	          }
   618  	        ]
   619  	      },
   620  	      "primaryType": "Foo",
   621  	      "domain": {
   622  	        "name": "Lorem",
   623  	        "version": "1",
   624  	        "chainId": 1,
   625  	        "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
   626  	      },
   627  	      "message": {
   628  	        "bar": [
   629  	        	"0x0000000000000000000000000000000000000001",
   630  	        	"0x0000000000000000000000000000000000000002",
   631  	        	"0x0000000000000000000000000000000000000003"
   632          	]
   633  	      }
   634  	    }
   635  	`
   636  	var arrayTypedData TypedData
   637  	err := json.Unmarshal([]byte(jsonTypedData), &arrayTypedData)
   638  	if err != nil {
   639  		t.Fatalf("unmarshalling failed '%v'", err)
   640  	}
   641  	_, err = arrayTypedData.HashStruct(arrayTypedData.PrimaryType, arrayTypedData.Message)
   642  	if err != nil {
   643  		t.Fatalf("Expected no err, got '%v'", err)
   644  	}
   645  
   646  	// Change array to string
   647  	arrayTypedData.Types["Foo"][0].Type = "string[]"
   648  	arrayTypedData.Message["bar"] = []interface{}{
   649  		"lorem",
   650  		"ipsum",
   651  		"dolores",
   652  	}
   653  	_, err = arrayTypedData.HashStruct(arrayTypedData.PrimaryType, arrayTypedData.Message)
   654  	if err != nil {
   655  		t.Fatalf("Expected no err, got '%v'", err)
   656  	}
   657  
   658  	// Change array to uint
   659  	arrayTypedData.Types["Foo"][0].Type = "uint[]"
   660  	arrayTypedData.Message["bar"] = []interface{}{
   661  		big.NewInt(1955),
   662  		big.NewInt(108),
   663  		big.NewInt(44010),
   664  	}
   665  	_, err = arrayTypedData.HashStruct(arrayTypedData.PrimaryType, arrayTypedData.Message)
   666  	if err != nil {
   667  		t.Fatalf("Expected no err, got '%v'", err)
   668  	}
   669  
   670  	// Should not work with fixed-size arrays
   671  	arrayTypedData.Types["Foo"][0].Type = "uint[3]"
   672  	_, err = arrayTypedData.HashStruct(arrayTypedData.PrimaryType, arrayTypedData.Message)
   673  	if err == nil || err.Error() != "unknown type 'uint[3]'" {
   674  		t.Fatalf("Expected `unknown type 'uint[3]'`, got '%v'", err)
   675  	}
   676  }
   677  
   678  func TestCustomTypeAsArray(t *testing.T) {
   679  	var jsonTypedData = `
   680      {
   681        "types": {
   682          "EIP712Domain": [
   683            {
   684              "name": "name",
   685              "type": "string"
   686            },
   687            {
   688  			"name": "version",
   689              "type": "string"
   690            },
   691            {
   692              "name": "chainId",
   693              "type": "uint256"
   694            },
   695            {
   696  			"name": "verifyingContract",
   697              "type": "address"
   698            }
   699          ],
   700          "Person": [
   701            {
   702  			"name": "name",
   703              "type": "string"
   704            },
   705            {
   706  			"name": "wallet",
   707              "type": "address"
   708            }
   709          ],
   710          "Person[]": [
   711            {
   712  			"name": "baz",
   713              "type": "string"
   714            }
   715  		],
   716          "Mail": [
   717            {
   718  			"name": "from",
   719              "type": "Person"
   720            },
   721            {
   722  			"name": "to",
   723              "type": "Person[]"
   724            },
   725            {
   726  			"name": "contents",
   727              "type": "string"
   728            }
   729          ]
   730        },
   731        "primaryType": "Mail",
   732        "domain": {
   733  		"name": "Ether Mail",
   734          "version": "1",
   735          "chainId": 1,
   736          "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
   737        },
   738        "message": {
   739          "from": {
   740  			"name": "Cow",
   741  			"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
   742          },
   743          "to": {"baz": "foo"},
   744          "contents": "Hello, Bob!"
   745        }
   746      }
   747  
   748  `
   749  	var malformedTypedData TypedData
   750  	err := json.Unmarshal([]byte(jsonTypedData), &malformedTypedData)
   751  	if err != nil {
   752  		t.Fatalf("unmarshalling failed '%v'", err)
   753  	}
   754  	_, err = malformedTypedData.HashStruct("EIP712Domain", malformedTypedData.Domain.Map())
   755  	if err != nil {
   756  		t.Errorf("Expected no error, got '%v'", err)
   757  	}
   758  }
   759  
   760  func TestFormatter(t *testing.T) {
   761  
   762  	var d TypedData
   763  	err := json.Unmarshal([]byte(jsonTypedData), &d)
   764  	if err != nil {
   765  		t.Fatalf("unmarshalling failed '%v'", err)
   766  	}
   767  	formatted := d.Format()
   768  	for _, item := range formatted {
   769  		fmt.Printf("'%v'\n", item.Pprint(0))
   770  	}
   771  
   772  	j, _ := json.Marshal(formatted)
   773  	fmt.Printf("'%v'\n", string(j))
   774  }