save cold seed
[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
258             if not options.offline:
259                 network = Network(config)
260                 network.start()
261                 wallet.start_threads(network)
262                 print_msg("Recovering wallet...")
263                 wallet.restore(lambda x: x)
264                 if wallet.is_found():
265                     print_msg("Recovery successful")
266                 else:
267                     print_msg("Warning: Found no history for this wallet")
268             else:
269                 wallet.synchronize()
270                 print_msg("Warning: This wallet was restored offline. It may contain more addresses than displayed.")
271
272         else:
273             if not config.get('2of3'):
274                 wallet = Wallet(storage)
275                 wallet.init_seed(None)
276                 wallet.save_seed(password)
277                 wallet.synchronize()
278                 print_msg("Your wallet generation seed is:\n\"%s\"" % wallet.get_mnemonic(password))
279                 print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
280             else:
281                 wallet = Wallet_2of3(storage)
282                 cold_seed = wallet.init_cold_seed()
283                 wallet.save_cold_seed()
284                 print_msg("Your cold seed is:\n\"%s\"" % cold_seed)
285                 print_msg("Please store it on paper. ")
286                 print_msg("Open this file on your online computer to complete your wallet creation.")
287
288
289         print_msg("Wallet saved in '%s'" % wallet.storage.path)
290
291         # terminate
292         sys.exit(0)
293
294
295     if cmd.name not in ['create', 'restore'] and cmd.requires_wallet and not storage.file_exists:
296         print_msg("Error: Wallet file not found.")
297         print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
298         sys.exit(0)
299
300
301     if cmd.requires_wallet:
302         wallet = Wallet(storage)
303     else:
304         wallet = None
305
306
307     # important warning
308     if cmd.name in ['dumpprivkey', 'dumpprivkeys']:
309         print_msg("WARNING: ALL your private keys are secret.")
310         print_msg("Exposing a single private key can compromise your entire wallet!")
311         print_msg("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
312
313     # commands needing password
314     if cmd.requires_password:
315         if wallet.seed == '':
316             seed = ''
317             password = None
318         elif wallet.use_encryption:
319             password = prompt_password('Password:', False)
320             if not password:
321                 print_msg("Error: Password required")
322                 sys.exit(1)
323             # check password
324             try:
325                 seed = wallet.get_seed(password)
326             except Exception:
327                 print_msg("Error: This password does not decode this wallet.")
328                 sys.exit(1)
329         else:
330             password = None
331             seed = wallet.get_seed(None)
332     else:
333         password = None
334
335     # add missing arguments, do type conversions
336     if cmd.name == 'importprivkey':
337         # See if they specificed a key on the cmd line, if not prompt
338         if len(args) == 1:
339             args[1] = prompt_password('Enter PrivateKey (will not echo):', False)
340
341     elif cmd.name == 'signrawtransaction':
342         args = [cmd, args[1], json.loads(args[2]) if len(args) > 2 else [], json.loads(args[3]) if len(args) > 3 else []]
343
344     elif cmd.name == 'createmultisig':
345         args = [cmd, int(args[1]), json.loads(args[2])]
346
347     elif cmd.name == 'createrawtransaction':
348         args = [cmd, json.loads(args[1]), json.loads(args[2])]
349
350     elif cmd.name == 'listaddresses':
351         args = [cmd, options.show_all, options.show_labels]
352
353     elif cmd.name in ['payto', 'mktx']:
354         domain = [options.from_addr] if options.from_addr else None
355         args = ['mktx', args[1], Decimal(args[2]), Decimal(options.tx_fee) if options.tx_fee else None, options.change_addr, domain]
356
357     elif cmd.name in ['paytomany', 'mksendmanytx']:
358         domain = [options.from_addr] if options.from_addr else None
359         outputs = []
360         for i in range(1, len(args), 2):
361             if len(args) < i+2:
362                 print_msg("Error: Mismatched arguments.")
363                 sys.exit(1)
364             outputs.append((args[i], Decimal(args[i+1])))
365         args = ['mksendmanytx', outputs, Decimal(options.tx_fee) if options.tx_fee else None, options.change_addr, domain]
366
367     elif cmd.name == 'help':
368         if len(args) < 2:
369             print_help(parser)
370
371     # check the number of arguments
372     if len(args) - 1 < cmd.min_args:
373         print_msg("Not enough arguments")
374         print_msg("Syntax:", cmd.syntax)
375         sys.exit(1)
376
377     if cmd.max_args >= 0 and len(args) - 1 > cmd.max_args:
378         print_msg("too many arguments", args)
379         print_msg("Syntax:", cmd.syntax)
380         sys.exit(1)
381
382     if cmd.max_args < 0:
383         if len(args) > cmd.min_args + 1:
384             message = ' '.join(args[cmd.min_args:])
385             print_msg("Warning: Final argument was reconstructed from several arguments:", repr(message))
386             args = args[0:cmd.min_args] + [message]
387
388
389
390     # run the command
391     if cmd.name == 'deseed':
392         if not wallet.seed:
393             print_msg("Error: This wallet has no seed")
394         else:
395             ns = wallet.storage.path + '.seedless'
396             print_msg("Warning: you are going to create a seedless wallet'\nIt will be saved in '%s'" % ns)
397             if raw_input("Are you sure you want to continue? (y/n) ") in ['y', 'Y', 'yes']:
398                 wallet.storage.path = ns
399                 wallet.seed = ''
400                 wallet.storage.put('seed', '', True)
401                 wallet.use_encryption = False
402                 wallet.storage.put('use_encryption', wallet.use_encryption, True)
403                 for k in wallet.imported_keys.keys():
404                     wallet.imported_keys[k] = ''
405                 wallet.storage.put('imported_keys', wallet.imported_keys, True)
406                 print_msg("Done.")
407             else:
408                 print_msg("Action canceled.")
409
410     elif cmd.name == 'getconfig':
411         key = args[1]
412         out = config.get(key)
413         print_msg(out)
414
415     elif cmd.name == 'setconfig':
416         key, value = args[1:3]
417         try:
418             value = ast.literal_eval(value)
419         except:
420             pass
421         config.set_key(key, value, True)
422         print_msg(True)
423
424     elif cmd.name == 'password':
425         new_password = prompt_password('New password:')
426         wallet.update_password(password, new_password)
427
428     else:
429         run_command(cmd, password, args)
430
431
432     time.sleep(0.1)
433     sys.exit(0)