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