fix: create accounts
[electrum-nvc.git] / electrum
1 #!/usr/bin/env python
2 #
3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2011 thomasv@gitorious
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 from decimal import Decimal
20 import json
21 import optparse
22 import os
23 import re
24 import ast
25 import sys
26 import time
27 import traceback
28
29 try:
30     import ecdsa  # todo: 'ecdsa' imported but unused
31 except ImportError:
32     sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
33
34 try:
35     import aes  # todo: 'aes' imported but unused
36 except ImportError:
37     sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
38
39
40 is_local = os.path.dirname(os.path.realpath(__file__)) == os.getcwd()
41 is_android = 'ANDROID_DATA' in os.environ
42
43 import __builtin__
44 __builtin__.use_local_modules = is_local or is_android
45
46 # load local module as electrum
47 if __builtin__.use_local_modules:
48     import imp
49     imp.load_module('electrum', *imp.find_module('lib'))
50     imp.load_module('electrum_gui', *imp.find_module('gui'))
51
52 from electrum import *  # todo: import * is generally frowned upon. should import just what is used
53
54
55 # get password routine
56 def prompt_password(prompt, confirm=True):
57     import getpass
58     if sys.stdin.isatty():
59         password = getpass.getpass(prompt)
60         if password and confirm:
61             password2 = getpass.getpass("Confirm: ")
62             if password != password2:
63                 sys.exit("Error: Passwords do not match.")
64     else:
65         password = raw_input(prompt)
66     if not password:
67         password = None
68     return password
69
70
71 def arg_parser():
72     usage = "%prog [options] command"
73     parser = optparse.OptionParser(prog=usage, add_help_option=False)
74     parser.add_option("-h", "--help", action="callback", callback=print_help_cb, help="show this help text")
75     parser.add_option("-g", "--gui", dest="gui", help="User interface: qt, lite, gtk, text or stdio")
76     parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
77     parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
78     parser.add_option("-C", "--concealed", action="store_true", dest="concealed", default=False, help="don't echo seed to console when restoring")
79     parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
80     parser.add_option("-l", "--labels", action="store_true", dest="show_labels", default=False, help="show the labels of listed addresses")
81     parser.add_option("-f", "--fee", dest="tx_fee", default=None, help="set tx fee")
82     parser.add_option("-F", "--fromaddr", dest="from_addr", default=None, help="set source address for payto/mktx. if it isn't in the wallet, it will ask for the private key unless supplied in the format public_key:private_key. It's not saved in the wallet.")
83     parser.add_option("-c", "--changeaddr", dest="change_addr", default=None, help="set the change address for payto/mktx. default is a spare address, or the source address if it's not in the wallet")
84     parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is either t (tcp), h (http), s (tcp+ssl), or g (https)")
85     parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
86     parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="show debugging information")
87     parser.add_option("-P", "--portable", action="store_true", dest="portable", default=False, help="portable wallet")
88     parser.add_option("-L", "--lang", dest="language", default=None, help="defaut language used in GUI")
89     parser.add_option("-u", "--usb", dest="bitkey", action="store_true", help="Turn on support for hardware wallets (EXPERIMENTAL)")
90     parser.add_option("-G", "--gap", dest="gap_limit", default=None, help="gap limit")
91     parser.add_option("-W", "--password", dest="password", default=None, help="set password for usage with commands (currently only implemented for create command, do not use it for longrunning gui session since the password is visible in /proc)")
92     parser.add_option("-1", "--oneserver", action="store_true", dest="oneserver", default=False, help="connect to one server only")
93     parser.add_option("--bip32", action="store_true", dest="bip32", default=False, help="bip32 (not final)")
94     parser.add_option("--2of3", action="store_true", dest="2of3", default=False, help="create 2of3 wallet")
95     parser.add_option("--mpk", dest="mpk", default=False, help="restore from master public key")
96     return parser
97
98
99 def print_help(parser):
100     parser.print_help()
101     print_msg("Type 'electrum help <command>' to see the help for a specific command")
102     print_msg("Type 'electrum --help' to see the list of options")
103     run_command(known_commands['help'])
104     sys.exit(1)
105
106
107 def print_help_cb(self, opt, value, parser):
108     print_help(parser)
109
110
111 def run_command(cmd, password=None, args=[]):
112     import socket
113     if cmd.requires_network and not options.offline:
114         network = NetworkProxy(config)
115         if not network.start(start_daemon= (True if cmd.name!='daemon' else False)):
116             print "Daemon not running"
117             sys.exit(1)
118
119
120
121         if wallet:
122             wallet.start_threads(network)
123             wallet.update()
124     else:
125         network = None
126
127     cmd_runner = Commands(wallet, network)
128     func = getattr(cmd_runner, cmd.name)
129     cmd_runner.password = password
130     try:
131         result = func(*args[1:])
132     except Exception:
133         traceback.print_exc(file=sys.stdout)
134         sys.exit(1)
135
136
137     if cmd.requires_network and not options.offline:
138         if wallet:
139             wallet.stop_threads()
140
141
142     if type(result) == str:
143         util.print_msg(result)
144     elif result is not None:
145         util.print_json(result)
146
147
148
149
150
151
152 if __name__ == '__main__':
153
154     parser = arg_parser()
155     options, args = parser.parse_args()
156     if options.portable and options.wallet_path is None:
157         options.electrum_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'electrum_data')
158
159     # config is an object passed to the various constructors (wallet, interface, gui)
160     if is_android:
161         config_options = {
162             'portable': True,
163             'verbose': True,
164             'gui': 'android',
165             'auto_cycle': True,
166         }
167     else:
168         config_options = eval(str(options))
169         for k, v in config_options.items():
170             if v is None:
171                 config_options.pop(k)
172
173     set_verbosity(config_options.get('verbose'))
174
175     config = SimpleConfig(config_options)
176
177     if len(args) == 0:
178         url = None
179         cmd = 'gui'
180     elif len(args) == 1 and re.match('^bitcoin:', args[0]):
181         url = args[0]
182         cmd = 'gui'
183     else:
184         cmd = args[0]
185
186     if cmd == 'gui':
187         gui_name = config.get('gui', 'classic')
188         if gui_name in ['lite', 'classic']:
189             gui_name = 'qt'
190         try:
191             gui = __import__('electrum_gui.' + gui_name, fromlist=['electrum_gui'])
192         except ImportError:
193             traceback.print_exc(file=sys.stdout)
194             sys.exit()
195             #sys.exit("Error: Unknown GUI: " + gui_name )
196
197         # network interface
198         if not options.offline:
199             network = Network(config)
200             network.start()
201         else:
202             network = None
203
204         gui = gui.ElectrumGui(config, network)
205         gui.main(url)
206
207         if network:
208             network.stop()
209
210         # we use daemon threads, their termination is enforced.
211         # this sleep command gives them time to terminate cleanly.
212         time.sleep(0.1)
213         sys.exit(0)
214
215     if cmd not in known_commands:
216         cmd = 'help'
217
218     cmd = known_commands[cmd]
219
220     # instanciate wallet for command-line
221     storage = WalletStorage(config)
222
223
224     if cmd.name in ['create', 'restore']:
225         if storage.file_exists:
226             sys.exit("Error: Remove the existing wallet first!")
227         if options.password is not None:
228             password = options.password
229         elif cmd.name == 'restore' and options.mpk:
230             password = None
231         else:
232             password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
233
234         # if config.server is set, the user either passed the server on command line
235         # or chose it previously already. if he didn't pass a server on the command line,
236         # we just pick up a random one.
237         if not config.get('server'):
238             config.set_key('server', pick_random_server())
239
240         #fee = options.tx_fee if options.tx_fee else raw_input("fee (default:%s):" % (str(Decimal(wallet.fee)/100000000)))
241         #gap = options.gap_limit if options.gap_limit else raw_input("gap limit (default 5):")
242         #if fee:
243         #    wallet.set_fee(float(fee)*100000000)
244         #if gap:
245         #    wallet.change_gap_limit(int(gap))
246
247         if cmd.name == 'restore':
248             if options.mpk:
249                 wallet = Wallet.from_mpk(options.mpk, storage)
250             else:
251                 import getpass
252                 seed = getpass.getpass(prompt="seed:", stream=None) if options.concealed else raw_input("seed:")
253                 wallet = Wallet.from_seed(str(seed),storage)
254                 if not wallet:
255                     sys.exit("Error: Invalid seed")
256                 wallet.save_seed(password)
257                 wallet.create_accounts(password)
258
259             if not options.offline:
260                 network = Network(config)
261                 network.start()
262                 wallet.start_threads(network)
263                 print_msg("Recovering wallet...")
264                 wallet.restore(lambda x: x)
265                 if wallet.is_found():
266                     print_msg("Recovery successful")
267                 else:
268                     print_msg("Warning: Found no history for this wallet")
269             else:
270                 wallet.synchronize()
271                 print_msg("Warning: This wallet was restored offline. It may contain more addresses than displayed.")
272
273         else:
274             if not config.get('2of3'):
275                 wallet = Wallet(storage)
276                 wallet.init_seed(None)
277                 wallet.save_seed(password)
278                 wallet.synchronize()
279                 print_msg("Your wallet generation seed is:\n\"%s\"" % wallet.get_mnemonic(password))
280                 print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
281             else:
282                 wallet = Wallet_2of3(storage)
283                 cold_seed = wallet.init_cold_seed()
284                 wallet.save_cold_seed()
285                 print_msg("Your cold seed is:\n\"%s\"" % cold_seed)
286                 print_msg("Please store it on paper. ")
287                 print_msg("Open this file on your online computer to complete your wallet creation.")
288
289
290         print_msg("Wallet saved in '%s'" % wallet.storage.path)
291
292         # terminate
293         sys.exit(0)
294
295
296     if cmd.name not in ['create', 'restore'] and cmd.requires_wallet and not storage.file_exists:
297         print_msg("Error: Wallet file not found.")
298         print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
299         sys.exit(0)
300
301
302     if cmd.requires_wallet:
303         wallet = Wallet(storage)
304     else:
305         wallet = None
306
307
308     # important warning
309     if cmd.name in ['dumpprivkey', 'dumpprivkeys']:
310         print_msg("WARNING: ALL your private keys are secret.")
311         print_msg("Exposing a single private key can compromise your entire wallet!")
312         print_msg("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
313
314     # commands needing password
315     if cmd.requires_password:
316         if wallet.seed == '':
317             seed = ''
318             password = None
319         elif wallet.use_encryption:
320             password = prompt_password('Password:', False)
321             if not password:
322                 print_msg("Error: Password required")
323                 sys.exit(1)
324             # check password
325             try:
326                 seed = wallet.get_seed(password)
327             except Exception:
328                 print_msg("Error: This password does not decode this wallet.")
329                 sys.exit(1)
330         else:
331             password = None
332             seed = wallet.get_seed(None)
333     else:
334         password = None
335
336     # add missing arguments, do type conversions
337     if cmd.name == 'importprivkey':
338         # See if they specificed a key on the cmd line, if not prompt
339         if len(args) == 1:
340             args[1] = prompt_password('Enter PrivateKey (will not echo):', False)
341
342     elif cmd.name == 'signrawtransaction':
343         args = [cmd, args[1], json.loads(args[2]) if len(args) > 2 else [], json.loads(args[3]) if len(args) > 3 else []]
344
345     elif cmd.name == 'createmultisig':
346         args = [cmd, int(args[1]), json.loads(args[2])]
347
348     elif cmd.name == 'createrawtransaction':
349         args = [cmd, json.loads(args[1]), json.loads(args[2])]
350
351     elif cmd.name == 'listaddresses':
352         args = [cmd, options.show_all, options.show_labels]
353
354     elif cmd.name in ['payto', 'mktx']:
355         domain = [options.from_addr] if options.from_addr else None
356         args = ['mktx', args[1], Decimal(args[2]), Decimal(options.tx_fee) if options.tx_fee else None, options.change_addr, domain]
357
358     elif cmd.name in ['paytomany', 'mksendmanytx']:
359         domain = [options.from_addr] if options.from_addr else None
360         outputs = []
361         for i in range(1, len(args), 2):
362             if len(args) < i+2:
363                 print_msg("Error: Mismatched arguments.")
364                 sys.exit(1)
365             outputs.append((args[i], Decimal(args[i+1])))
366         args = ['mksendmanytx', outputs, Decimal(options.tx_fee) if options.tx_fee else None, options.change_addr, domain]
367
368     elif cmd.name == 'help':
369         if len(args) < 2:
370             print_help(parser)
371
372     # check the number of arguments
373     if len(args) - 1 < cmd.min_args:
374         print_msg("Not enough arguments")
375         print_msg("Syntax:", cmd.syntax)
376         sys.exit(1)
377
378     if cmd.max_args >= 0 and len(args) - 1 > cmd.max_args:
379         print_msg("too many arguments", args)
380         print_msg("Syntax:", cmd.syntax)
381         sys.exit(1)
382
383     if cmd.max_args < 0:
384         if len(args) > cmd.min_args + 1:
385             message = ' '.join(args[cmd.min_args:])
386             print_msg("Warning: Final argument was reconstructed from several arguments:", repr(message))
387             args = args[0:cmd.min_args] + [message]
388
389
390
391     # run the command
392     if cmd.name == 'deseed':
393         if not wallet.seed:
394             print_msg("Error: This wallet has no seed")
395         else:
396             ns = wallet.storage.path + '.seedless'
397             print_msg("Warning: you are going to create a seedless wallet'\nIt will be saved in '%s'" % ns)
398             if raw_input("Are you sure you want to continue? (y/n) ") in ['y', 'Y', 'yes']:
399                 wallet.storage.path = ns
400                 wallet.seed = ''
401                 wallet.storage.put('seed', '', True)
402                 wallet.use_encryption = False
403                 wallet.storage.put('use_encryption', wallet.use_encryption, True)
404                 for k in wallet.imported_keys.keys():
405                     wallet.imported_keys[k] = ''
406                 wallet.storage.put('imported_keys', wallet.imported_keys, True)
407                 print_msg("Done.")
408             else:
409                 print_msg("Action canceled.")
410
411     elif cmd.name == 'getconfig':
412         key = args[1]
413         out = config.get(key)
414         print_msg(out)
415
416     elif cmd.name == 'setconfig':
417         key, value = args[1:3]
418         try:
419             value = ast.literal_eval(value)
420         except:
421             pass
422         config.set_key(key, value, True)
423         print_msg(True)
424
425     elif cmd.name == 'password':
426         new_password = prompt_password('New password:')
427         wallet.update_password(password, new_password)
428
429     else:
430         run_command(cmd, password, args)
431
432
433     time.sleep(0.1)
434     sys.exit(0)