3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2011 thomasv@gitorious
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.
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.
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/>.
26 sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
31 sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
36 from electrum import *
38 from decimal import Decimal
41 'help':'Prints this help',
42 'validateaddress':'Check that the address is valid',
43 'balance': "Display the balance of your wallet or of an address.\nSyntax: balance [<address>]",
44 'contacts': "Show your list of contacts",
45 'create':'Create a wallet',
46 'restore':'Restore a wallet',
47 'payto':"""Create and broadcast a transaction.
48 Syntax: payto <recipient> <amount> [label]
49 <recipient> can be a bitcoin address or a label
50 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
53 'Broadcasts a transaction to the network. \nSyntax: sendtx <tx>\n<tx> must be in hexadecimal.',
55 "Changes your password",
57 """Shows your list of addresses.
59 -a: show all addresses, including change addresses
61 -b: show the balance of addresses""",
63 'history':"Shows the transaction history",
64 'label':'Assign a label to an item\nSyntax: label <tx_hash> <label>',
66 """Create a signed transaction, password protected.
67 Syntax: mktx <recipient> <amount> [label]
68 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
70 'signtx':"Sign an unsigned transaction created by a deseeded wallet\nSyntax: signtx <filename>",
72 "Print the generation seed of your wallet.",
74 'Imports a key pair\nSyntax: import <address>:<privatekey>',
76 'Signs a message with a key\nSyntax: signmessage <address> <message>\nIf you want to lead or end a message with spaces, or want double spaces inside the message make sure you quote the string. I.e. " Hello This is a weird String "',
78 'Verifies a signature\nSyntax: verifymessage <address> <signature> <message>\nIf you want to lead or end a message with spaces, or want double spaces inside the message make sure you quote the string. I.e. " Hello This is a weird String "',
80 "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
82 "Get config parameter.",
84 "Set config parameter.",
86 "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
88 "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
97 offline_commands = [ 'password', 'mktx', 'signtx',
99 'help', 'validateaddress',
100 'signmessage', 'verifymessage',
101 'eval', 'set', 'get', 'create', 'addresses',
102 'importprivkey', 'seed',
105 'prioritize','unprioritize']
108 protected_commands = ['payto', 'password', 'mktx', 'signtx', 'seed', 'importprivkey','signmessage' ]
110 # get password routine
111 def prompt_password(prompt, confirm=True):
113 if sys.stdin.isatty():
114 password = getpass.getpass(prompt)
115 if password and confirm:
116 password2 = getpass.getpass("Confirm: ")
117 if password != password2:
118 sys.exit("Error: Passwords do not match.")
120 password = raw_input(prompt)
126 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
127 parser = optparse.OptionParser(prog=usage)
128 parser.add_option("-g", "--gui", dest="gui", help="User interface: qt, lite, gtk or text")
129 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
130 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
131 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
132 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
133 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
134 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
135 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.")
136 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")
137 parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is t or h")
138 parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
139 parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="show debugging information")
140 parser.add_option("-P", "--portable", action="store_true", dest="portable", default=False, help="portable wallet")
141 parser.add_option("-L", "--lang", dest="language", default=None, help="defaut language used in GUI")
145 if __name__ == '__main__':
147 parser = arg_parser()
148 options, args = parser.parse_args()
149 set_verbosity(options.verbose)
151 # config is an object passed to the various constructors (wallet, interface, gui)
152 if 'ANDROID_DATA' in os.environ:
153 config_options = {'wallet_path':"/sdcard/electrum.dat", 'portable':True, 'verbose':True, 'gui':'android'}
155 config_options = eval(str(options))
156 for k, v in config_options.items():
157 if v is None: config_options.pop(k)
159 config = SimpleConfig(config_options)
160 wallet = Wallet(config)
165 elif len(args)==1 and re.match('^bitcoin:', args[0]):
170 firstarg = args[1] if len(args) > 1 else ''
172 #this entire if/else block is just concerned with importing the
173 #right GUI toolkit based the GUI command line option given
175 pref_gui = config.get('gui','classic')
176 if pref_gui == 'gtk':
178 import lib.gui as gui
180 import electrum.gui as gui
181 elif pref_gui in ['classic', 'qt']:
183 import lib.gui_qt as gui
185 import electrum.gui_qt as gui
186 elif pref_gui == 'lite':
188 import lib.gui_lite as gui
190 import electrum.gui_lite as gui
191 elif pref_gui == 'text':
193 import lib.gui_text as gui
195 import electrum.gui_text as gui
196 elif pref_gui == 'android':
198 import lib.gui_android as gui
200 import electrum.gui_android as gui
202 sys.exit("Error: Unknown GUI: " + pref_gui )
205 interface = Interface(config, True)
206 wallet.interface = interface
208 if interface.is_connected:
209 interface.send([('server.peers.subscribe',[])])
211 set_language(config.get('language'))
212 gui = gui.ElectrumGui(wallet, config)
214 found = config.wallet_file_exists
216 a = gui.restore_or_create()
219 s = gui.network_dialog()
222 wallet.new_seed(None)
223 wallet.init_mpk( wallet.seed )
225 # ask for seed and gap.
226 if not gui.seed_dialog(): exit()
227 wallet.init_mpk( wallet.seed )
229 # generate the first addresses, in case we are offline
230 if s is None or a == 'create':
236 verifier = WalletVerifier(interface, config)
237 wallet.set_verifier(verifier)
238 synchronizer = WalletSynchronizer(wallet, config)
241 if not found and a == 'restore' and s is not None:
243 keep_it = gui.restore_wallet()
244 wallet.fill_addressbook()
247 traceback.print_exc(file=sys.stdout)
250 if not keep_it: exit()
253 gui.password_dialog()
264 # we use daemon threads, their termination is enforced.
265 # this sleep command gives them time to terminate cleanly.
269 if cmd not in known_commands:
272 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
273 print_msg("Error: Wallet file not found.")
274 print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
277 if cmd in ['create', 'restore']:
278 if config.wallet_file_exists:
279 sys.exit("Error: Remove the existing wallet first!")
280 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
282 server = config.get('server')
283 if not server: server = pick_random_server()
284 w_host, w_port, w_protocol = server.split(':')
285 host = raw_input("server (default:%s):"%w_host)
286 port = raw_input("port (default:%s):"%w_port)
287 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
288 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
289 gap = raw_input("gap limit (default 5):")
290 if host: w_host = host
291 if port: w_port = port
292 if protocol: w_protocol = protocol
293 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
294 if fee: wallet.fee = float(fee)
295 if gap: wallet.gap_limit = int(gap)
298 seed = raw_input("seed:")
302 print_error("Warning: Not hex, trying decode.")
303 seed = mnemonic_decode( seed.split(' ') )
305 sys.exit("Error: No seed")
307 wallet.seed = str(seed)
308 wallet.init_mpk( wallet.seed )
309 if not options.offline:
311 interface = Interface(config)
313 wallet.interface = interface
315 verifier = WalletVerifier(interface, config)
316 wallet.set_verifier(verifier)
318 print_msg("Recovering wallet...")
319 WalletSynchronizer(wallet, config).start()
321 if wallet.is_found():
322 print_msg("Recovery successful")
324 print_msg("Warning: Found no history for this wallet")
327 wallet.fill_addressbook()
329 print_msg("Wallet saved in '%s'"%wallet.config.path)
331 wallet.new_seed(None)
332 wallet.init_mpk( wallet.seed )
333 wallet.synchronize() # there is no wallet thread
335 print_msg("Your wallet generation seed is: " + wallet.seed)
336 print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
337 print_msg("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:")
338 print_msg("\""+' '.join(mnemonic_encode(wallet.seed))+"\"")
339 print_msg("Wallet saved in '%s'"%wallet.config.path)
342 wallet.update_password(wallet.seed, None, password)
345 if cmd in ['payto', 'mktx']:
348 amount = int( 100000000 * Decimal(args[2]) )
350 label = ' '.join(args[3:])
352 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
358 if cmd not in offline_commands and not options.offline:
359 interface = Interface(config)
360 interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n"))
362 wallet.interface = interface
363 synchronizer = WalletSynchronizer(wallet, config)
368 # check if --from_addr not in wallet (for mktx/payto)
371 if options.from_addr:
372 from_addr = options.from_addr
373 if from_addr not in wallet.all_addresses():
377 if cmd=='addresses' and options.show_keys:
378 print_msg("WARNING: ALL your private keys are secret.")
379 print_msg("Exposing a single private key can compromise your entire wallet!")
380 print_msg("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
382 # commands needing password
383 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
384 if wallet.use_encryption and not is_temporary:
385 password = prompt_password('Password:', False)
387 print_msg("Error: Password required")
391 wallet.pw_decode( wallet.seed, password)
393 print_msg("Error: This password does not decode this wallet.")
398 if cmd == 'importprivkey':
399 # See if they specificed a key on the cmd line, if not prompt
403 sec = prompt_password('Enter PrivateKey (will not echo):', False)
405 addr = wallet.import_key(sec,password)
407 print_msg("Keypair imported: ", addr)
408 except BaseException as e:
409 print_msg("Error: Keypair import failed: " + str(e))
413 if cmd2 not in known_commands:
415 print_msg("Type 'electrum help <command>' to see the help for a specific command")
416 print_msg("Type 'electrum --help' to see the list of options")
417 print_msg("List of commands:", ', '.join(known_commands))
419 print_msg(known_commands[cmd2])
422 seed = wallet.pw_decode( wallet.seed, password)
423 print_msg(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"')
425 elif cmd == 'deseed':
427 print_msg("Error: This wallet has no seed")
428 elif wallet.use_encryption:
429 print_msg("Error: This wallet is encrypted")
431 ns = wallet.config.path + '.seed'
432 print_msg("Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.config.path,ns))
433 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
435 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
438 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
442 print_msg("Action canceled.")
444 elif cmd == 'reseed':
446 print_msg("Warning: This wallet already has a seed", wallet.seed)
448 ns = wallet.config.path + '.seed'
454 sys.exit("Error: Seed file not found")
457 d = ast.literal_eval( data )
459 imported_keys = d.get('imported_keys',{})
461 sys.exit("Error: Error with seed file")
463 mpk = wallet.master_public_key
465 wallet.imported_keys = imported_keys
466 wallet.use_encryption = False
467 wallet.init_mpk(seed)
468 if mpk == wallet.master_public_key:
470 print_msg("Done: " + wallet.config.path)
472 print_msg("Error: Master public key does not match")
474 elif cmd == 'validateaddress':
476 print_msg(wallet.is_valid(addr))
478 elif cmd == 'balance':
484 c, u = wallet.get_balance()
486 print_msg(Decimal( c ) / 100000000 , Decimal( u ) / 100000000)
488 print_msg(Decimal( c ) / 100000000)
491 c, u = wallet.get_addr_balance(addr)
493 print_msg("%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000)))
495 print_msg("%s %s" % (addr, str(Decimal(c)/100000000)))
497 elif cmd in [ 'contacts']:
498 for addr in wallet.addressbook:
499 print_msg(addr, " ", wallet.labels.get(addr))
502 print_msg(eval(args[1]))
507 print_msg(wallet.config.get(key))
510 key, value = args[1:3]
511 if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
512 wallet.config.set_key(key, value, True)
517 elif cmd in [ 'addresses']:
518 for addr in wallet.all_addresses():
519 if options.show_all or not wallet.is_change(addr):
521 flags = wallet.get_address_flags(addr)
522 label = wallet.labels.get(addr,'')
524 if label: label = "\"%s\""%label
526 if options.show_balance:
527 h = wallet.history.get(addr,[])
530 # if item['is_input']: ni += 1
532 b = format_satoshis(wallet.get_addr_balance(addr)[0])
535 if options.show_keys:
536 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
537 print_msg(flags, m_addr, b, label)
541 for item in wallet.get_tx_history():
542 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
544 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
548 label, is_default_label = wallet.get_label(tx_hash)
549 if not label: label = tx_hash
550 else: label = label + ' '*(64 - len(label) )
552 print_msg("%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance))
553 print_msg("# balance: ", format_satoshis(balance))
558 label = ' '.join(args[2:])
560 print_msg("Error. Syntax: label <tx_hash> <text>")
562 wallet.labels[tx] = label
565 elif cmd in ['payto', 'mktx']:
566 if from_addr and is_temporary:
567 if from_addr.find(":") == -1:
568 keypair = from_addr + ":" + prompt_password('Private key:', False)
571 from_addr = keypair.split(':')[0]
572 if not wallet.import_key(keypair,password):
573 print_msg("Error: Invalid key pair")
575 wallet.history[from_addr] = interface.retrieve_history(from_addr)
576 wallet.update_tx_history()
577 change_addr = from_addr
579 if options.change_addr:
580 change_addr = options.change_addr
582 for k, v in wallet.labels.items():
585 print_msg("alias", to_address)
587 if change_addr and v == change_addr:
590 tx = wallet.mktx( [(to_address, amount)], label, password,
591 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
594 traceback.print_exc(file=sys.stdout)
597 if tx and cmd=='payto':
598 r, h = wallet.sendtx( tx )
604 wallet.imported_keys.pop(from_addr)
605 del(wallet.history[from_addr])
608 elif cmd == 'signtx':
611 f = open(filename, 'r')
612 d = ast.literal_eval(f.read())
615 outputs = d['outputs']
616 tx = wallet.signed_tx( inputs, outputs, password )
619 elif cmd == 'sendtx':
621 r, h = wallet.sendtx( tx )
624 elif cmd == 'password':
626 seed = wallet.pw_decode( wallet.seed, password)
628 sys.exit("Error: Password does not decrypt this wallet.")
630 new_password = prompt_password('New password:')
631 wallet.update_password(seed, password, new_password)
633 elif cmd == 'signmessage':
635 print_msg("Error: Invalid usage of signmessage.")
636 print_msg(known_commands[cmd])
639 message = ' '.join(args[2:])
641 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
642 print_msg(wallet.sign_message(address, message, password))
644 elif cmd == 'verifymessage':
648 message = ' '.join(args[3:])
650 print_msg("Error: Not all parameters were given, displaying help instead.")
651 print_msg(known_commands[cmd])
654 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
656 wallet.verify_message(address, signature, message)
658 except BaseException as e:
659 print_error("Verification error: {0}".format(e))
662 elif cmd == 'freeze':
664 print_msg(wallet.freeze(addr))
666 elif cmd == 'unfreeze':
668 print_msg(wallet.unfreeze(addr))
670 elif cmd == 'prioritize':
672 print_msg(wallet.prioritize(addr))
674 elif cmd == 'unprioritize':
676 print_msg(wallet.unprioritize(addr))
679 if cmd not in offline_commands and not options.offline: