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