update bip32 accounts and wallet
[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("--mpk", dest="mpk", default=False, help="restore from master public key")
95     return parser
96
97
98 def print_help(parser):
99     parser.print_help()
100     print_msg("Type 'electrum help <command>' to see the help for a specific command")
101     print_msg("Type 'electrum --help' to see the list of options")
102     run_command(known_commands['help'])
103     sys.exit(1)
104
105
106 def print_help_cb(self, opt, value, parser):
107     print_help(parser)
108
109
110 def run_command(cmd, password=None, args=[]):
111     import socket
112     if cmd.requires_network and not options.offline:
113         network = NetworkProxy(config)
114         if not network.start(start_daemon= (True if cmd.name!='daemon' else False)):
115             print "Daemon not running"
116             sys.exit(1)
117
118
119
120         if wallet:
121             wallet.start_threads(network)
122             wallet.update()
123     else:
124         network = None
125
126     cmd_runner = Commands(wallet, network)
127     func = getattr(cmd_runner, cmd.name)
128     cmd_runner.password = password
129     try:
130         result = func(*args[1:])
131     except Exception:
132         traceback.print_exc(file=sys.stdout)
133         sys.exit(1)
134
135
136     if cmd.requires_network and not options.offline:
137         if wallet:
138             wallet.stop_threads()
139
140
141     if type(result) == str:
142         util.print_msg(result)
143     elif result is not None:
144         util.print_json(result)
145
146
147
148
149
150
151 if __name__ == '__main__':
152
153     parser = arg_parser()
154     options, args = parser.parse_args()
155     if options.portable and options.wallet_path is None:
156         options.electrum_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'electrum_data')
157
158     # config is an object passed to the various constructors (wallet, interface, gui)
159     if is_android:
160         config_options = {
161             'portable': True,
162             'verbose': True,
163             'gui': 'android',
164             'auto_cycle': True,
165         }
166     else:
167         config_options = eval(str(options))
168         for k, v in config_options.items():
169             if v is None:
170                 config_options.pop(k)
171
172     set_verbosity(config_options.get('verbose'))
173
174     config = SimpleConfig(config_options)
175
176     if len(args) == 0:
177         url = None
178         cmd = 'gui'
179     elif len(args) == 1 and re.match('^bitcoin:', args[0]):
180         url = args[0]
181         cmd = 'gui'
182     else:
183         cmd = args[0]
184
185     if cmd == 'gui':
186         gui_name = config.get('gui', 'classic')
187         if gui_name in ['lite', 'classic']:
188             gui_name = 'qt'
189         try:
190             gui = __import__('electrum_gui.' + gui_name, fromlist=['electrum_gui'])
191         except ImportError:
192             traceback.print_exc(file=sys.stdout)
193             sys.exit()
194             #sys.exit("Error: Unknown GUI: " + gui_name )
195
196         # network interface
197         if not options.offline:
198             network = Network(config)
199             network.start()
200         else:
201             network = None
202
203         gui = gui.ElectrumGui(config, network)
204         gui.main(url)
205
206         if network:
207             network.stop()
208
209         # we use daemon threads, their termination is enforced.
210         # this sleep command gives them time to terminate cleanly.
211         time.sleep(0.1)
212         sys.exit(0)
213
214     if cmd not in known_commands:
215         cmd = 'help'
216
217     cmd = known_commands[cmd]
218
219     # instanciate wallet for command-line
220     storage = WalletStorage(config)
221
222
223     if cmd.name in ['create', 'restore']:
224         if storage.file_exists:
225             sys.exit("Error: Remove the existing wallet first!")
226         if options.password is not None:
227             password = options.password
228         elif cmd.name == 'restore' and options.mpk:
229             password = None
230         else:
231             password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
232
233         # if config.server is set, the user either passed the server on command line
234         # or chose it previously already. if he didn't pass a server on the command line,
235         # we just pick up a random one.
236         if not config.get('server'):
237             config.set_key('server', pick_random_server())
238
239         #fee = options.tx_fee if options.tx_fee else raw_input("fee (default:%s):" % (str(Decimal(wallet.fee)/100000000)))
240         #gap = options.gap_limit if options.gap_limit else raw_input("gap limit (default 5):")
241         #if fee:
242         #    wallet.set_fee(float(fee)*100000000)
243         #if gap:
244         #    wallet.change_gap_limit(int(gap))
245
246         if cmd.name == 'restore':
247             if options.mpk:
248                 wallet = Wallet.from_mpk(options.mpk, storage)
249             else:
250                 import getpass
251                 seed = getpass.getpass(prompt="seed:", stream=None) if options.concealed else raw_input("seed:")
252                 wallet = Wallet.from_seed(str(seed),storage)
253                 if not wallet:
254                     sys.exit("Error: Invalid seed")
255                 wallet.save_seed(password)
256
257             if not options.offline:
258                 network = Network(config)
259                 network.start()
260                 wallet.start_threads(network)
261                 print_msg("Recovering wallet...")
262                 wallet.restore(lambda x: x)
263                 if wallet.is_found():
264                     print_msg("Recovery successful")
265                 else:
266                     print_msg("Warning: Found no history for this wallet")
267             else:
268                 wallet.synchronize()
269                 print_msg("Warning: This wallet was restored offline. It may contain more addresses than displayed.")
270
271         else:
272             wallet = Wallet(storage)
273             wallet.init_seed(None)
274             wallet.save_seed(password)
275             wallet.synchronize()
276             print_msg("Your wallet generation seed is:\n\"%s\"" % wallet.get_mnemonic(password))
277             print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
278
279         print_msg("Wallet saved in '%s'" % wallet.storage.path)
280
281         # terminate
282         sys.exit(0)
283
284
285     if cmd.name not in ['create', 'restore'] and cmd.requires_wallet and not storage.file_exists:
286         print_msg("Error: Wallet file not found.")
287         print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
288         sys.exit(0)
289
290
291     if cmd.requires_wallet:
292         wallet = Wallet(storage)
293     else:
294         wallet = None
295
296
297     # important warning
298     if cmd.name in ['dumpprivkey', 'dumpprivkeys']:
299         print_msg("WARNING: ALL your private keys are secret.")
300         print_msg("Exposing a single private key can compromise your entire wallet!")
301         print_msg("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
302
303     # commands needing password
304     if cmd.requires_password:
305         if wallet.seed == '':
306             seed = ''
307             password = None
308         elif wallet.use_encryption:
309             password = prompt_password('Password:', False)
310             if not password:
311                 print_msg("Error: Password required")
312                 sys.exit(1)
313             # check password
314             try:
315                 seed = wallet.get_seed(password)
316             except Exception:
317                 print_msg("Error: This password does not decode this wallet.")
318                 sys.exit(1)
319         else:
320             password = None
321             seed = wallet.get_seed(None)
322     else:
323         password = None
324
325     # add missing arguments, do type conversions
326     if cmd.name == 'importprivkey':
327         # See if they specificed a key on the cmd line, if not prompt
328         if len(args) == 1:
329             args[1] = prompt_password('Enter PrivateKey (will not echo):', False)
330
331     elif cmd.name == 'signrawtransaction':
332         args = [cmd, args[1], json.loads(args[2]) if len(args) > 2 else [], json.loads(args[3]) if len(args) > 3 else []]
333
334     elif cmd.name == 'createmultisig':
335         args = [cmd, int(args[1]), json.loads(args[2])]
336
337     elif cmd.name == 'createrawtransaction':
338         args = [cmd, json.loads(args[1]), json.loads(args[2])]
339
340     elif cmd.name == 'listaddresses':
341         args = [cmd, options.show_all, options.show_labels]
342
343     elif cmd.name in ['payto', 'mktx']:
344         domain = [options.from_addr] if options.from_addr else None
345         args = ['mktx', args[1], Decimal(args[2]), Decimal(options.tx_fee) if options.tx_fee else None, options.change_addr, domain]
346
347     elif cmd.name in ['paytomany', 'mksendmanytx']:
348         domain = [options.from_addr] if options.from_addr else None
349         outputs = []
350         for i in range(1, len(args), 2):
351             if len(args) < i+2:
352                 print_msg("Error: Mismatched arguments.")
353                 sys.exit(1)
354             outputs.append((args[i], Decimal(args[i+1])))
355         args = ['mksendmanytx', outputs, Decimal(options.tx_fee) if options.tx_fee else None, options.change_addr, domain]
356
357     elif cmd.name == 'help':
358         if len(args) < 2:
359             print_help(parser)
360
361     # check the number of arguments
362     if len(args) - 1 < cmd.min_args:
363         print_msg("Not enough arguments")
364         print_msg("Syntax:", cmd.syntax)
365         sys.exit(1)
366
367     if cmd.max_args >= 0 and len(args) - 1 > cmd.max_args:
368         print_msg("too many arguments", args)
369         print_msg("Syntax:", cmd.syntax)
370         sys.exit(1)
371
372     if cmd.max_args < 0:
373         if len(args) > cmd.min_args + 1:
374             message = ' '.join(args[cmd.min_args:])
375             print_msg("Warning: Final argument was reconstructed from several arguments:", repr(message))
376             args = args[0:cmd.min_args] + [message]
377
378
379
380     # run the command
381     if cmd.name == 'deseed':
382         if not wallet.seed:
383             print_msg("Error: This wallet has no seed")
384         else:
385             ns = wallet.storage.path + '.seedless'
386             print_msg("Warning: you are going to create a seedless wallet'\nIt will be saved in '%s'" % ns)
387             if raw_input("Are you sure you want to continue? (y/n) ") in ['y', 'Y', 'yes']:
388                 wallet.storage.path = ns
389                 wallet.seed = ''
390                 wallet.storage.put('seed', '', True)
391                 wallet.use_encryption = False
392                 wallet.storage.put('use_encryption', wallet.use_encryption, True)
393                 for k in wallet.imported_keys.keys():
394                     wallet.imported_keys[k] = ''
395                 wallet.storage.put('imported_keys', wallet.imported_keys, True)
396                 print_msg("Done.")
397             else:
398                 print_msg("Action canceled.")
399
400     elif cmd.name == 'getconfig':
401         key = args[1]
402         out = config.get(key)
403         print_msg(out)
404
405     elif cmd.name == 'setconfig':
406         key, value = args[1:3]
407         try:
408             value = ast.literal_eval(value)
409         except:
410             pass
411         config.set_key(key, value, True)
412         print_msg(True)
413
414     elif cmd.name == 'password':
415         new_password = prompt_password('New password:')
416         wallet.update_password(password, new_password)
417
418     else:
419         run_command(cmd, password, args)
420
421
422     time.sleep(0.1)
423     sys.exit(0)