github.com/felberj/go-ethereum@v1.8.23/signer/rules/rules_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 rules 18 19 import ( 20 "fmt" 21 "math/big" 22 "strings" 23 "testing" 24 25 "github.com/ethereum/go-ethereum/accounts" 26 "github.com/ethereum/go-ethereum/common" 27 "github.com/ethereum/go-ethereum/common/hexutil" 28 "github.com/ethereum/go-ethereum/core/types" 29 "github.com/ethereum/go-ethereum/internal/ethapi" 30 "github.com/ethereum/go-ethereum/signer/core" 31 "github.com/ethereum/go-ethereum/signer/storage" 32 ) 33 34 const JS = ` 35 /** 36 This is an example implementation of a Javascript rule file. 37 38 When the signer receives a request over the external API, the corresponding method is evaluated. 39 Three things can happen: 40 41 1. The method returns "Approve". This means the operation is permitted. 42 2. The method returns "Reject". This means the operation is rejected. 43 3. Anything else; other return values [*], method not implemented or exception occurred during processing. This means 44 that the operation will continue to manual processing, via the regular UI method chosen by the user. 45 46 [*] Note: Future version of the ruleset may use more complex json-based returnvalues, making it possible to not 47 only respond Approve/Reject/Manual, but also modify responses. For example, choose to list only one, but not all 48 accounts in a list-request. The points above will continue to hold for non-json based responses ("Approve"/"Reject"). 49 50 **/ 51 52 function ApproveListing(request){ 53 console.log("In js approve listing"); 54 console.log(request.accounts[3].Address) 55 console.log(request.meta.Remote) 56 return "Approve" 57 } 58 59 function ApproveTx(request){ 60 console.log("test"); 61 console.log("from"); 62 return "Reject"; 63 } 64 65 function test(thing){ 66 console.log(thing.String()) 67 } 68 69 ` 70 71 func mixAddr(a string) (*common.MixedcaseAddress, error) { 72 return common.NewMixedcaseAddressFromString(a) 73 } 74 75 type alwaysDenyUI struct{} 76 77 func (alwaysDenyUI) OnInputRequired(info core.UserInputRequest) (core.UserInputResponse, error) { 78 return core.UserInputResponse{}, nil 79 } 80 81 func (alwaysDenyUI) OnSignerStartup(info core.StartupInfo) { 82 } 83 84 func (alwaysDenyUI) OnMasterPassword(request *core.PasswordRequest) (core.PasswordResponse, error) { 85 return core.PasswordResponse{}, nil 86 } 87 88 func (alwaysDenyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { 89 return core.SignTxResponse{Transaction: request.Transaction, Approved: false, Password: ""}, nil 90 } 91 92 func (alwaysDenyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) { 93 return core.SignDataResponse{Approved: false, Password: ""}, nil 94 } 95 96 func (alwaysDenyUI) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) { 97 return core.ExportResponse{Approved: false}, nil 98 } 99 100 func (alwaysDenyUI) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) { 101 return core.ImportResponse{Approved: false, OldPassword: "", NewPassword: ""}, nil 102 } 103 104 func (alwaysDenyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) { 105 return core.ListResponse{Accounts: nil}, nil 106 } 107 108 func (alwaysDenyUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) { 109 return core.NewAccountResponse{Approved: false, Password: ""}, nil 110 } 111 112 func (alwaysDenyUI) ShowError(message string) { 113 panic("implement me") 114 } 115 116 func (alwaysDenyUI) ShowInfo(message string) { 117 panic("implement me") 118 } 119 120 func (alwaysDenyUI) OnApprovedTx(tx ethapi.SignTransactionResult) { 121 panic("implement me") 122 } 123 124 func initRuleEngine(js string) (*rulesetUI, error) { 125 r, err := NewRuleEvaluator(&alwaysDenyUI{}, storage.NewEphemeralStorage(), storage.NewEphemeralStorage()) 126 if err != nil { 127 return nil, fmt.Errorf("failed to create js engine: %v", err) 128 } 129 if err = r.Init(js); err != nil { 130 return nil, fmt.Errorf("failed to load bootstrap js: %v", err) 131 } 132 return r, nil 133 } 134 135 func TestListRequest(t *testing.T) { 136 accs := make([]core.Account, 5) 137 138 for i := range accs { 139 addr := fmt.Sprintf("000000000000000000000000000000000000000%x", i) 140 acc := core.Account{ 141 Address: common.BytesToAddress(common.Hex2Bytes(addr)), 142 URL: accounts.URL{Scheme: "test", Path: fmt.Sprintf("acc-%d", i)}, 143 } 144 accs[i] = acc 145 } 146 147 js := `function ApproveListing(){ return "Approve" }` 148 149 r, err := initRuleEngine(js) 150 if err != nil { 151 t.Errorf("Couldn't create evaluator %v", err) 152 return 153 } 154 resp, _ := r.ApproveListing(&core.ListRequest{ 155 Accounts: accs, 156 Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"}, 157 }) 158 if len(resp.Accounts) != len(accs) { 159 t.Errorf("Expected check to resolve to 'Approve'") 160 } 161 } 162 163 func TestSignTxRequest(t *testing.T) { 164 165 js := ` 166 function ApproveTx(r){ 167 console.log("transaction.from", r.transaction.from); 168 console.log("transaction.to", r.transaction.to); 169 console.log("transaction.value", r.transaction.value); 170 console.log("transaction.nonce", r.transaction.nonce); 171 if(r.transaction.from.toLowerCase()=="0x0000000000000000000000000000000000001337"){ return "Approve"} 172 if(r.transaction.from.toLowerCase()=="0x000000000000000000000000000000000000dead"){ return "Reject"} 173 }` 174 175 r, err := initRuleEngine(js) 176 if err != nil { 177 t.Errorf("Couldn't create evaluator %v", err) 178 return 179 } 180 to, err := mixAddr("000000000000000000000000000000000000dead") 181 if err != nil { 182 t.Error(err) 183 return 184 } 185 from, err := mixAddr("0000000000000000000000000000000000001337") 186 187 if err != nil { 188 t.Error(err) 189 return 190 } 191 fmt.Printf("to %v", to.Address().String()) 192 resp, err := r.ApproveTx(&core.SignTxRequest{ 193 Transaction: core.SendTxArgs{ 194 From: *from, 195 To: to}, 196 Callinfo: nil, 197 Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"}, 198 }) 199 if err != nil { 200 t.Errorf("Unexpected error %v", err) 201 } 202 if !resp.Approved { 203 t.Errorf("Expected check to resolve to 'Approve'") 204 } 205 } 206 207 type dummyUI struct { 208 calls []string 209 } 210 211 func (d *dummyUI) OnInputRequired(info core.UserInputRequest) (core.UserInputResponse, error) { 212 d.calls = append(d.calls, "OnInputRequired") 213 return core.UserInputResponse{}, nil 214 } 215 216 func (d *dummyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { 217 d.calls = append(d.calls, "ApproveTx") 218 return core.SignTxResponse{}, core.ErrRequestDenied 219 } 220 221 func (d *dummyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) { 222 d.calls = append(d.calls, "ApproveSignData") 223 return core.SignDataResponse{}, core.ErrRequestDenied 224 } 225 226 func (d *dummyUI) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) { 227 d.calls = append(d.calls, "ApproveExport") 228 return core.ExportResponse{}, core.ErrRequestDenied 229 } 230 231 func (d *dummyUI) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) { 232 d.calls = append(d.calls, "ApproveImport") 233 return core.ImportResponse{}, core.ErrRequestDenied 234 } 235 236 func (d *dummyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) { 237 d.calls = append(d.calls, "ApproveListing") 238 return core.ListResponse{}, core.ErrRequestDenied 239 } 240 241 func (d *dummyUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) { 242 d.calls = append(d.calls, "ApproveNewAccount") 243 return core.NewAccountResponse{}, core.ErrRequestDenied 244 } 245 246 func (d *dummyUI) ShowError(message string) { 247 d.calls = append(d.calls, "ShowError") 248 } 249 250 func (d *dummyUI) ShowInfo(message string) { 251 d.calls = append(d.calls, "ShowInfo") 252 } 253 254 func (d *dummyUI) OnApprovedTx(tx ethapi.SignTransactionResult) { 255 d.calls = append(d.calls, "OnApprovedTx") 256 } 257 258 func (d *dummyUI) OnMasterPassword(request *core.PasswordRequest) (core.PasswordResponse, error) { 259 return core.PasswordResponse{}, nil 260 } 261 262 func (d *dummyUI) OnSignerStartup(info core.StartupInfo) { 263 } 264 265 //TestForwarding tests that the rule-engine correctly dispatches requests to the next caller 266 func TestForwarding(t *testing.T) { 267 268 js := "" 269 ui := &dummyUI{make([]string, 0)} 270 jsBackend := storage.NewEphemeralStorage() 271 credBackend := storage.NewEphemeralStorage() 272 r, err := NewRuleEvaluator(ui, jsBackend, credBackend) 273 if err != nil { 274 t.Fatalf("Failed to create js engine: %v", err) 275 } 276 if err = r.Init(js); err != nil { 277 t.Fatalf("Failed to load bootstrap js: %v", err) 278 } 279 r.ApproveSignData(nil) 280 r.ApproveTx(nil) 281 r.ApproveImport(nil) 282 r.ApproveNewAccount(nil) 283 r.ApproveListing(nil) 284 r.ApproveExport(nil) 285 r.ShowError("test") 286 r.ShowInfo("test") 287 288 //This one is not forwarded 289 r.OnApprovedTx(ethapi.SignTransactionResult{}) 290 291 expCalls := 8 292 if len(ui.calls) != expCalls { 293 294 t.Errorf("Expected %d forwarded calls, got %d: %s", expCalls, len(ui.calls), strings.Join(ui.calls, ",")) 295 296 } 297 298 } 299 300 func TestMissingFunc(t *testing.T) { 301 r, err := initRuleEngine(JS) 302 if err != nil { 303 t.Errorf("Couldn't create evaluator %v", err) 304 return 305 } 306 307 _, err = r.execute("MissingMethod", "test") 308 309 if err == nil { 310 t.Error("Expected error") 311 } 312 313 approved, err := r.checkApproval("MissingMethod", nil, nil) 314 if err == nil { 315 t.Errorf("Expected missing method to yield error'") 316 } 317 if approved { 318 t.Errorf("Expected missing method to cause non-approval") 319 } 320 fmt.Printf("Err %v", err) 321 322 } 323 func TestStorage(t *testing.T) { 324 325 js := ` 326 function testStorage(){ 327 storage.Put("mykey", "myvalue") 328 a = storage.Get("mykey") 329 330 storage.Put("mykey", ["a", "list"]) // Should result in "a,list" 331 a += storage.Get("mykey") 332 333 334 storage.Put("mykey", {"an": "object"}) // Should result in "[object Object]" 335 a += storage.Get("mykey") 336 337 338 storage.Put("mykey", JSON.stringify({"an": "object"})) // Should result in '{"an":"object"}' 339 a += storage.Get("mykey") 340 341 a += storage.Get("missingkey") //Missing keys should result in empty string 342 storage.Put("","missing key==noop") // Can't store with 0-length key 343 a += storage.Get("") // Should result in '' 344 345 var b = new BigNumber(2) 346 var c = new BigNumber(16)//"0xf0",16) 347 var d = b.plus(c) 348 console.log(d) 349 return a 350 } 351 ` 352 r, err := initRuleEngine(js) 353 if err != nil { 354 t.Errorf("Couldn't create evaluator %v", err) 355 return 356 } 357 358 v, err := r.execute("testStorage", nil) 359 360 if err != nil { 361 t.Errorf("Unexpected error %v", err) 362 } 363 364 retval, err := v.ToString() 365 366 if err != nil { 367 t.Errorf("Unexpected error %v", err) 368 } 369 exp := `myvaluea,list[object Object]{"an":"object"}` 370 if retval != exp { 371 t.Errorf("Unexpected data, expected '%v', got '%v'", exp, retval) 372 } 373 fmt.Printf("Err %v", err) 374 375 } 376 377 const ExampleTxWindow = ` 378 function big(str){ 379 if(str.slice(0,2) == "0x"){ return new BigNumber(str.slice(2),16)} 380 return new BigNumber(str) 381 } 382 383 // Time window: 1 week 384 var window = 1000* 3600*24*7; 385 386 // Limit : 1 ether 387 var limit = new BigNumber("1e18"); 388 389 function isLimitOk(transaction){ 390 var value = big(transaction.value) 391 // Start of our window function 392 var windowstart = new Date().getTime() - window; 393 394 var txs = []; 395 var stored = storage.Get('txs'); 396 397 if(stored != ""){ 398 txs = JSON.parse(stored) 399 } 400 // First, remove all that have passed out of the time-window 401 var newtxs = txs.filter(function(tx){return tx.tstamp > windowstart}); 402 console.log(txs, newtxs.length); 403 404 // Secondly, aggregate the current sum 405 sum = new BigNumber(0) 406 407 sum = newtxs.reduce(function(agg, tx){ return big(tx.value).plus(agg)}, sum); 408 console.log("ApproveTx > Sum so far", sum); 409 console.log("ApproveTx > Requested", value.toNumber()); 410 411 // Would we exceed weekly limit ? 412 return sum.plus(value).lt(limit) 413 414 } 415 function ApproveTx(r){ 416 console.log(r) 417 console.log(typeof(r)) 418 if (isLimitOk(r.transaction)){ 419 return "Approve" 420 } 421 return "Nope" 422 } 423 424 /** 425 * OnApprovedTx(str) is called when a transaction has been approved and signed. The parameter 426 * 'response_str' contains the return value that will be sent to the external caller. 427 * The return value from this method is ignore - the reason for having this callback is to allow the 428 * ruleset to keep track of approved transactions. 429 * 430 * When implementing rate-limited rules, this callback should be used. 431 * If a rule responds with neither 'Approve' nor 'Reject' - the tx goes to manual processing. If the user 432 * then accepts the transaction, this method will be called. 433 * 434 * TLDR; Use this method to keep track of signed transactions, instead of using the data in ApproveTx. 435 */ 436 function OnApprovedTx(resp){ 437 var value = big(resp.tx.value) 438 var txs = [] 439 // Load stored transactions 440 var stored = storage.Get('txs'); 441 if(stored != ""){ 442 txs = JSON.parse(stored) 443 } 444 // Add this to the storage 445 txs.push({tstamp: new Date().getTime(), value: value}); 446 storage.Put("txs", JSON.stringify(txs)); 447 } 448 449 ` 450 451 func dummyTx(value hexutil.Big) *core.SignTxRequest { 452 453 to, _ := mixAddr("000000000000000000000000000000000000dead") 454 from, _ := mixAddr("000000000000000000000000000000000000dead") 455 n := hexutil.Uint64(3) 456 gas := hexutil.Uint64(21000) 457 gasPrice := hexutil.Big(*big.NewInt(2000000)) 458 459 return &core.SignTxRequest{ 460 Transaction: core.SendTxArgs{ 461 From: *from, 462 To: to, 463 Value: value, 464 Nonce: n, 465 GasPrice: gasPrice, 466 Gas: gas, 467 }, 468 Callinfo: []core.ValidationInfo{ 469 {Typ: "Warning", Message: "All your base are bellong to us"}, 470 }, 471 Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"}, 472 } 473 } 474 func dummyTxWithV(value uint64) *core.SignTxRequest { 475 476 v := big.NewInt(0).SetUint64(value) 477 h := hexutil.Big(*v) 478 return dummyTx(h) 479 } 480 func dummySigned(value *big.Int) *types.Transaction { 481 to := common.HexToAddress("000000000000000000000000000000000000dead") 482 gas := uint64(21000) 483 gasPrice := big.NewInt(2000000) 484 data := make([]byte, 0) 485 return types.NewTransaction(3, to, value, gas, gasPrice, data) 486 487 } 488 func TestLimitWindow(t *testing.T) { 489 490 r, err := initRuleEngine(ExampleTxWindow) 491 if err != nil { 492 t.Errorf("Couldn't create evaluator %v", err) 493 return 494 } 495 496 // 0.3 ether: 429D069189E0000 wei 497 v := big.NewInt(0).SetBytes(common.Hex2Bytes("0429D069189E0000")) 498 h := hexutil.Big(*v) 499 // The first three should succeed 500 for i := 0; i < 3; i++ { 501 unsigned := dummyTx(h) 502 resp, err := r.ApproveTx(unsigned) 503 if err != nil { 504 t.Errorf("Unexpected error %v", err) 505 } 506 if !resp.Approved { 507 t.Errorf("Expected check to resolve to 'Approve'") 508 } 509 // Create a dummy signed transaction 510 511 response := ethapi.SignTransactionResult{ 512 Tx: dummySigned(v), 513 Raw: common.Hex2Bytes("deadbeef"), 514 } 515 r.OnApprovedTx(response) 516 } 517 // Fourth should fail 518 resp, _ := r.ApproveTx(dummyTx(h)) 519 if resp.Approved { 520 t.Errorf("Expected check to resolve to 'Reject'") 521 } 522 523 } 524 525 // dontCallMe is used as a next-handler that does not want to be called - it invokes test failure 526 type dontCallMe struct { 527 t *testing.T 528 } 529 530 func (d *dontCallMe) OnInputRequired(info core.UserInputRequest) (core.UserInputResponse, error) { 531 d.t.Fatalf("Did not expect next-handler to be called") 532 return core.UserInputResponse{}, nil 533 } 534 535 func (d *dontCallMe) OnSignerStartup(info core.StartupInfo) { 536 } 537 538 func (d *dontCallMe) OnMasterPassword(request *core.PasswordRequest) (core.PasswordResponse, error) { 539 return core.PasswordResponse{}, nil 540 } 541 542 func (d *dontCallMe) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { 543 d.t.Fatalf("Did not expect next-handler to be called") 544 return core.SignTxResponse{}, core.ErrRequestDenied 545 } 546 547 func (d *dontCallMe) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) { 548 d.t.Fatalf("Did not expect next-handler to be called") 549 return core.SignDataResponse{}, core.ErrRequestDenied 550 } 551 552 func (d *dontCallMe) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) { 553 d.t.Fatalf("Did not expect next-handler to be called") 554 return core.ExportResponse{}, core.ErrRequestDenied 555 } 556 557 func (d *dontCallMe) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) { 558 d.t.Fatalf("Did not expect next-handler to be called") 559 return core.ImportResponse{}, core.ErrRequestDenied 560 } 561 562 func (d *dontCallMe) ApproveListing(request *core.ListRequest) (core.ListResponse, error) { 563 d.t.Fatalf("Did not expect next-handler to be called") 564 return core.ListResponse{}, core.ErrRequestDenied 565 } 566 567 func (d *dontCallMe) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) { 568 d.t.Fatalf("Did not expect next-handler to be called") 569 return core.NewAccountResponse{}, core.ErrRequestDenied 570 } 571 572 func (d *dontCallMe) ShowError(message string) { 573 d.t.Fatalf("Did not expect next-handler to be called") 574 } 575 576 func (d *dontCallMe) ShowInfo(message string) { 577 d.t.Fatalf("Did not expect next-handler to be called") 578 } 579 580 func (d *dontCallMe) OnApprovedTx(tx ethapi.SignTransactionResult) { 581 d.t.Fatalf("Did not expect next-handler to be called") 582 } 583 584 //TestContextIsCleared tests that the rule-engine does not retain variables over several requests. 585 // if it does, that would be bad since developers may rely on that to store data, 586 // instead of using the disk-based data storage 587 func TestContextIsCleared(t *testing.T) { 588 589 js := ` 590 function ApproveTx(){ 591 if (typeof foobar == 'undefined') { 592 foobar = "Approve" 593 } 594 console.log(foobar) 595 if (foobar == "Approve"){ 596 foobar = "Reject" 597 }else{ 598 foobar = "Approve" 599 } 600 return foobar 601 } 602 ` 603 ui := &dontCallMe{t} 604 r, err := NewRuleEvaluator(ui, storage.NewEphemeralStorage(), storage.NewEphemeralStorage()) 605 if err != nil { 606 t.Fatalf("Failed to create js engine: %v", err) 607 } 608 if err = r.Init(js); err != nil { 609 t.Fatalf("Failed to load bootstrap js: %v", err) 610 } 611 tx := dummyTxWithV(0) 612 r1, _ := r.ApproveTx(tx) 613 r2, _ := r.ApproveTx(tx) 614 if r1.Approved != r2.Approved { 615 t.Errorf("Expected execution context to be cleared between executions") 616 } 617 } 618 619 func TestSignData(t *testing.T) { 620 621 js := `function ApproveListing(){ 622 return "Approve" 623 } 624 function ApproveSignData(r){ 625 if( r.address.toLowerCase() == "0x694267f14675d7e1b9494fd8d72fefe1755710fa") 626 { 627 if(r.message.indexOf("bazonk") >= 0){ 628 return "Approve" 629 } 630 return "Reject" 631 } 632 // Otherwise goes to manual processing 633 }` 634 r, err := initRuleEngine(js) 635 if err != nil { 636 t.Errorf("Couldn't create evaluator %v", err) 637 return 638 } 639 message := []byte("baz bazonk foo") 640 hash, msg := core.SignHash(message) 641 raw := hexutil.Bytes(message) 642 addr, _ := mixAddr("0x694267f14675d7e1b9494fd8d72fefe1755710fa") 643 644 fmt.Printf("address %v %v\n", addr.String(), addr.Original()) 645 resp, err := r.ApproveSignData(&core.SignDataRequest{ 646 Address: *addr, 647 Message: msg, 648 Hash: hash, 649 Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"}, 650 Rawdata: raw, 651 }) 652 if err != nil { 653 t.Fatalf("Unexpected error %v", err) 654 } 655 if !resp.Approved { 656 t.Fatalf("Expected approved") 657 } 658 }