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