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