signed_data_test.go 15 KB

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