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 }