github.com/ethereum/go-ethereum@v1.16.1/signer/core/signed_data_test.go (about)

     1  // Copyright 2019 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser 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  // The go-ethereum library 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 Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package core_test
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"encoding/json"
    23  	"fmt"
    24  	"math/big"
    25  	"os"
    26  	"path/filepath"
    27  	"strings"
    28  	"testing"
    29  
    30  	"github.com/ethereum/go-ethereum/accounts/keystore"
    31  	"github.com/ethereum/go-ethereum/common"
    32  	"github.com/ethereum/go-ethereum/common/hexutil"
    33  	"github.com/ethereum/go-ethereum/common/math"
    34  	"github.com/ethereum/go-ethereum/crypto"
    35  	"github.com/ethereum/go-ethereum/signer/core"
    36  	"github.com/ethereum/go-ethereum/signer/core/apitypes"
    37  )
    38  
    39  var typesStandard = apitypes.Types{
    40  	"EIP712Domain": {
    41  		{
    42  			Name: "name",
    43  			Type: "string",
    44  		},
    45  		{
    46  			Name: "version",
    47  			Type: "string",
    48  		},
    49  		{
    50  			Name: "chainId",
    51  			Type: "uint256",
    52  		},
    53  		{
    54  			Name: "verifyingContract",
    55  			Type: "address",
    56  		},
    57  	},
    58  	"Person": {
    59  		{
    60  			Name: "name",
    61  			Type: "string",
    62  		},
    63  		{
    64  			Name: "wallet",
    65  			Type: "address",
    66  		},
    67  	},
    68  	"Mail": {
    69  		{
    70  			Name: "from",
    71  			Type: "Person",
    72  		},
    73  		{
    74  			Name: "to",
    75  			Type: "Person",
    76  		},
    77  		{
    78  			Name: "contents",
    79  			Type: "string",
    80  		},
    81  	},
    82  }
    83  
    84  var jsonTypedData = `
    85      {
    86        "types": {
    87          "EIP712Domain": [
    88            {
    89              "name": "name",
    90              "type": "string"
    91            },
    92            {
    93              "name": "version",
    94              "type": "string"
    95            },
    96            {
    97              "name": "chainId",
    98              "type": "uint256"
    99            },
   100            {
   101              "name": "verifyingContract",
   102              "type": "address"
   103            }
   104          ],
   105          "Person": [
   106            {
   107              "name": "name",
   108              "type": "string"
   109            },
   110            {
   111              "name": "test",
   112              "type": "uint8"
   113            },
   114            {
   115              "name": "wallet",
   116              "type": "address"
   117            }
   118          ],
   119          "Mail": [
   120            {
   121              "name": "from",
   122              "type": "Person"
   123            },
   124            {
   125              "name": "to",
   126              "type": "Person"
   127            },
   128            {
   129              "name": "contents",
   130              "type": "string"
   131            }
   132          ]
   133        },
   134        "primaryType": "Mail",
   135        "domain": {
   136          "name": "Ether Mail",
   137          "version": "1",
   138          "chainId": "1",
   139          "verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
   140        },
   141        "message": {
   142          "from": {
   143            "name": "Cow",
   144  		  "test": 3,
   145            "wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
   146          },
   147          "to": {
   148            "name": "Bob",
   149            "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
   150          },
   151          "contents": "Hello, Bob!"
   152        }
   153      }
   154  `
   155  
   156  const primaryType = "Mail"
   157  
   158  var domainStandard = apitypes.TypedDataDomain{
   159  	Name:              "Ether Mail",
   160  	Version:           "1",
   161  	ChainId:           math.NewHexOrDecimal256(1),
   162  	VerifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
   163  	Salt:              "",
   164  }
   165  
   166  var messageStandard = map[string]interface{}{
   167  	"from": map[string]interface{}{
   168  		"name":   "Cow",
   169  		"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
   170  	},
   171  	"to": map[string]interface{}{
   172  		"name":   "Bob",
   173  		"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
   174  	},
   175  	"contents": "Hello, Bob!",
   176  }
   177  
   178  var typedData = apitypes.TypedData{
   179  	Types:       typesStandard,
   180  	PrimaryType: primaryType,
   181  	Domain:      domainStandard,
   182  	Message:     messageStandard,
   183  }
   184  
   185  func TestSignData(t *testing.T) {
   186  	t.Parallel()
   187  	api, control := setup(t)
   188  	//Create two accounts
   189  	createAccount(control, api, t)
   190  	createAccount(control, api, t)
   191  	control.approveCh <- "1"
   192  	list, err := api.List(context.Background())
   193  	if err != nil {
   194  		t.Fatal(err)
   195  	}
   196  	a := common.NewMixedcaseAddress(list[0])
   197  
   198  	control.approveCh <- "Y"
   199  	control.inputCh <- "wrongpassword"
   200  	signature, err := api.SignData(context.Background(), apitypes.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
   201  	if signature != nil {
   202  		t.Errorf("Expected nil-data, got %x", signature)
   203  	}
   204  	if err != keystore.ErrDecrypt {
   205  		t.Errorf("Expected ErrLocked! '%v'", err)
   206  	}
   207  	control.approveCh <- "No way"
   208  	signature, err = api.SignData(context.Background(), apitypes.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
   209  	if signature != nil {
   210  		t.Errorf("Expected nil-data, got %x", signature)
   211  	}
   212  	if err != core.ErrRequestDenied {
   213  		t.Errorf("Expected ErrRequestDenied! '%v'", err)
   214  	}
   215  	// text/plain
   216  	control.approveCh <- "Y"
   217  	control.inputCh <- "a_long_password"
   218  	signature, err = api.SignData(context.Background(), apitypes.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
   219  	if err != nil {
   220  		t.Fatal(err)
   221  	}
   222  	if signature == nil || len(signature) != 65 {
   223  		t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
   224  	}
   225  	// data/typed via SignTypeData
   226  	control.approveCh <- "Y"
   227  	control.inputCh <- "a_long_password"
   228  	var want []byte
   229  	if signature, err = api.SignTypedData(context.Background(), a, typedData); err != nil {
   230  		t.Fatal(err)
   231  	} else if signature == nil || len(signature) != 65 {
   232  		t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
   233  	} else {
   234  		want = signature
   235  	}
   236  
   237  	// data/typed via SignData / mimetype typed data
   238  	control.approveCh <- "Y"
   239  	control.inputCh <- "a_long_password"
   240  	if typedDataJson, err := json.Marshal(typedData); err != nil {
   241  		t.Fatal(err)
   242  	} else if signature, err = api.SignData(context.Background(), apitypes.DataTyped.Mime, a, hexutil.Encode(typedDataJson)); err != nil {
   243  		t.Fatal(err)
   244  	} else if signature == nil || len(signature) != 65 {
   245  		t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
   246  	} else if have := signature; !bytes.Equal(have, want) {
   247  		t.Fatalf("want %x, have %x", want, have)
   248  	}
   249  }
   250  
   251  func TestDomainChainId(t *testing.T) {
   252  	t.Parallel()
   253  	withoutChainID := apitypes.TypedData{
   254  		Types: apitypes.Types{
   255  			"EIP712Domain": []apitypes.Type{
   256  				{Name: "name", Type: "string"},
   257  			},
   258  		},
   259  		Domain: apitypes.TypedDataDomain{
   260  			Name: "test",
   261  		},
   262  	}
   263  
   264  	if _, ok := withoutChainID.Domain.Map()["chainId"]; ok {
   265  		t.Errorf("Expected the chainId key to not be present in the domain map")
   266  	}
   267  	// should encode successfully
   268  	if _, err := withoutChainID.HashStruct("EIP712Domain", withoutChainID.Domain.Map()); err != nil {
   269  		t.Errorf("Expected the typedData to encode the domain successfully, got %v", err)
   270  	}
   271  	withChainID := apitypes.TypedData{
   272  		Types: apitypes.Types{
   273  			"EIP712Domain": []apitypes.Type{
   274  				{Name: "name", Type: "string"},
   275  				{Name: "chainId", Type: "uint256"},
   276  			},
   277  		},
   278  		Domain: apitypes.TypedDataDomain{
   279  			Name:    "test",
   280  			ChainId: math.NewHexOrDecimal256(1),
   281  		},
   282  	}
   283  
   284  	if _, ok := withChainID.Domain.Map()["chainId"]; !ok {
   285  		t.Errorf("Expected the chainId key be present in the domain map")
   286  	}
   287  	// should encode successfully
   288  	if _, err := withChainID.HashStruct("EIP712Domain", withChainID.Domain.Map()); err != nil {
   289  		t.Errorf("Expected the typedData to encode the domain successfully, got %v", err)
   290  	}
   291  }
   292  
   293  func TestHashStruct(t *testing.T) {
   294  	t.Parallel()
   295  	hash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
   296  	if err != nil {
   297  		t.Fatal(err)
   298  	}
   299  	mainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(hash))
   300  	if mainHash != "0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e" {
   301  		t.Errorf("Expected different hashStruct result (got %s)", mainHash)
   302  	}
   303  
   304  	hash, err = typedData.HashStruct("EIP712Domain", typedData.Domain.Map())
   305  	if err != nil {
   306  		t.Error(err)
   307  	}
   308  	domainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(hash))
   309  	if domainHash != "0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f" {
   310  		t.Errorf("Expected different domain hashStruct result (got %s)", domainHash)
   311  	}
   312  }
   313  
   314  func TestEncodeType(t *testing.T) {
   315  	t.Parallel()
   316  	domainTypeEncoding := string(typedData.EncodeType("EIP712Domain"))
   317  	if domainTypeEncoding != "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" {
   318  		t.Errorf("Expected different encodeType result (got %s)", domainTypeEncoding)
   319  	}
   320  
   321  	mailTypeEncoding := string(typedData.EncodeType(typedData.PrimaryType))
   322  	if mailTypeEncoding != "Mail(Person from,Person to,string contents)Person(string name,address wallet)" {
   323  		t.Errorf("Expected different encodeType result (got %s)", mailTypeEncoding)
   324  	}
   325  }
   326  
   327  func TestTypeHash(t *testing.T) {
   328  	t.Parallel()
   329  	mailTypeHash := fmt.Sprintf("0x%s", common.Bytes2Hex(typedData.TypeHash(typedData.PrimaryType)))
   330  	if mailTypeHash != "0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2" {
   331  		t.Errorf("Expected different typeHash result (got %s)", mailTypeHash)
   332  	}
   333  }
   334  
   335  func TestEncodeData(t *testing.T) {
   336  	t.Parallel()
   337  	hash, err := typedData.EncodeData(typedData.PrimaryType, typedData.Message, 0)
   338  	if err != nil {
   339  		t.Fatal(err)
   340  	}
   341  	dataEncoding := fmt.Sprintf("0x%s", common.Bytes2Hex(hash))
   342  	if dataEncoding != "0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8cd54f074a4af31b4411ff6a60c9719dbd559c221c8ac3492d9d872b041d703d1b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8" {
   343  		t.Errorf("Expected different encodeData result (got %s)", dataEncoding)
   344  	}
   345  }
   346  
   347  func TestFormatter(t *testing.T) {
   348  	t.Parallel()
   349  	var d apitypes.TypedData
   350  	err := json.Unmarshal([]byte(jsonTypedData), &d)
   351  	if err != nil {
   352  		t.Fatalf("unmarshalling failed '%v'", err)
   353  	}
   354  	formatted, _ := d.Format()
   355  	for _, item := range formatted {
   356  		t.Logf("'%v'\n", item.Pprint(0))
   357  	}
   358  
   359  	j, _ := json.Marshal(formatted)
   360  	t.Logf("'%v'\n", string(j))
   361  }
   362  
   363  func sign(typedData apitypes.TypedData) ([]byte, []byte, error) {
   364  	domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map())
   365  	if err != nil {
   366  		return nil, nil, err
   367  	}
   368  	typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
   369  	if err != nil {
   370  		return nil, nil, err
   371  	}
   372  	rawData := fmt.Appendf(nil, "\x19\x01%s%s", string(domainSeparator), string(typedDataHash))
   373  	sighash := crypto.Keccak256(rawData)
   374  	return typedDataHash, sighash, nil
   375  }
   376  
   377  func TestJsonFiles(t *testing.T) {
   378  	t.Parallel()
   379  	testfiles, err := os.ReadDir("testdata/")
   380  	if err != nil {
   381  		t.Fatalf("failed reading files: %v", err)
   382  	}
   383  	for i, fInfo := range testfiles {
   384  		if !strings.HasSuffix(fInfo.Name(), "json") {
   385  			continue
   386  		}
   387  		expectedFailure := strings.HasPrefix(fInfo.Name(), "expfail")
   388  		data, err := os.ReadFile(filepath.Join("testdata", fInfo.Name()))
   389  		if err != nil {
   390  			t.Errorf("Failed to read file %v: %v", fInfo.Name(), err)
   391  			continue
   392  		}
   393  		var typedData apitypes.TypedData
   394  		err = json.Unmarshal(data, &typedData)
   395  		if err != nil {
   396  			t.Errorf("Test %d, file %v, json unmarshalling failed: %v", i, fInfo.Name(), err)
   397  			continue
   398  		}
   399  		_, _, err = sign(typedData)
   400  		t.Logf("Error %v\n", err)
   401  		if err != nil && !expectedFailure {
   402  			t.Errorf("Test %d failed, file %v: %v", i, fInfo.Name(), err)
   403  		}
   404  		if expectedFailure && err == nil {
   405  			t.Errorf("Test %d succeeded (expected failure), file %v: %v", i, fInfo.Name(), err)
   406  		}
   407  	}
   408  }
   409  
   410  // TestFuzzerFiles tests some files that have been found by fuzzing to cause
   411  // crashes or hangs.
   412  func TestFuzzerFiles(t *testing.T) {
   413  	t.Parallel()
   414  	corpusdir := filepath.Join("testdata", "fuzzing")
   415  	testfiles, err := os.ReadDir(corpusdir)
   416  	if err != nil {
   417  		t.Fatalf("failed reading files: %v", err)
   418  	}
   419  	verbose := false
   420  	for i, fInfo := range testfiles {
   421  		data, err := os.ReadFile(filepath.Join(corpusdir, fInfo.Name()))
   422  		if err != nil {
   423  			t.Errorf("Failed to read file %v: %v", fInfo.Name(), err)
   424  			continue
   425  		}
   426  		var typedData apitypes.TypedData
   427  		err = json.Unmarshal(data, &typedData)
   428  		if err != nil {
   429  			t.Errorf("Test %d, file %v, json unmarshalling failed: %v", i, fInfo.Name(), err)
   430  			continue
   431  		}
   432  		_, err = typedData.EncodeData("EIP712Domain", typedData.Domain.Map(), 1)
   433  		if verbose && err != nil {
   434  			t.Logf("%d, EncodeData[1] err: %v\n", i, err)
   435  		}
   436  		_, err = typedData.EncodeData(typedData.PrimaryType, typedData.Message, 1)
   437  		if verbose && err != nil {
   438  			t.Logf("%d, EncodeData[2] err: %v\n", i, err)
   439  		}
   440  		typedData.Format()
   441  	}
   442  }
   443  
   444  var gnosisTypedData = `
   445  {
   446  	"types": {
   447  		"EIP712Domain": [
   448  			{ "type": "address", "name": "verifyingContract" }
   449  		],
   450  		"SafeTx": [
   451  			{ "type": "address", "name": "to" },
   452  			{ "type": "uint256", "name": "value" },
   453  			{ "type": "bytes", "name": "data" },
   454  			{ "type": "uint8", "name": "operation" },
   455  			{ "type": "uint256", "name": "safeTxGas" },
   456  			{ "type": "uint256", "name": "baseGas" },
   457  			{ "type": "uint256", "name": "gasPrice" },
   458  			{ "type": "address", "name": "gasToken" },
   459  			{ "type": "address", "name": "refundReceiver" },
   460  			{ "type": "uint256", "name": "nonce" }
   461  		]
   462  	},
   463  	"domain": {
   464  		"verifyingContract": "0x25a6c4BBd32B2424A9c99aEB0584Ad12045382B3"
   465  	},
   466  	"primaryType": "SafeTx",
   467  	"message": {
   468  		"to": "0x9eE457023bB3De16D51A003a247BaEaD7fce313D",
   469  		"value": "20000000000000000",
   470  		"data": "0x",
   471  		"operation": 0,
   472  		"safeTxGas": 27845,
   473  		"baseGas": 0,
   474  		"gasPrice": "0",
   475  		"gasToken": "0x0000000000000000000000000000000000000000",
   476  		"refundReceiver": "0x0000000000000000000000000000000000000000",
   477  		"nonce": 3
   478  	}
   479  }`
   480  
   481  var gnosisTx = `
   482  {
   483        "safe": "0x25a6c4BBd32B2424A9c99aEB0584Ad12045382B3",
   484        "to": "0x9eE457023bB3De16D51A003a247BaEaD7fce313D",
   485        "value": "20000000000000000",
   486        "data": null,
   487        "operation": 0,
   488        "gasToken": "0x0000000000000000000000000000000000000000",
   489        "safeTxGas": 27845,
   490        "baseGas": 0,
   491        "gasPrice": "0",
   492        "refundReceiver": "0x0000000000000000000000000000000000000000",
   493        "nonce": 3,
   494        "executionDate": null,
   495        "submissionDate": "2020-09-15T21:59:23.815748Z",
   496        "modified": "2020-09-15T21:59:23.815748Z",
   497        "blockNumber": null,
   498        "transactionHash": null,
   499        "safeTxHash": "0x28bae2bd58d894a1d9b69e5e9fde3570c4b98a6fc5499aefb54fb830137e831f",
   500        "executor": null,
   501        "isExecuted": false,
   502        "isSuccessful": null,
   503        "ethGasPrice": null,
   504        "gasUsed": null,
   505        "fee": null,
   506        "origin": null,
   507        "dataDecoded": null,
   508        "confirmationsRequired": null,
   509        "confirmations": [
   510          {
   511            "owner": "0xAd2e180019FCa9e55CADe76E4487F126Fd08DA34",
   512            "submissionDate": "2020-09-15T21:59:28.281243Z",
   513            "transactionHash": null,
   514            "confirmationType": "CONFIRMATION",
   515            "signature": "0x5e562065a0cb15d766dac0cd49eb6d196a41183af302c4ecad45f1a81958d7797753f04424a9b0aa1cb0448e4ec8e189540fbcdda7530ef9b9d95dfc2d36cb521b",
   516            "signatureType": "EOA"
   517          }
   518        ],
   519        "signatures": null
   520      }
   521  `
   522  
   523  // TestGnosisTypedData tests the scenario where a user submits a full EIP-712
   524  // struct without using the gnosis-specific endpoint
   525  func TestGnosisTypedData(t *testing.T) {
   526  	t.Parallel()
   527  	var td apitypes.TypedData
   528  	err := json.Unmarshal([]byte(gnosisTypedData), &td)
   529  	if err != nil {
   530  		t.Fatalf("unmarshalling failed '%v'", err)
   531  	}
   532  	_, sighash, err := sign(td)
   533  	if err != nil {
   534  		t.Fatal(err)
   535  	}
   536  	expSigHash := common.FromHex("0x28bae2bd58d894a1d9b69e5e9fde3570c4b98a6fc5499aefb54fb830137e831f")
   537  	if !bytes.Equal(expSigHash, sighash) {
   538  		t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash)
   539  	}
   540  }
   541  
   542  // TestGnosisCustomData tests the scenario where a user submits only the gnosis-safe
   543  // specific data, and we fill the TypedData struct on our side
   544  func TestGnosisCustomData(t *testing.T) {
   545  	t.Parallel()
   546  	var tx core.GnosisSafeTx
   547  	err := json.Unmarshal([]byte(gnosisTx), &tx)
   548  	if err != nil {
   549  		t.Fatal(err)
   550  	}
   551  	var td = tx.ToTypedData()
   552  	_, sighash, err := sign(td)
   553  	if err != nil {
   554  		t.Fatal(err)
   555  	}
   556  	expSigHash := common.FromHex("0x28bae2bd58d894a1d9b69e5e9fde3570c4b98a6fc5499aefb54fb830137e831f")
   557  	if !bytes.Equal(expSigHash, sighash) {
   558  		t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash)
   559  	}
   560  }
   561  
   562  var gnosisTypedDataWithChainId = `
   563  {
   564  	"types": {
   565      "EIP712Domain": [
   566          { "type": "uint256", "name": "chainId" },
   567          { "type": "address", "name": "verifyingContract" }
   568      ],
   569  		"SafeTx": [
   570  			{ "type": "address", "name": "to" },
   571  			{ "type": "uint256", "name": "value" },
   572  			{ "type": "bytes", "name": "data" },
   573  			{ "type": "uint8", "name": "operation" },
   574  			{ "type": "uint256", "name": "safeTxGas" },
   575  			{ "type": "uint256", "name": "baseGas" },
   576  			{ "type": "uint256", "name": "gasPrice" },
   577  			{ "type": "address", "name": "gasToken" },
   578  			{ "type": "address", "name": "refundReceiver" },
   579  			{ "type": "uint256", "name": "nonce" }
   580  		]
   581  	},
   582  	"domain": {
   583  		"verifyingContract": "0x111dAE35D176A9607053e0c46e91F36AFbC1dc57",
   584  		"chainId": "4"
   585  	},
   586  	"primaryType": "SafeTx",
   587  	"message": {
   588  		"to": "0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa",
   589  		"value": "0",
   590  		"data": "0xa9059cbb00000000000000000000000099d580d3a7fe7bd183b2464517b2cd7ce5a8f15a0000000000000000000000000000000000000000000000000de0b6b3a7640000",
   591  		"operation": 0,
   592  		"safeTxGas": 0,
   593  		"baseGas": 0,
   594  		"gasPrice": "0",
   595  		"gasToken": "0x0000000000000000000000000000000000000000",
   596  		"refundReceiver": "0x0000000000000000000000000000000000000000",
   597  		"nonce": 15
   598  	}
   599  }`
   600  
   601  var gnosisTxWithChainId = `
   602  {
   603  	"safe": "0x111dAE35D176A9607053e0c46e91F36AFbC1dc57",
   604  	"to": "0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa",
   605  	"value": "0",
   606  	"data": "0xa9059cbb00000000000000000000000099d580d3a7fe7bd183b2464517b2cd7ce5a8f15a0000000000000000000000000000000000000000000000000de0b6b3a7640000",
   607  	"operation": 0,
   608  	"gasToken": "0x0000000000000000000000000000000000000000",
   609  	"safeTxGas": 0,
   610  	"baseGas": 0,
   611  	"gasPrice": "0",
   612  	"refundReceiver": "0x0000000000000000000000000000000000000000",
   613  	"nonce": 15,
   614  	"executionDate": "2022-01-10T20:00:12Z",
   615  	"submissionDate": "2022-01-10T19:59:59.689989Z",
   616  	"modified": "2022-01-10T20:00:31.903635Z",
   617  	"blockNumber": 9968802,
   618  	"transactionHash": "0xc9fef30499ee8984974ab9dddd9d15c2a97c1a4393935dceed5efc3af9fc41a4",
   619  	"safeTxHash": "0x6619dab5401503f2735256e12b898e69eb701d6a7e0d07abf1be4bb8aebfba29",
   620  	"executor": "0xbc2BB26a6d821e69A38016f3858561a1D80d4182",
   621  	"isExecuted": true,
   622  	"isSuccessful": true,
   623  	"ethGasPrice": "2500000009",
   624  	"gasUsed": 82902,
   625  	"fee": "207255000746118",
   626  	"chainId": "4",
   627  	"origin": null,
   628  	"dataDecoded": {
   629  		"method": "transfer",
   630  		"parameters": [
   631  				{
   632  				"name": "to",
   633  				"type": "address",
   634  				"value": "0x99D580d3a7FE7BD183b2464517B2cD7ce5A8F15A"
   635  				},
   636  				{
   637  				"name": "value",
   638  				"type": "uint256",
   639  				"value": "1000000000000000000"
   640  				}
   641  		]
   642  	},
   643  	"confirmationsRequired": 1,
   644  	"confirmations": [
   645  		{
   646  		"owner": "0xbc2BB26a6d821e69A38016f3858561a1D80d4182",
   647  		"submissionDate": "2022-01-10T19:59:59.722500Z",
   648  		"transactionHash": null,
   649  		"signature": "0x5ca34641bcdee06e7b99143bfe34778195ca41022bd35837b96c204c7786be9d6dfa6dba43b53cd92da45ac728899e1561b232d28f38ba82df45f164caba38be1b",
   650  		"signatureType": "EOA"
   651  		}
   652  	],
   653  	"signatures": "0x5ca34641bcdee06e7b99143bfe34778195ca41022bd35837b96c204c7786be9d6dfa6dba43b53cd92da45ac728899e1561b232d28f38ba82df45f164caba38be1b"
   654  }
   655  `
   656  
   657  func TestGnosisTypedDataWithChainId(t *testing.T) {
   658  	t.Parallel()
   659  	var td apitypes.TypedData
   660  	err := json.Unmarshal([]byte(gnosisTypedDataWithChainId), &td)
   661  	if err != nil {
   662  		t.Fatalf("unmarshalling failed '%v'", err)
   663  	}
   664  	_, sighash, err := sign(td)
   665  	if err != nil {
   666  		t.Fatal(err)
   667  	}
   668  	expSigHash := common.FromHex("0x6619dab5401503f2735256e12b898e69eb701d6a7e0d07abf1be4bb8aebfba29")
   669  	if !bytes.Equal(expSigHash, sighash) {
   670  		t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash)
   671  	}
   672  }
   673  
   674  // TestGnosisCustomDataWithChainId tests the scenario where a user submits only the gnosis-safe
   675  // specific data, and we fill the TypedData struct on our side
   676  func TestGnosisCustomDataWithChainId(t *testing.T) {
   677  	t.Parallel()
   678  	var tx core.GnosisSafeTx
   679  	err := json.Unmarshal([]byte(gnosisTxWithChainId), &tx)
   680  	if err != nil {
   681  		t.Fatal(err)
   682  	}
   683  	var td = tx.ToTypedData()
   684  	_, sighash, err := sign(td)
   685  	if err != nil {
   686  		t.Fatal(err)
   687  	}
   688  	expSigHash := common.FromHex("0x6619dab5401503f2735256e12b898e69eb701d6a7e0d07abf1be4bb8aebfba29")
   689  	if !bytes.Equal(expSigHash, sighash) {
   690  		t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash)
   691  	}
   692  }
   693  
   694  var complexTypedData = `
   695  {
   696      "types": {
   697          "EIP712Domain": [
   698              {
   699                  "name": "chainId",
   700                  "type": "uint256"
   701              },
   702              {
   703                  "name": "name",
   704                  "type": "string"
   705              },
   706              {
   707                  "name": "verifyingContract",
   708                  "type": "address"
   709              },
   710              {
   711                  "name": "version",
   712                  "type": "string"
   713              }
   714          ],
   715          "Action": [
   716              {
   717                  "name": "action",
   718                  "type": "string"
   719              },
   720              {
   721                  "name": "params",
   722                  "type": "string"
   723              }
   724          ],
   725          "Cell": [
   726              {
   727                  "name": "capacity",
   728                  "type": "string"
   729              },
   730              {
   731                  "name": "lock",
   732                  "type": "string"
   733              },
   734              {
   735                  "name": "type",
   736                  "type": "string"
   737              },
   738              {
   739                  "name": "data",
   740                  "type": "string"
   741              },
   742              {
   743                  "name": "extraData",
   744                  "type": "string"
   745              }
   746          ],
   747          "Transaction": [
   748              {
   749                  "name": "DAS_MESSAGE",
   750                  "type": "string"
   751              },
   752              {
   753                  "name": "inputsCapacity",
   754                  "type": "string"
   755              },
   756              {
   757                  "name": "outputsCapacity",
   758                  "type": "string"
   759              },
   760              {
   761                  "name": "fee",
   762                  "type": "string"
   763              },
   764              {
   765                  "name": "action",
   766                  "type": "Action"
   767              },
   768              {
   769                  "name": "inputs",
   770                  "type": "Cell[]"
   771              },
   772              {
   773                  "name": "outputs",
   774                  "type": "Cell[]"
   775              },
   776              {
   777                  "name": "digest",
   778                  "type": "bytes32"
   779              }
   780          ]
   781      },
   782      "primaryType": "Transaction",
   783      "domain": {
   784          "chainId": "56",
   785          "name": "da.systems",
   786          "verifyingContract": "0x0000000000000000000000000000000020210722",
   787          "version": "1"
   788      },
   789      "message": {
   790          "DAS_MESSAGE": "SELL mobcion.bit FOR 100000 CKB",
   791          "inputsCapacity": "1216.9999 CKB",
   792          "outputsCapacity": "1216.9998 CKB",
   793          "fee": "0.0001 CKB",
   794          "digest": "0x53a6c0f19ec281604607f5d6817e442082ad1882bef0df64d84d3810dae561eb",
   795          "action": {
   796              "action": "start_account_sale",
   797              "params": "0x00"
   798          },
   799          "inputs": [
   800              {
   801                  "capacity": "218 CKB",
   802                  "lock": "das-lock,0x01,0x051c152f77f8efa9c7c6d181cc97ee67c165c506...",
   803                  "type": "account-cell-type,0x01,0x",
   804                  "data": "{ account: mobcion.bit, expired_at: 1670913958 }",
   805                  "extraData": "{ status: 0, records_hash: 0x55478d76900611eb079b22088081124ed6c8bae21a05dd1a0d197efcc7c114ce }"
   806              }
   807          ],
   808          "outputs": [
   809              {
   810                  "capacity": "218 CKB",
   811                  "lock": "das-lock,0x01,0x051c152f77f8efa9c7c6d181cc97ee67c165c506...",
   812                  "type": "account-cell-type,0x01,0x",
   813                  "data": "{ account: mobcion.bit, expired_at: 1670913958 }",
   814                  "extraData": "{ status: 1, records_hash: 0x55478d76900611eb079b22088081124ed6c8bae21a05dd1a0d197efcc7c114ce }"
   815              },
   816              {
   817                  "capacity": "201 CKB",
   818                  "lock": "das-lock,0x01,0x051c152f77f8efa9c7c6d181cc97ee67c165c506...",
   819                  "type": "account-sale-cell-type,0x01,0x",
   820                  "data": "0x1209460ef3cb5f1c68ed2c43a3e020eec2d9de6e...",
   821                  "extraData": ""
   822              }
   823          ]
   824      }
   825  }
   826  `
   827  
   828  func TestComplexTypedData(t *testing.T) {
   829  	t.Parallel()
   830  	var td apitypes.TypedData
   831  	err := json.Unmarshal([]byte(complexTypedData), &td)
   832  	if err != nil {
   833  		t.Fatalf("unmarshalling failed '%v'", err)
   834  	}
   835  	_, sighash, err := sign(td)
   836  	if err != nil {
   837  		t.Fatal(err)
   838  	}
   839  	expSigHash := common.FromHex("0x42b1aca82bb6900ff75e90a136de550a58f1a220a071704088eabd5e6ce20446")
   840  	if !bytes.Equal(expSigHash, sighash) {
   841  		t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash)
   842  	}
   843  }
   844  
   845  func TestGnosisSafe(t *testing.T) {
   846  	t.Parallel()
   847  	// json missing chain id
   848  	js := "{\n  \"safe\": \"0x899FcB1437DE65DC6315f5a69C017dd3F2837557\",\n  \"to\": \"0x899FcB1437DE65DC6315f5a69C017dd3F2837557\",\n  \"value\": \"0\",\n  \"data\": \"0x0d582f13000000000000000000000000d3ed2b8756b942c98c851722f3bd507a17b4745f0000000000000000000000000000000000000000000000000000000000000005\",\n  \"operation\": 0,\n  \"gasToken\": \"0x0000000000000000000000000000000000000000\",\n  \"safeTxGas\": 0,\n  \"baseGas\": 0,\n  \"gasPrice\": \"0\",\n  \"refundReceiver\": \"0x0000000000000000000000000000000000000000\",\n  \"nonce\": 0,\n  \"executionDate\": null,\n  \"submissionDate\": \"2022-02-23T14:09:00.018475Z\",\n  \"modified\": \"2022-12-01T15:52:21.214357Z\",\n  \"blockNumber\": null,\n  \"transactionHash\": null,\n  \"safeTxHash\": \"0x6f0f5cffee69087c9d2471e477a63cab2ae171cf433e754315d558d8836274f4\",\n  \"executor\": null,\n  \"isExecuted\": false,\n  \"isSuccessful\": null,\n  \"ethGasPrice\": null,\n  \"maxFeePerGas\": null,\n  \"maxPriorityFeePerGas\": null,\n  \"gasUsed\": null,\n  \"fee\": null,\n  \"origin\": \"https://gnosis-safe.io\",\n  \"dataDecoded\": {\n    \"method\": \"addOwnerWithThreshold\",\n    \"parameters\": [\n      {\n        \"name\": \"owner\",\n        \"type\": \"address\",\n        \"value\": \"0xD3Ed2b8756b942c98c851722F3bd507a17B4745F\"\n      },\n      {\n        \"name\": \"_threshold\",\n        \"type\": \"uint256\",\n        \"value\": \"5\"\n      }\n    ]\n  },\n  \"confirmationsRequired\": 4,\n  \"confirmations\": [\n    {\n      \"owner\": \"0x30B714E065B879F5c042A75Bb40a220A0BE27966\",\n      \"submissionDate\": \"2022-03-01T14:56:22Z\",\n      \"transactionHash\": \"0x6d0a9c83ac7578ef3be1f2afce089fb83b619583dfa779b82f4422fd64ff3ee9\",\n      \"signature\": \"0x00000000000000000000000030b714e065b879f5c042a75bb40a220a0be27966000000000000000000000000000000000000000000000000000000000000000001\",\n      \"signatureType\": \"APPROVED_HASH\"\n    },\n    {\n      \"owner\": \"0x8300dFEa25Da0eb744fC0D98c23283F86AB8c10C\",\n      \"submissionDate\": \"2022-12-01T15:52:21.214357Z\",\n      \"transactionHash\": null,\n      \"signature\": \"0xbce73de4cc6ee208e933a93c794dcb8ba1810f9848d1eec416b7be4dae9854c07dbf1720e60bbd310d2159197a380c941cfdb55b3ce58f9dd69efd395d7bef881b\",\n      \"signatureType\": \"EOA\"\n    }\n  ],\n  \"trusted\": true,\n  \"signatures\": null\n}\n"
   849  	var gnosisTx core.GnosisSafeTx
   850  	if err := json.Unmarshal([]byte(js), &gnosisTx); err != nil {
   851  		t.Fatal(err)
   852  	}
   853  	sighash, _, err := apitypes.TypedDataAndHash(gnosisTx.ToTypedData())
   854  	if err != nil {
   855  		t.Fatal(err)
   856  	}
   857  	if bytes.Equal(sighash, gnosisTx.InputExpHash.Bytes()) {
   858  		t.Fatal("expected inequality")
   859  	}
   860  	gnosisTx.ChainId = (*math.HexOrDecimal256)(big.NewInt(1))
   861  	sighash, _, _ = apitypes.TypedDataAndHash(gnosisTx.ToTypedData())
   862  	if !bytes.Equal(sighash, gnosisTx.InputExpHash.Bytes()) {
   863  		t.Fatal("expected equality")
   864  	}
   865  }
   866  
   867  var complexTypedDataLCRefType = `
   868  {
   869      "types": {
   870          "EIP712Domain": [
   871              {
   872                  "name": "chainId",
   873                  "type": "uint256"
   874              },
   875              {
   876                  "name": "name",
   877                  "type": "string"
   878              },
   879              {
   880                  "name": "verifyingContract",
   881                  "type": "address"
   882              },
   883              {
   884                  "name": "version",
   885                  "type": "string"
   886              }
   887          ],
   888          "Action": [
   889              {
   890                  "name": "action",
   891                  "type": "string"
   892              },
   893              {
   894                  "name": "params",
   895                  "type": "string"
   896              }
   897          ],
   898          "cCell": [
   899              {
   900                  "name": "capacity",
   901                  "type": "string"
   902              },
   903              {
   904                  "name": "lock",
   905                  "type": "string"
   906              },
   907              {
   908                  "name": "type",
   909                  "type": "string"
   910              },
   911              {
   912                  "name": "data",
   913                  "type": "string"
   914              },
   915              {
   916                  "name": "extraData",
   917                  "type": "string"
   918              }
   919          ],
   920          "Transaction": [
   921              {
   922                  "name": "DAS_MESSAGE",
   923                  "type": "string"
   924              },
   925              {
   926                  "name": "inputsCapacity",
   927                  "type": "string"
   928              },
   929              {
   930                  "name": "outputsCapacity",
   931                  "type": "string"
   932              },
   933              {
   934                  "name": "fee",
   935                  "type": "string"
   936              },
   937              {
   938                  "name": "action",
   939                  "type": "Action"
   940              },
   941              {
   942                  "name": "inputs",
   943                  "type": "cCell[]"
   944              },
   945              {
   946                  "name": "outputs",
   947                  "type": "cCell[]"
   948              },
   949              {
   950                  "name": "digest",
   951                  "type": "bytes32"
   952              }
   953          ]
   954      },
   955      "primaryType": "Transaction",
   956      "domain": {
   957          "chainId": "56",
   958          "name": "da.systems",
   959          "verifyingContract": "0x0000000000000000000000000000000020210722",
   960          "version": "1"
   961      },
   962      "message": {
   963          "DAS_MESSAGE": "SELL mobcion.bit FOR 100000 CKB",
   964          "inputsCapacity": "1216.9999 CKB",
   965          "outputsCapacity": "1216.9998 CKB",
   966          "fee": "0.0001 CKB",
   967          "digest": "0x53a6c0f19ec281604607f5d6817e442082ad1882bef0df64d84d3810dae561eb",
   968          "action": {
   969              "action": "start_account_sale",
   970              "params": "0x00"
   971          },
   972          "inputs": [
   973              {
   974                  "capacity": "218 CKB",
   975                  "lock": "das-lock,0x01,0x051c152f77f8efa9c7c6d181cc97ee67c165c506...",
   976                  "type": "account-cell-type,0x01,0x",
   977                  "data": "{ account: mobcion.bit, expired_at: 1670913958 }",
   978                  "extraData": "{ status: 0, records_hash: 0x55478d76900611eb079b22088081124ed6c8bae21a05dd1a0d197efcc7c114ce }"
   979              }
   980          ],
   981          "outputs": [
   982              {
   983                  "capacity": "218 CKB",
   984                  "lock": "das-lock,0x01,0x051c152f77f8efa9c7c6d181cc97ee67c165c506...",
   985                  "type": "account-cell-type,0x01,0x",
   986                  "data": "{ account: mobcion.bit, expired_at: 1670913958 }",
   987                  "extraData": "{ status: 1, records_hash: 0x55478d76900611eb079b22088081124ed6c8bae21a05dd1a0d197efcc7c114ce }"
   988              },
   989              {
   990                  "capacity": "201 CKB",
   991                  "lock": "das-lock,0x01,0x051c152f77f8efa9c7c6d181cc97ee67c165c506...",
   992                  "type": "account-sale-cell-type,0x01,0x",
   993                  "data": "0x1209460ef3cb5f1c68ed2c43a3e020eec2d9de6e...",
   994                  "extraData": ""
   995              }
   996          ]
   997      }
   998  }
   999  `
  1000  
  1001  func TestComplexTypedDataWithLowercaseReftype(t *testing.T) {
  1002  	t.Parallel()
  1003  	var td apitypes.TypedData
  1004  	err := json.Unmarshal([]byte(complexTypedDataLCRefType), &td)
  1005  	if err != nil {
  1006  		t.Fatalf("unmarshalling failed '%v'", err)
  1007  	}
  1008  	_, sighash, err := sign(td)
  1009  	if err != nil {
  1010  		t.Fatal(err)
  1011  	}
  1012  	expSigHash := common.FromHex("0x49191f910874f0148597204d9076af128d4694a7c4b714f1ccff330b87207bff")
  1013  	if !bytes.Equal(expSigHash, sighash) {
  1014  		t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash)
  1015  	}
  1016  }
  1017  
  1018  var recursiveBytesTypesStandard = apitypes.Types{
  1019  	"EIP712Domain": {
  1020  		{
  1021  			Name: "name",
  1022  			Type: "string",
  1023  		},
  1024  		{
  1025  			Name: "version",
  1026  			Type: "string",
  1027  		},
  1028  		{
  1029  			Name: "chainId",
  1030  			Type: "uint256",
  1031  		},
  1032  		{
  1033  			Name: "verifyingContract",
  1034  			Type: "address",
  1035  		},
  1036  	},
  1037  	"Val": {
  1038  		{
  1039  			Name: "field",
  1040  			Type: "bytes[][]",
  1041  		},
  1042  	},
  1043  }
  1044  
  1045  var recursiveBytesMessageStandard = map[string]interface{}{
  1046  	"field": [][][]byte{{{1}, {2}}, {{3}, {4}}},
  1047  }
  1048  
  1049  var recursiveBytesTypedData = apitypes.TypedData{
  1050  	Types:       recursiveBytesTypesStandard,
  1051  	PrimaryType: "Val",
  1052  	Domain:      domainStandard,
  1053  	Message:     recursiveBytesMessageStandard,
  1054  }
  1055  
  1056  func TestEncodeDataRecursiveBytes(t *testing.T) {
  1057  	typedData := recursiveBytesTypedData
  1058  	_, err := typedData.EncodeData(typedData.PrimaryType, typedData.Message, 0)
  1059  	if err != nil {
  1060  		t.Fatalf("got err %v", err)
  1061  	}
  1062  }