1// Copyright 2018 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
17package core_test
18
19import (
20	"bytes"
21	"context"
22	"fmt"
23	"io/ioutil"
24	"math/big"
25	"os"
26	"path/filepath"
27	"testing"
28	"time"
29
30	"github.com/ethereum/go-ethereum/accounts"
31	"github.com/ethereum/go-ethereum/accounts/keystore"
32	"github.com/ethereum/go-ethereum/common"
33	"github.com/ethereum/go-ethereum/common/hexutil"
34	"github.com/ethereum/go-ethereum/core/types"
35	"github.com/ethereum/go-ethereum/internal/ethapi"
36	"github.com/ethereum/go-ethereum/rlp"
37	"github.com/ethereum/go-ethereum/signer/core"
38	"github.com/ethereum/go-ethereum/signer/core/apitypes"
39	"github.com/ethereum/go-ethereum/signer/fourbyte"
40	"github.com/ethereum/go-ethereum/signer/storage"
41)
42
43//Used for testing
44type headlessUi struct {
45	approveCh chan string // to send approve/deny
46	inputCh   chan string // to send password
47}
48
49func (ui *headlessUi) OnInputRequired(info core.UserInputRequest) (core.UserInputResponse, error) {
50	input := <-ui.inputCh
51	return core.UserInputResponse{Text: input}, nil
52}
53
54func (ui *headlessUi) OnSignerStartup(info core.StartupInfo)        {}
55func (ui *headlessUi) RegisterUIServer(api *core.UIServerAPI)       {}
56func (ui *headlessUi) OnApprovedTx(tx ethapi.SignTransactionResult) {}
57
58func (ui *headlessUi) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
59
60	switch <-ui.approveCh {
61	case "Y":
62		return core.SignTxResponse{request.Transaction, true}, nil
63	case "M": // modify
64		// The headless UI always modifies the transaction
65		old := big.Int(request.Transaction.Value)
66		newVal := big.NewInt(0).Add(&old, big.NewInt(1))
67		request.Transaction.Value = hexutil.Big(*newVal)
68		return core.SignTxResponse{request.Transaction, true}, nil
69	default:
70		return core.SignTxResponse{request.Transaction, false}, nil
71	}
72}
73
74func (ui *headlessUi) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
75	approved := (<-ui.approveCh == "Y")
76	return core.SignDataResponse{approved}, nil
77}
78
79func (ui *headlessUi) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
80	approval := <-ui.approveCh
81	//fmt.Printf("approval %s\n", approval)
82	switch approval {
83	case "A":
84		return core.ListResponse{request.Accounts}, nil
85	case "1":
86		l := make([]accounts.Account, 1)
87		l[0] = request.Accounts[1]
88		return core.ListResponse{l}, nil
89	default:
90		return core.ListResponse{nil}, nil
91	}
92}
93
94func (ui *headlessUi) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
95	if <-ui.approveCh == "Y" {
96		return core.NewAccountResponse{true}, nil
97	}
98	return core.NewAccountResponse{false}, nil
99}
100
101func (ui *headlessUi) ShowError(message string) {
102	//stdout is used by communication
103	fmt.Fprintln(os.Stderr, message)
104}
105
106func (ui *headlessUi) ShowInfo(message string) {
107	//stdout is used by communication
108	fmt.Fprintln(os.Stderr, message)
109}
110
111func tmpDirName(t *testing.T) string {
112	d, err := ioutil.TempDir("", "eth-keystore-test")
113	if err != nil {
114		t.Fatal(err)
115	}
116	d, err = filepath.EvalSymlinks(d)
117	if err != nil {
118		t.Fatal(err)
119	}
120	return d
121}
122
123func setup(t *testing.T) (*core.SignerAPI, *headlessUi) {
124	db, err := fourbyte.New()
125	if err != nil {
126		t.Fatal(err.Error())
127	}
128	ui := &headlessUi{make(chan string, 20), make(chan string, 20)}
129	am := core.StartClefAccountManager(tmpDirName(t), true, true, "")
130	api := core.NewSignerAPI(am, 1337, true, ui, db, true, &storage.NoStorage{})
131	return api, ui
132
133}
134func createAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) {
135	ui.approveCh <- "Y"
136	ui.inputCh <- "a_long_password"
137	_, err := api.New(context.Background())
138	if err != nil {
139		t.Fatal(err)
140	}
141	// Some time to allow changes to propagate
142	time.Sleep(250 * time.Millisecond)
143}
144
145func failCreateAccountWithPassword(ui *headlessUi, api *core.SignerAPI, password string, t *testing.T) {
146
147	ui.approveCh <- "Y"
148	// We will be asked three times to provide a suitable password
149	ui.inputCh <- password
150	ui.inputCh <- password
151	ui.inputCh <- password
152
153	addr, err := api.New(context.Background())
154	if err == nil {
155		t.Fatal("Should have returned an error")
156	}
157	if addr != (common.Address{}) {
158		t.Fatal("Empty address should be returned")
159	}
160}
161
162func failCreateAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) {
163	ui.approveCh <- "N"
164	addr, err := api.New(context.Background())
165	if err != core.ErrRequestDenied {
166		t.Fatal(err)
167	}
168	if addr != (common.Address{}) {
169		t.Fatal("Empty address should be returned")
170	}
171}
172
173func list(ui *headlessUi, api *core.SignerAPI, t *testing.T) ([]common.Address, error) {
174	ui.approveCh <- "A"
175	return api.List(context.Background())
176
177}
178
179func TestNewAcc(t *testing.T) {
180	api, control := setup(t)
181	verifyNum := func(num int) {
182		list, err := list(control, api, t)
183		if err != nil {
184			t.Errorf("Unexpected error %v", err)
185		}
186		if len(list) != num {
187			t.Errorf("Expected %d accounts, got %d", num, len(list))
188		}
189	}
190	// Testing create and create-deny
191	createAccount(control, api, t)
192	createAccount(control, api, t)
193	failCreateAccount(control, api, t)
194	failCreateAccount(control, api, t)
195	createAccount(control, api, t)
196	failCreateAccount(control, api, t)
197	createAccount(control, api, t)
198	failCreateAccount(control, api, t)
199	verifyNum(4)
200
201	// Fail to create this, due to bad password
202	failCreateAccountWithPassword(control, api, "short", t)
203	failCreateAccountWithPassword(control, api, "longerbutbad\rfoo", t)
204	verifyNum(4)
205
206	// Testing listing:
207	// Listing one Account
208	control.approveCh <- "1"
209	list, err := api.List(context.Background())
210	if err != nil {
211		t.Fatal(err)
212	}
213	if len(list) != 1 {
214		t.Fatalf("List should only show one Account")
215	}
216	// Listing denied
217	control.approveCh <- "Nope"
218	list, err = api.List(context.Background())
219	if len(list) != 0 {
220		t.Fatalf("List should be empty")
221	}
222	if err != core.ErrRequestDenied {
223		t.Fatal("Expected deny")
224	}
225}
226
227func mkTestTx(from common.MixedcaseAddress) apitypes.SendTxArgs {
228	to := common.NewMixedcaseAddress(common.HexToAddress("0x1337"))
229	gas := hexutil.Uint64(21000)
230	gasPrice := (hexutil.Big)(*big.NewInt(2000000000))
231	value := (hexutil.Big)(*big.NewInt(1e18))
232	nonce := (hexutil.Uint64)(0)
233	data := hexutil.Bytes(common.Hex2Bytes("01020304050607080a"))
234	tx := apitypes.SendTxArgs{
235		From:     from,
236		To:       &to,
237		Gas:      gas,
238		GasPrice: &gasPrice,
239		Value:    value,
240		Data:     &data,
241		Nonce:    nonce}
242	return tx
243}
244
245func TestSignTx(t *testing.T) {
246	var (
247		list      []common.Address
248		res, res2 *ethapi.SignTransactionResult
249		err       error
250	)
251
252	api, control := setup(t)
253	createAccount(control, api, t)
254	control.approveCh <- "A"
255	list, err = api.List(context.Background())
256	if err != nil {
257		t.Fatal(err)
258	}
259	a := common.NewMixedcaseAddress(list[0])
260
261	methodSig := "test(uint)"
262	tx := mkTestTx(a)
263
264	control.approveCh <- "Y"
265	control.inputCh <- "wrongpassword"
266	res, err = api.SignTransaction(context.Background(), tx, &methodSig)
267	if res != nil {
268		t.Errorf("Expected nil-response, got %v", res)
269	}
270	if err != keystore.ErrDecrypt {
271		t.Errorf("Expected ErrLocked! %v", err)
272	}
273	control.approveCh <- "No way"
274	res, err = api.SignTransaction(context.Background(), tx, &methodSig)
275	if res != nil {
276		t.Errorf("Expected nil-response, got %v", res)
277	}
278	if err != core.ErrRequestDenied {
279		t.Errorf("Expected ErrRequestDenied! %v", err)
280	}
281	// Sign with correct password
282	control.approveCh <- "Y"
283	control.inputCh <- "a_long_password"
284	res, err = api.SignTransaction(context.Background(), tx, &methodSig)
285
286	if err != nil {
287		t.Fatal(err)
288	}
289	parsedTx := &types.Transaction{}
290	rlp.Decode(bytes.NewReader(res.Raw), parsedTx)
291
292	//The tx should NOT be modified by the UI
293	if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 {
294		t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value())
295	}
296	control.approveCh <- "Y"
297	control.inputCh <- "a_long_password"
298
299	res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
300	if err != nil {
301		t.Fatal(err)
302	}
303	if !bytes.Equal(res.Raw, res2.Raw) {
304		t.Error("Expected tx to be unmodified by UI")
305	}
306
307	//The tx is modified by the UI
308	control.approveCh <- "M"
309	control.inputCh <- "a_long_password"
310
311	res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
312	if err != nil {
313		t.Fatal(err)
314	}
315	parsedTx2 := &types.Transaction{}
316	rlp.Decode(bytes.NewReader(res.Raw), parsedTx2)
317
318	//The tx should be modified by the UI
319	if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 {
320		t.Errorf("Expected value to be unchanged, got %v", parsedTx.Value())
321	}
322	if bytes.Equal(res.Raw, res2.Raw) {
323		t.Error("Expected tx to be modified by UI")
324	}
325
326}
327