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