github.com/tirogen/go-ethereum@v1.10.12-0.20221226051715-250cfede41b6/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"
    27  	"strings"
    28  	"testing"
    29  
    30  	"github.com/tirogen/go-ethereum/accounts/keystore"
    31  	"github.com/tirogen/go-ethereum/common"
    32  	"github.com/tirogen/go-ethereum/common/hexutil"
    33  	"github.com/tirogen/go-ethereum/common/math"
    34  	"github.com/tirogen/go-ethereum/crypto"
    35  	"github.com/tirogen/go-ethereum/signer/core"
    36  	"github.com/tirogen/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  	api, control := setup(t)
   187  	//Create two accounts
   188  	createAccount(control, api, t)
   189  	createAccount(control, api, t)
   190  	control.approveCh <- "1"
   191  	list, err := api.List(context.Background())
   192  	if err != nil {
   193  		t.Fatal(err)
   194  	}
   195  	a := common.NewMixedcaseAddress(list[0])
   196  
   197  	control.approveCh <- "Y"
   198  	control.inputCh <- "wrongpassword"
   199  	signature, err := api.SignData(context.Background(), apitypes.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 != keystore.ErrDecrypt {
   204  		t.Errorf("Expected ErrLocked! '%v'", err)
   205  	}
   206  	control.approveCh <- "No way"
   207  	signature, err = api.SignData(context.Background(), apitypes.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
   208  	if signature != nil {
   209  		t.Errorf("Expected nil-data, got %x", signature)
   210  	}
   211  	if err != core.ErrRequestDenied {
   212  		t.Errorf("Expected ErrRequestDenied! '%v'", err)
   213  	}
   214  	// text/plain
   215  	control.approveCh <- "Y"
   216  	control.inputCh <- "a_long_password"
   217  	signature, err = api.SignData(context.Background(), apitypes.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
   218  	if err != nil {
   219  		t.Fatal(err)
   220  	}
   221  	if signature == nil || len(signature) != 65 {
   222  		t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
   223  	}
   224  	// data/typed via SignTypeData
   225  	control.approveCh <- "Y"
   226  	control.inputCh <- "a_long_password"
   227  	var want []byte
   228  	if signature, err = api.SignTypedData(context.Background(), a, typedData); err != nil {
   229  		t.Fatal(err)
   230  	} else if signature == nil || len(signature) != 65 {
   231  		t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
   232  	} else {
   233  		want = signature
   234  	}
   235  
   236  	// data/typed via SignData / mimetype typed data
   237  	control.approveCh <- "Y"
   238  	control.inputCh <- "a_long_password"
   239  	if typedDataJson, err := json.Marshal(typedData); err != nil {
   240  		t.Fatal(err)
   241  	} else if signature, err = api.SignData(context.Background(), apitypes.DataTyped.Mime, a, hexutil.Encode(typedDataJson)); err != nil {
   242  		t.Fatal(err)
   243  	} else if signature == nil || len(signature) != 65 {
   244  		t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
   245  	} else if have := signature; !bytes.Equal(have, want) {
   246  		t.Fatalf("want %x, have %x", want, have)
   247  	}
   248  }
   249  
   250  func TestDomainChainId(t *testing.T) {
   251  	withoutChainID := apitypes.TypedData{
   252  		Types: apitypes.Types{
   253  			"EIP712Domain": []apitypes.Type{
   254  				{Name: "name", Type: "string"},
   255  			},
   256  		},
   257  		Domain: apitypes.TypedDataDomain{
   258  			Name: "test",
   259  		},
   260  	}
   261  
   262  	if _, ok := withoutChainID.Domain.Map()["chainId"]; ok {
   263  		t.Errorf("Expected the chainId key to not be present in the domain map")
   264  	}
   265  	// should encode successfully
   266  	if _, err := withoutChainID.HashStruct("EIP712Domain", withoutChainID.Domain.Map()); err != nil {
   267  		t.Errorf("Expected the typedData to encode the domain successfully, got %v", err)
   268  	}
   269  	withChainID := apitypes.TypedData{
   270  		Types: apitypes.Types{
   271  			"EIP712Domain": []apitypes.Type{
   272  				{Name: "name", Type: "string"},
   273  				{Name: "chainId", Type: "uint256"},
   274  			},
   275  		},
   276  		Domain: apitypes.TypedDataDomain{
   277  			Name:    "test",
   278  			ChainId: math.NewHexOrDecimal256(1),
   279  		},
   280  	}
   281  
   282  	if _, ok := withChainID.Domain.Map()["chainId"]; !ok {
   283  		t.Errorf("Expected the chainId key be present in the domain map")
   284  	}
   285  	// should encode successfully
   286  	if _, err := withChainID.HashStruct("EIP712Domain", withChainID.Domain.Map()); err != nil {
   287  		t.Errorf("Expected the typedData to encode the domain successfully, got %v", err)
   288  	}
   289  }
   290  
   291  func TestHashStruct(t *testing.T) {
   292  	hash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
   293  	if err != nil {
   294  		t.Fatal(err)
   295  	}
   296  	mainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(hash))
   297  	if mainHash != "0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e" {
   298  		t.Errorf("Expected different hashStruct result (got %s)", mainHash)
   299  	}
   300  
   301  	hash, err = typedData.HashStruct("EIP712Domain", typedData.Domain.Map())
   302  	if err != nil {
   303  		t.Error(err)
   304  	}
   305  	domainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(hash))
   306  	if domainHash != "0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f" {
   307  		t.Errorf("Expected different domain hashStruct result (got %s)", domainHash)
   308  	}
   309  }
   310  
   311  func TestEncodeType(t *testing.T) {
   312  	domainTypeEncoding := string(typedData.EncodeType("EIP712Domain"))
   313  	if domainTypeEncoding != "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" {
   314  		t.Errorf("Expected different encodeType result (got %s)", domainTypeEncoding)
   315  	}
   316  
   317  	mailTypeEncoding := string(typedData.EncodeType(typedData.PrimaryType))
   318  	if mailTypeEncoding != "Mail(Person from,Person to,string contents)Person(string name,address wallet)" {
   319  		t.Errorf("Expected different encodeType result (got %s)", mailTypeEncoding)
   320  	}
   321  }
   322  
   323  func TestTypeHash(t *testing.T) {
   324  	mailTypeHash := fmt.Sprintf("0x%s", common.Bytes2Hex(typedData.TypeHash(typedData.PrimaryType)))
   325  	if mailTypeHash != "0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2" {
   326  		t.Errorf("Expected different typeHash result (got %s)", mailTypeHash)
   327  	}
   328  }
   329  
   330  func TestEncodeData(t *testing.T) {
   331  	hash, err := typedData.EncodeData(typedData.PrimaryType, typedData.Message, 0)
   332  	if err != nil {
   333  		t.Fatal(err)
   334  	}
   335  	dataEncoding := fmt.Sprintf("0x%s", common.Bytes2Hex(hash))
   336  	if dataEncoding != "0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8cd54f074a4af31b4411ff6a60c9719dbd559c221c8ac3492d9d872b041d703d1b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8" {
   337  		t.Errorf("Expected different encodeData result (got %s)", dataEncoding)
   338  	}
   339  }
   340  
   341  func TestFormatter(t *testing.T) {
   342  	var d apitypes.TypedData
   343  	err := json.Unmarshal([]byte(jsonTypedData), &d)
   344  	if err != nil {
   345  		t.Fatalf("unmarshalling failed '%v'", err)
   346  	}
   347  	formatted, _ := d.Format()
   348  	for _, item := range formatted {
   349  		t.Logf("'%v'\n", item.Pprint(0))
   350  	}
   351  
   352  	j, _ := json.Marshal(formatted)
   353  	t.Logf("'%v'\n", string(j))
   354  }
   355  
   356  func sign(typedData apitypes.TypedData) ([]byte, []byte, error) {
   357  	domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map())
   358  	if err != nil {
   359  		return nil, nil, err
   360  	}
   361  	typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
   362  	if err != nil {
   363  		return nil, nil, err
   364  	}
   365  	rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))
   366  	sighash := crypto.Keccak256(rawData)
   367  	return typedDataHash, sighash, nil
   368  }
   369  
   370  func TestJsonFiles(t *testing.T) {
   371  	testfiles, err := os.ReadDir("testdata/")
   372  	if err != nil {
   373  		t.Fatalf("failed reading files: %v", err)
   374  	}
   375  	for i, fInfo := range testfiles {
   376  		if !strings.HasSuffix(fInfo.Name(), "json") {
   377  			continue
   378  		}
   379  		expectedFailure := strings.HasPrefix(fInfo.Name(), "expfail")
   380  		data, err := os.ReadFile(path.Join("testdata", fInfo.Name()))
   381  		if err != nil {
   382  			t.Errorf("Failed to read file %v: %v", fInfo.Name(), err)
   383  			continue
   384  		}
   385  		var typedData apitypes.TypedData
   386  		err = json.Unmarshal(data, &typedData)
   387  		if err != nil {
   388  			t.Errorf("Test %d, file %v, json unmarshalling failed: %v", i, fInfo.Name(), err)
   389  			continue
   390  		}
   391  		_, _, err = sign(typedData)
   392  		t.Logf("Error %v\n", err)
   393  		if err != nil && !expectedFailure {
   394  			t.Errorf("Test %d failed, file %v: %v", i, fInfo.Name(), err)
   395  		}
   396  		if expectedFailure && err == nil {
   397  			t.Errorf("Test %d succeeded (expected failure), file %v: %v", i, fInfo.Name(), err)
   398  		}
   399  	}
   400  }
   401  
   402  // TestFuzzerFiles tests some files that have been found by fuzzing to cause
   403  // crashes or hangs.
   404  func TestFuzzerFiles(t *testing.T) {
   405  	corpusdir := path.Join("testdata", "fuzzing")
   406  	testfiles, err := os.ReadDir(corpusdir)
   407  	if err != nil {
   408  		t.Fatalf("failed reading files: %v", err)
   409  	}
   410  	verbose := false
   411  	for i, fInfo := range testfiles {
   412  		data, err := os.ReadFile(path.Join(corpusdir, fInfo.Name()))
   413  		if err != nil {
   414  			t.Errorf("Failed to read file %v: %v", fInfo.Name(), err)
   415  			continue
   416  		}
   417  		var typedData apitypes.TypedData
   418  		err = json.Unmarshal(data, &typedData)
   419  		if err != nil {
   420  			t.Errorf("Test %d, file %v, json unmarshalling failed: %v", i, fInfo.Name(), err)
   421  			continue
   422  		}
   423  		_, err = typedData.EncodeData("EIP712Domain", typedData.Domain.Map(), 1)
   424  		if verbose && err != nil {
   425  			t.Logf("%d, EncodeData[1] err: %v\n", i, err)
   426  		}
   427  		_, err = typedData.EncodeData(typedData.PrimaryType, typedData.Message, 1)
   428  		if verbose && err != nil {
   429  			t.Logf("%d, EncodeData[2] err: %v\n", i, err)
   430  		}
   431  		typedData.Format()
   432  	}
   433  }
   434  
   435  var gnosisTypedData = `
   436  {
   437  	"types": {
   438  		"EIP712Domain": [
   439  			{ "type": "address", "name": "verifyingContract" }
   440  		],
   441  		"SafeTx": [
   442  			{ "type": "address", "name": "to" },
   443  			{ "type": "uint256", "name": "value" },
   444  			{ "type": "bytes", "name": "data" },
   445  			{ "type": "uint8", "name": "operation" },
   446  			{ "type": "uint256", "name": "safeTxGas" },
   447  			{ "type": "uint256", "name": "baseGas" },
   448  			{ "type": "uint256", "name": "gasPrice" },
   449  			{ "type": "address", "name": "gasToken" },
   450  			{ "type": "address", "name": "refundReceiver" },
   451  			{ "type": "uint256", "name": "nonce" }
   452  		]
   453  	},
   454  	"domain": {
   455  		"verifyingContract": "0x25a6c4BBd32B2424A9c99aEB0584Ad12045382B3"
   456  	},
   457  	"primaryType": "SafeTx",
   458  	"message": {
   459  		"to": "0x9eE457023bB3De16D51A003a247BaEaD7fce313D",
   460  		"value": "20000000000000000",
   461  		"data": "0x",
   462  		"operation": 0,
   463  		"safeTxGas": 27845,
   464  		"baseGas": 0,
   465  		"gasPrice": "0",
   466  		"gasToken": "0x0000000000000000000000000000000000000000",
   467  		"refundReceiver": "0x0000000000000000000000000000000000000000",
   468  		"nonce": 3
   469  	}
   470  }`
   471  
   472  var gnosisTx = `
   473  {
   474        "safe": "0x25a6c4BBd32B2424A9c99aEB0584Ad12045382B3",
   475        "to": "0x9eE457023bB3De16D51A003a247BaEaD7fce313D",
   476        "value": "20000000000000000",
   477        "data": null,
   478        "operation": 0,
   479        "gasToken": "0x0000000000000000000000000000000000000000",
   480        "safeTxGas": 27845,
   481        "baseGas": 0,
   482        "gasPrice": "0",
   483        "refundReceiver": "0x0000000000000000000000000000000000000000",
   484        "nonce": 3,
   485        "executionDate": null,
   486        "submissionDate": "2020-09-15T21:59:23.815748Z",
   487        "modified": "2020-09-15T21:59:23.815748Z",
   488        "blockNumber": null,
   489        "transactionHash": null,
   490        "safeTxHash": "0x28bae2bd58d894a1d9b69e5e9fde3570c4b98a6fc5499aefb54fb830137e831f",
   491        "executor": null,
   492        "isExecuted": false,
   493        "isSuccessful": null,
   494        "ethGasPrice": null,
   495        "gasUsed": null,
   496        "fee": null,
   497        "origin": null,
   498        "dataDecoded": null,
   499        "confirmationsRequired": null,
   500        "confirmations": [
   501          {
   502            "owner": "0xAd2e180019FCa9e55CADe76E4487F126Fd08DA34",
   503            "submissionDate": "2020-09-15T21:59:28.281243Z",
   504            "transactionHash": null,
   505            "confirmationType": "CONFIRMATION",
   506            "signature": "0x5e562065a0cb15d766dac0cd49eb6d196a41183af302c4ecad45f1a81958d7797753f04424a9b0aa1cb0448e4ec8e189540fbcdda7530ef9b9d95dfc2d36cb521b",
   507            "signatureType": "EOA"
   508          }
   509        ],
   510        "signatures": null
   511      }
   512  `
   513  
   514  // TestGnosisTypedData tests the scenario where a user submits a full EIP-712
   515  // struct without using the gnosis-specific endpoint
   516  func TestGnosisTypedData(t *testing.T) {
   517  	var td apitypes.TypedData
   518  	err := json.Unmarshal([]byte(gnosisTypedData), &td)
   519  	if err != nil {
   520  		t.Fatalf("unmarshalling failed '%v'", err)
   521  	}
   522  	_, sighash, err := sign(td)
   523  	if err != nil {
   524  		t.Fatal(err)
   525  	}
   526  	expSigHash := common.FromHex("0x28bae2bd58d894a1d9b69e5e9fde3570c4b98a6fc5499aefb54fb830137e831f")
   527  	if !bytes.Equal(expSigHash, sighash) {
   528  		t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash)
   529  	}
   530  }
   531  
   532  // TestGnosisCustomData tests the scenario where a user submits only the gnosis-safe
   533  // specific data, and we fill the TypedData struct on our side
   534  func TestGnosisCustomData(t *testing.T) {
   535  	var tx core.GnosisSafeTx
   536  	err := json.Unmarshal([]byte(gnosisTx), &tx)
   537  	if err != nil {
   538  		t.Fatal(err)
   539  	}
   540  	var td = tx.ToTypedData()
   541  	_, sighash, err := sign(td)
   542  	if err != nil {
   543  		t.Fatal(err)
   544  	}
   545  	expSigHash := common.FromHex("0x28bae2bd58d894a1d9b69e5e9fde3570c4b98a6fc5499aefb54fb830137e831f")
   546  	if !bytes.Equal(expSigHash, sighash) {
   547  		t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash)
   548  	}
   549  }
   550  
   551  var gnosisTypedDataWithChainId = `
   552  {
   553  	"types": {
   554      "EIP712Domain": [
   555          { "type": "uint256", "name": "chainId" },
   556          { "type": "address", "name": "verifyingContract" }
   557      ],
   558  		"SafeTx": [
   559  			{ "type": "address", "name": "to" },
   560  			{ "type": "uint256", "name": "value" },
   561  			{ "type": "bytes", "name": "data" },
   562  			{ "type": "uint8", "name": "operation" },
   563  			{ "type": "uint256", "name": "safeTxGas" },
   564  			{ "type": "uint256", "name": "baseGas" },
   565  			{ "type": "uint256", "name": "gasPrice" },
   566  			{ "type": "address", "name": "gasToken" },
   567  			{ "type": "address", "name": "refundReceiver" },
   568  			{ "type": "uint256", "name": "nonce" }
   569  		]
   570  	},
   571  	"domain": {
   572  		"verifyingContract": "0x111dAE35D176A9607053e0c46e91F36AFbC1dc57",
   573  		"chainId": "4"
   574  	},
   575  	"primaryType": "SafeTx",
   576  	"message": {
   577  		"to": "0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa",
   578  		"value": "0",
   579  		"data": "0xa9059cbb00000000000000000000000099d580d3a7fe7bd183b2464517b2cd7ce5a8f15a0000000000000000000000000000000000000000000000000de0b6b3a7640000",
   580  		"operation": 0,
   581  		"safeTxGas": 0,
   582  		"baseGas": 0,
   583  		"gasPrice": "0",
   584  		"gasToken": "0x0000000000000000000000000000000000000000",
   585  		"refundReceiver": "0x0000000000000000000000000000000000000000",
   586  		"nonce": 15
   587  	}
   588  }`
   589  
   590  var gnosisTxWithChainId = `
   591  {
   592  	"safe": "0x111dAE35D176A9607053e0c46e91F36AFbC1dc57",
   593  	"to": "0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa",
   594  	"value": "0",
   595  	"data": "0xa9059cbb00000000000000000000000099d580d3a7fe7bd183b2464517b2cd7ce5a8f15a0000000000000000000000000000000000000000000000000de0b6b3a7640000",
   596  	"operation": 0,
   597  	"gasToken": "0x0000000000000000000000000000000000000000",
   598  	"safeTxGas": 0,
   599  	"baseGas": 0,
   600  	"gasPrice": "0",
   601  	"refundReceiver": "0x0000000000000000000000000000000000000000",
   602  	"nonce": 15,
   603  	"executionDate": "2022-01-10T20:00:12Z",
   604  	"submissionDate": "2022-01-10T19:59:59.689989Z",
   605  	"modified": "2022-01-10T20:00:31.903635Z",
   606  	"blockNumber": 9968802,
   607  	"transactionHash": "0xc9fef30499ee8984974ab9dddd9d15c2a97c1a4393935dceed5efc3af9fc41a4",
   608  	"safeTxHash": "0x6619dab5401503f2735256e12b898e69eb701d6a7e0d07abf1be4bb8aebfba29",
   609  	"executor": "0xbc2BB26a6d821e69A38016f3858561a1D80d4182",
   610  	"isExecuted": true,
   611  	"isSuccessful": true,
   612  	"ethGasPrice": "2500000009",
   613  	"gasUsed": 82902,
   614  	"fee": "207255000746118",
   615  	"chainId": "4",
   616  	"origin": null,
   617  	"dataDecoded": {
   618  		"method": "transfer",
   619  		"parameters": [
   620  				{
   621  				"name": "to",
   622  				"type": "address",
   623  				"value": "0x99D580d3a7FE7BD183b2464517B2cD7ce5A8F15A"
   624  				},
   625  				{
   626  				"name": "value",
   627  				"type": "uint256",
   628  				"value": "1000000000000000000"
   629  				}
   630  		]
   631  	},
   632  	"confirmationsRequired": 1,
   633  	"confirmations": [
   634  		{
   635  		"owner": "0xbc2BB26a6d821e69A38016f3858561a1D80d4182",
   636  		"submissionDate": "2022-01-10T19:59:59.722500Z",
   637  		"transactionHash": null,
   638  		"signature": "0x5ca34641bcdee06e7b99143bfe34778195ca41022bd35837b96c204c7786be9d6dfa6dba43b53cd92da45ac728899e1561b232d28f38ba82df45f164caba38be1b",
   639  		"signatureType": "EOA"
   640  		}
   641  	],
   642  	"signatures": "0x5ca34641bcdee06e7b99143bfe34778195ca41022bd35837b96c204c7786be9d6dfa6dba43b53cd92da45ac728899e1561b232d28f38ba82df45f164caba38be1b"
   643  }
   644  `
   645  
   646  func TestGnosisTypedDataWithChainId(t *testing.T) {
   647  	var td apitypes.TypedData
   648  	err := json.Unmarshal([]byte(gnosisTypedDataWithChainId), &td)
   649  	if err != nil {
   650  		t.Fatalf("unmarshalling failed '%v'", err)
   651  	}
   652  	_, sighash, err := sign(td)
   653  	if err != nil {
   654  		t.Fatal(err)
   655  	}
   656  	expSigHash := common.FromHex("0x6619dab5401503f2735256e12b898e69eb701d6a7e0d07abf1be4bb8aebfba29")
   657  	if !bytes.Equal(expSigHash, sighash) {
   658  		t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash)
   659  	}
   660  }
   661  
   662  // TestGnosisCustomData tests the scenario where a user submits only the gnosis-safe
   663  // specific data, and we fill the TypedData struct on our side
   664  func TestGnosisCustomDataWithChainId(t *testing.T) {
   665  	var tx core.GnosisSafeTx
   666  	err := json.Unmarshal([]byte(gnosisTxWithChainId), &tx)
   667  	if err != nil {
   668  		t.Fatal(err)
   669  	}
   670  	var td = tx.ToTypedData()
   671  	_, sighash, err := sign(td)
   672  	if err != nil {
   673  		t.Fatal(err)
   674  	}
   675  	expSigHash := common.FromHex("0x6619dab5401503f2735256e12b898e69eb701d6a7e0d07abf1be4bb8aebfba29")
   676  	if !bytes.Equal(expSigHash, sighash) {
   677  		t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash)
   678  	}
   679  }
   680  
   681  var complexTypedData = `
   682  {
   683      "types": {
   684          "EIP712Domain": [
   685              {
   686                  "name": "chainId",
   687                  "type": "uint256"
   688              },
   689              {
   690                  "name": "name",
   691                  "type": "string"
   692              },
   693              {
   694                  "name": "verifyingContract",
   695                  "type": "address"
   696              },
   697              {
   698                  "name": "version",
   699                  "type": "string"
   700              }
   701          ],
   702          "Action": [
   703              {
   704                  "name": "action",
   705                  "type": "string"
   706              },
   707              {
   708                  "name": "params",
   709                  "type": "string"
   710              }
   711          ],
   712          "Cell": [
   713              {
   714                  "name": "capacity",
   715                  "type": "string"
   716              },
   717              {
   718                  "name": "lock",
   719                  "type": "string"
   720              },
   721              {
   722                  "name": "type",
   723                  "type": "string"
   724              },
   725              {
   726                  "name": "data",
   727                  "type": "string"
   728              },
   729              {
   730                  "name": "extraData",
   731                  "type": "string"
   732              }
   733          ],
   734          "Transaction": [
   735              {
   736                  "name": "DAS_MESSAGE",
   737                  "type": "string"
   738              },
   739              {
   740                  "name": "inputsCapacity",
   741                  "type": "string"
   742              },
   743              {
   744                  "name": "outputsCapacity",
   745                  "type": "string"
   746              },
   747              {
   748                  "name": "fee",
   749                  "type": "string"
   750              },
   751              {
   752                  "name": "action",
   753                  "type": "Action"
   754              },
   755              {
   756                  "name": "inputs",
   757                  "type": "Cell[]"
   758              },
   759              {
   760                  "name": "outputs",
   761                  "type": "Cell[]"
   762              },
   763              {
   764                  "name": "digest",
   765                  "type": "bytes32"
   766              }
   767          ]
   768      },
   769      "primaryType": "Transaction",
   770      "domain": {
   771          "chainId": "56",
   772          "name": "da.systems",
   773          "verifyingContract": "0x0000000000000000000000000000000020210722",
   774          "version": "1"
   775      },
   776      "message": {
   777          "DAS_MESSAGE": "SELL mobcion.bit FOR 100000 CKB",
   778          "inputsCapacity": "1216.9999 CKB",
   779          "outputsCapacity": "1216.9998 CKB",
   780          "fee": "0.0001 CKB",
   781          "digest": "0x53a6c0f19ec281604607f5d6817e442082ad1882bef0df64d84d3810dae561eb",
   782          "action": {
   783              "action": "start_account_sale",
   784              "params": "0x00"
   785          },
   786          "inputs": [
   787              {
   788                  "capacity": "218 CKB",
   789                  "lock": "das-lock,0x01,0x051c152f77f8efa9c7c6d181cc97ee67c165c506...",
   790                  "type": "account-cell-type,0x01,0x",
   791                  "data": "{ account: mobcion.bit, expired_at: 1670913958 }",
   792                  "extraData": "{ status: 0, records_hash: 0x55478d76900611eb079b22088081124ed6c8bae21a05dd1a0d197efcc7c114ce }"
   793              }
   794          ],
   795          "outputs": [
   796              {
   797                  "capacity": "218 CKB",
   798                  "lock": "das-lock,0x01,0x051c152f77f8efa9c7c6d181cc97ee67c165c506...",
   799                  "type": "account-cell-type,0x01,0x",
   800                  "data": "{ account: mobcion.bit, expired_at: 1670913958 }",
   801                  "extraData": "{ status: 1, records_hash: 0x55478d76900611eb079b22088081124ed6c8bae21a05dd1a0d197efcc7c114ce }"
   802              },
   803              {
   804                  "capacity": "201 CKB",
   805                  "lock": "das-lock,0x01,0x051c152f77f8efa9c7c6d181cc97ee67c165c506...",
   806                  "type": "account-sale-cell-type,0x01,0x",
   807                  "data": "0x1209460ef3cb5f1c68ed2c43a3e020eec2d9de6e...",
   808                  "extraData": ""
   809              }
   810          ]
   811      }
   812  }
   813  `
   814  
   815  func TestComplexTypedData(t *testing.T) {
   816  	var td apitypes.TypedData
   817  	err := json.Unmarshal([]byte(complexTypedData), &td)
   818  	if err != nil {
   819  		t.Fatalf("unmarshalling failed '%v'", err)
   820  	}
   821  	_, sighash, err := sign(td)
   822  	if err != nil {
   823  		t.Fatal(err)
   824  	}
   825  	expSigHash := common.FromHex("0x42b1aca82bb6900ff75e90a136de550a58f1a220a071704088eabd5e6ce20446")
   826  	if !bytes.Equal(expSigHash, sighash) {
   827  		t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash)
   828  	}
   829  }
   830  
   831  func TestGnosisSafe(t *testing.T) {
   832  	// json missing chain id
   833  	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"
   834  	var gnosisTx core.GnosisSafeTx
   835  	if err := json.Unmarshal([]byte(js), &gnosisTx); err != nil {
   836  		t.Fatal(err)
   837  	}
   838  	sighash, _, err := apitypes.TypedDataAndHash(gnosisTx.ToTypedData())
   839  	if err != nil {
   840  		t.Fatal(err)
   841  	}
   842  	if bytes.Equal(sighash, gnosisTx.InputExpHash.Bytes()) {
   843  		t.Fatal("expected inequality")
   844  	}
   845  	gnosisTx.ChainId = (*math.HexOrDecimal256)(big.NewInt(1))
   846  	sighash, _, _ = apitypes.TypedDataAndHash(gnosisTx.ToTypedData())
   847  	if !bytes.Equal(sighash, gnosisTx.InputExpHash.Bytes()) {
   848  		t.Fatal("expected equality")
   849  	}
   850  }