github.com/core-coin/go-core/v2@v2.1.9/signer/core/signed_data_test.go (about) 1 // Copyright 2019 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core 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-core 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-core 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/core-coin/go-core/v2/accounts/keystore" 30 "github.com/core-coin/go-core/v2/common" 31 "github.com/core-coin/go-core/v2/common/hexutil" 32 "github.com/core-coin/go-core/v2/common/math" 33 "github.com/core-coin/go-core/v2/crypto" 34 "github.com/core-coin/go-core/v2/signer/core" 35 ) 36 37 var typesStandard = core.Types{ 38 "CIP712Domain": { 39 { 40 Name: "name", 41 Type: "string", 42 }, 43 { 44 Name: "version", 45 Type: "string", 46 }, 47 { 48 Name: "networkId", 49 Type: "uint256", 50 }, 51 { 52 Name: "verifyingContract", 53 Type: "address", 54 }, 55 }, 56 "Person": { 57 { 58 Name: "name", 59 Type: "string", 60 }, 61 { 62 Name: "wallet", 63 Type: "address", 64 }, 65 }, 66 "Mail": { 67 { 68 Name: "from", 69 Type: "Person", 70 }, 71 { 72 Name: "to", 73 Type: "Person", 74 }, 75 { 76 Name: "contents", 77 Type: "string", 78 }, 79 }, 80 } 81 82 var jsonTypedData = ` 83 { 84 "types": { 85 "CIP712Domain": [ 86 { 87 "name": "name", 88 "type": "string" 89 }, 90 { 91 "name": "version", 92 "type": "string" 93 }, 94 { 95 "name": "networkId", 96 "type": "uint256" 97 }, 98 { 99 "name": "verifyingContract", 100 "type": "address" 101 } 102 ], 103 "Person": [ 104 { 105 "name": "name", 106 "type": "string" 107 }, 108 { 109 "name": "test", 110 "type": "uint8" 111 }, 112 { 113 "name": "wallet", 114 "type": "address" 115 } 116 ], 117 "Mail": [ 118 { 119 "name": "from", 120 "type": "Person" 121 }, 122 { 123 "name": "to", 124 "type": "Person" 125 }, 126 { 127 "name": "contents", 128 "type": "string" 129 } 130 ] 131 }, 132 "primaryType": "Mail", 133 "domain": { 134 "name": "Core Mail", 135 "version": "1", 136 "networkId": "1", 137 "verifyingContract": "cb375a538daf54f2e568bb4237357b1cee1aa3cb7eba" 138 }, 139 "message": { 140 "from": { 141 "name": "Cow", 142 "test": 3, 143 "wallet": "cb76a631db606f1452ddc2432931d611f1d5b126f848" 144 }, 145 "to": { 146 "name": "Bob", 147 "wallet": "cb27de521e43741cf785cbad450d5649187b9612018f" 148 }, 149 "contents": "Hello, Bob!" 150 } 151 } 152 ` 153 154 const primaryType = "Mail" 155 156 var domainStandard = core.TypedDataDomain{ 157 "Core Mail", 158 "1", 159 math.NewHexOrDecimal256(1), 160 "cb375a538daf54f2e568bb4237357b1cee1aa3cb7eba", 161 "", 162 } 163 164 var messageStandard = map[string]interface{}{ 165 "from": map[string]interface{}{ 166 "name": "Cow", 167 "wallet": "cb76a631db606f1452ddc2432931d611f1d5b126f848", 168 }, 169 "to": map[string]interface{}{ 170 "name": "Bob", 171 "wallet": "cb27de521e43741cf785cbad450d5649187b9612018f", 172 }, 173 "contents": "Hello, Bob!", 174 } 175 176 var typedData = core.TypedData{ 177 Types: typesStandard, 178 PrimaryType: primaryType, 179 Domain: domainStandard, 180 Message: messageStandard, 181 } 182 183 func TestSignData(t *testing.T) { 184 api, control := setup(t) 185 //Create two accounts 186 createAccount(control, api, t) 187 createAccount(control, api, t) 188 control.approveCh <- "1" 189 list, err := api.List(context.Background()) 190 if err != nil { 191 t.Fatal(err) 192 } 193 a := list[0] 194 195 control.approveCh <- "Y" 196 control.inputCh <- "wrongpassword" 197 signature, err := api.SignData(context.Background(), core.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) 198 if signature != nil { 199 t.Errorf("Expected nil-data, got %x", signature) 200 } 201 if err != keystore.ErrDecrypt { 202 t.Errorf("Expected ErrLocked! '%v'", err) 203 } 204 control.approveCh <- "No way" 205 signature, err = api.SignData(context.Background(), core.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) 206 if signature != nil { 207 t.Errorf("Expected nil-data, got %x", signature) 208 } 209 if err != core.ErrRequestDenied { 210 t.Errorf("Expected ErrRequestDenied! '%v'", err) 211 } 212 // text/plain 213 control.approveCh <- "Y" 214 control.inputCh <- "a_long_password" 215 signature, err = api.SignData(context.Background(), core.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) 216 if err != nil { 217 t.Fatal(err) 218 } 219 if signature == nil || len(signature) != crypto.ExtendedSignatureLength { 220 t.Errorf("Expected crypto.ExtendedSignatureLength byte signature (got %d bytes)", len(signature)) 221 } 222 // data/typed 223 control.approveCh <- "Y" 224 control.inputCh <- "a_long_password" 225 signature, err = api.SignTypedData(context.Background(), a, typedData) 226 if err != nil { 227 t.Fatal(err) 228 } 229 if signature == nil || len(signature) != crypto.ExtendedSignatureLength { 230 t.Errorf("Expected crypto.ExtendedSignatureLength byte signature (got %d bytes)", len(signature)) 231 } 232 } 233 234 func TestDomainNetworkId(t *testing.T) { 235 withoutNetworkID := core.TypedData{ 236 Types: core.Types{ 237 "CIP712Domain": []core.Type{ 238 {Name: "name", Type: "string"}, 239 }, 240 }, 241 Domain: core.TypedDataDomain{ 242 Name: "test", 243 }, 244 } 245 246 if _, ok := withoutNetworkID.Domain.Map()["networkId"]; ok { 247 t.Errorf("Expected the networkId key to not be present in the domain map") 248 } 249 // should encode successfully 250 if _, err := withoutNetworkID.HashStruct("CIP712Domain", withoutNetworkID.Domain.Map()); err != nil { 251 t.Errorf("Expected the typedData to encode the domain successfully, got %v", err) 252 } 253 withNetworkID := core.TypedData{ 254 Types: core.Types{ 255 "CIP712Domain": []core.Type{ 256 {Name: "name", Type: "string"}, 257 {Name: "networkId", Type: "uint256"}, 258 }, 259 }, 260 Domain: core.TypedDataDomain{ 261 Name: "test", 262 NetworkId: math.NewHexOrDecimal256(1), 263 }, 264 } 265 266 if _, ok := withNetworkID.Domain.Map()["networkId"]; !ok { 267 t.Errorf("Expected the networkId key be present in the domain map") 268 } 269 // should encode successfully 270 if _, err := withNetworkID.HashStruct("CIP712Domain", withNetworkID.Domain.Map()); err != nil { 271 t.Errorf("Expected the typedData to encode the domain successfully, got %v", err) 272 } 273 } 274 275 func TestHashStruct(t *testing.T) { 276 hash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) 277 if err != nil { 278 t.Fatal(err) 279 } 280 mainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(hash)) 281 if mainHash != "0xef6fddde45efef4974d865911ed95b5713bad9416a70aefe6f1c9cf7cf2effdb" { 282 t.Errorf("Expected different hashStruct result (got %s)", mainHash) 283 } 284 285 hash, err = typedData.HashStruct("CIP712Domain", typedData.Domain.Map()) 286 if err != nil { 287 t.Error(err) 288 } 289 domainHash := fmt.Sprintf("0x%s", common.Bytes2Hex(hash)) 290 if domainHash != "0x453039a903a6cb0751f4b709f858d104351ba885a17a7daf98d7e6442d9f324d" { 291 t.Errorf("Expected different domain hashStruct result (got %s)", domainHash) 292 } 293 } 294 295 func TestEncodeType(t *testing.T) { 296 domainTypeEncoding := string(typedData.EncodeType("CIP712Domain")) 297 if domainTypeEncoding != "CIP712Domain(string name,string version,uint256 networkId,address verifyingContract)" { 298 t.Errorf("Expected different encodeType result (got %s)", domainTypeEncoding) 299 } 300 301 mailTypeEncoding := string(typedData.EncodeType(typedData.PrimaryType)) 302 if mailTypeEncoding != "Mail(Person from,Person to,string contents)Person(string name,address wallet)" { 303 t.Errorf("Expected different encodeType result (got %s)", mailTypeEncoding) 304 } 305 } 306 307 func TestTypeHash(t *testing.T) { 308 mailTypeHash := fmt.Sprintf("0x%s", common.Bytes2Hex(typedData.TypeHash(typedData.PrimaryType))) 309 if mailTypeHash != "0xda8b122f9405015467a4c2d2b5d72f976d0dcd07f39d640df998cb582f24622b" { 310 t.Errorf("Expected different typeHash result (got %s)", mailTypeHash) 311 } 312 } 313 314 func TestEncodeData(t *testing.T) { 315 hash, err := typedData.EncodeData(typedData.PrimaryType, typedData.Message, 0) 316 if err != nil { 317 t.Fatal(err) 318 } 319 dataEncoding := fmt.Sprintf("0x%s", common.Bytes2Hex(hash)) 320 if dataEncoding != "0xda8b122f9405015467a4c2d2b5d72f976d0dcd07f39d640df998cb582f24622bb5a2006b708c2c1a6ecb8e7e28b588a0c0f1cc91e1ad7f834b1b85a8b04fbc31b7287cdbb157715c966c50826f17d51a9311dec62c8957819d6b4de0bbc55b9cb58543c145f315ad2c9210b45c29c13e6c9fc5396a140d3b07f766925fda360e" { 321 t.Errorf("Expected different encodeData result (got %s)", dataEncoding) 322 } 323 } 324 325 func TestFormatter(t *testing.T) { 326 var d core.TypedData 327 err := json.Unmarshal([]byte(jsonTypedData), &d) 328 if err != nil { 329 t.Fatalf("unmarshalling failed '%v'", err) 330 } 331 formatted, _ := d.Format() 332 for _, item := range formatted { 333 t.Logf("'%v'\n", item.Pprint(0)) 334 } 335 336 j, _ := json.Marshal(formatted) 337 t.Logf("'%v'\n", string(j)) 338 } 339 340 func sign(typedData core.TypedData) ([]byte, []byte, error) { 341 domainSeparator, err := typedData.HashStruct("CIP712Domain", typedData.Domain.Map()) 342 if err != nil { 343 return nil, nil, err 344 } 345 typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) 346 if err != nil { 347 return nil, nil, err 348 } 349 rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash))) 350 sighash := crypto.SHA3(rawData) 351 return typedDataHash, sighash, nil 352 } 353 354 func TestJsonFiles(t *testing.T) { 355 testfiles, err := ioutil.ReadDir("testdata/") 356 if err != nil { 357 t.Fatalf("failed reading files: %v", err) 358 } 359 for i, fInfo := range testfiles { 360 if !strings.HasSuffix(fInfo.Name(), "json") { 361 continue 362 } 363 expectedFailure := strings.HasPrefix(fInfo.Name(), "expfail") 364 data, err := ioutil.ReadFile(path.Join("testdata", fInfo.Name())) 365 if err != nil { 366 t.Errorf("Failed to read file %v: %v", fInfo.Name(), err) 367 continue 368 } 369 var typedData core.TypedData 370 err = json.Unmarshal(data, &typedData) 371 if err != nil { 372 t.Errorf("Test %d, file %v, json unmarshalling failed: %v", i, fInfo.Name(), err) 373 continue 374 } 375 _, _, err = sign(typedData) 376 t.Logf("Error %v\n", err) 377 if err != nil && !expectedFailure { 378 t.Errorf("Test %d failed, file %v: %v", i, fInfo.Name(), err) 379 } 380 if expectedFailure && err == nil { 381 t.Errorf("Test %d succeeded (expected failure), file %v: %v", i, fInfo.Name(), err) 382 } 383 } 384 } 385 386 // TestFuzzerFiles tests some files that have been found by fuzzing to cause 387 // crashes or hangs. 388 func TestFuzzerFiles(t *testing.T) { 389 corpusdir := path.Join("testdata", "fuzzing") 390 testfiles, err := ioutil.ReadDir(corpusdir) 391 if err != nil { 392 t.Fatalf("failed reading files: %v", err) 393 } 394 verbose := false 395 for i, fInfo := range testfiles { 396 data, err := ioutil.ReadFile(path.Join(corpusdir, fInfo.Name())) 397 if err != nil { 398 t.Errorf("Failed to read file %v: %v", fInfo.Name(), err) 399 continue 400 } 401 var typedData core.TypedData 402 err = json.Unmarshal(data, &typedData) 403 if err != nil { 404 t.Errorf("Test %d, file %v, json unmarshalling failed: %v", i, fInfo.Name(), err) 405 continue 406 } 407 _, err = typedData.EncodeData("CIP712Domain", typedData.Domain.Map(), 1) 408 if verbose && err != nil { 409 t.Logf("%d, EncodeData[1] err: %v\n", i, err) 410 } 411 _, err = typedData.EncodeData(typedData.PrimaryType, typedData.Message, 1) 412 if verbose && err != nil { 413 t.Logf("%d, EncodeData[2] err: %v\n", i, err) 414 } 415 typedData.Format() 416 } 417 } 418 419 var gnosisTypedData = ` 420 { 421 "types": { 422 "CIP712Domain": [ 423 { "type": "address", "name": "verifyingContract" } 424 ], 425 "SafeTx": [ 426 { "type": "address", "name": "to" }, 427 { "type": "uint256", "name": "value" }, 428 { "type": "bytes", "name": "data" }, 429 { "type": "uint8", "name": "operation" }, 430 { "type": "uint256", "name": "safeTxEnergy" }, 431 { "type": "uint256", "name": "baseEnergy" }, 432 { "type": "uint256", "name": "energyPrice" }, 433 { "type": "address", "name": "energyToken" }, 434 { "type": "address", "name": "refundReceiver" }, 435 { "type": "uint256", "name": "nonce" } 436 ] 437 }, 438 "domain": { 439 "verifyingContract": "cb45f23d9ab6aefb2c22dfff511e29703435a3b50f50" 440 }, 441 "primaryType": "SafeTx", 442 "message": { 443 "to": "cb65e49851f010cd7d81b5b4969f3b0e8325c415359d", 444 "value": "20000000000000000", 445 "data": "0x", 446 "operation": 0, 447 "safeTxEnergy": 27845, 448 "baseEnergy": 0, 449 "energyPrice": "0", 450 "energyToken": "cb540000000000000000000000000000000000000000", 451 "refundReceiver": "cb540000000000000000000000000000000000000000", 452 "nonce": 3 453 } 454 }` 455 456 var gnosisTx = ` 457 { 458 "safe": "cb45f23d9ab6aefb2c22dfff511e29703435a3b50f50", 459 "to": "cb65e49851f010cd7d81b5b4969f3b0e8325c415359d", 460 "value": "20000000000000000", 461 "data": null, 462 "operation": 0, 463 "energyToken": "cb540000000000000000000000000000000000000000", 464 "safeTxEnergy": 27845, 465 "baseEnergy": 0, 466 "energyPrice": "0", 467 "refundReceiver": "cb540000000000000000000000000000000000000000", 468 "nonce": 3, 469 "executionDate": null, 470 "submissionDate": "2020-09-15T21:59:23.815748Z", 471 "modified": "2020-09-15T21:59:23.815748Z", 472 "blockNumber": null, 473 "transactionHash": null, 474 "safeTxHash": "0x28bae2bd58d894a1d9b69e5e9fde3570c4b98a6fc5499aefb54fb830137e831f", 475 "executor": null, 476 "isExecuted": false, 477 "isSuccessful": null, 478 "xcbEnergyPrice": null, 479 "energyUsed": null, 480 "fee": null, 481 "origin": null, 482 "dataDecoded": null, 483 "confirmationsRequired": null, 484 "confirmations": [ 485 { 486 "owner": "0xAd2e180019FCa9e55CADe76E4487F126Fd08DA34", 487 "submissionDate": "2020-09-15T21:59:28.281243Z", 488 "transactionHash": null, 489 "confirmationType": "CONFIRMATION", 490 "signature": "0x5e562065a0cb15d766dac0cd49eb6d196a41183af302c4ecad45f1a81958d7797753f04424a9b0aa1cb0448e4ec8e189540fbcdda7530ef9b9d95dfc2d36cb521b", 491 "signatureType": "EOA" 492 } 493 ], 494 "signatures": null 495 } 496 ` 497 498 // TestGnosisTypedData tests the scenario where a user submits a full CIP-712 499 // struct without using the gnosis-specific endpoint 500 func TestGnosisTypedData(t *testing.T) { 501 var td core.TypedData 502 err := json.Unmarshal([]byte(gnosisTypedData), &td) 503 if err != nil { 504 t.Fatalf("unmarshalling failed '%v'", err) 505 } 506 _, sighash, err := sign(td) 507 if err != nil { 508 t.Fatal(err) 509 } 510 expSigHash := common.FromHex("0x402d167803f194fe5e448757cbac9a9c17fb4674a7da5c061233f49118c379b7") 511 if !bytes.Equal(expSigHash, sighash) { 512 t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash) 513 } 514 } 515 516 // TestGnosisCustomData tests the scenario where a user submits only the gnosis-safe 517 // specific data, and we fill the TypedData struct on our side 518 func TestGnosisCustomData(t *testing.T) { 519 var tx core.GnosisSafeTx 520 err := json.Unmarshal([]byte(gnosisTx), &tx) 521 if err != nil { 522 t.Fatal(err) 523 } 524 var td = tx.ToTypedData() 525 _, sighash, err := sign(td) 526 if err != nil { 527 t.Fatal(err) 528 } 529 expSigHash := common.FromHex("0x402d167803f194fe5e448757cbac9a9c17fb4674a7da5c061233f49118c379b7") 530 if !bytes.Equal(expSigHash, sighash) { 531 t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash) 532 } 533 }