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