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',
105 'prioritize','unprioritize']
108 protected_commands = ['payto', 'password', 'mktx', 'signtx', 'seed', 'import','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")
143 if __name__ == '__main__':
145 parser = arg_parser()
146 options, args = parser.parse_args()
147 set_verbosity(options.verbose)
149 # config is an object passed to the various constructors (wallet, interface, gui)
150 if 'ANDROID_DATA' in os.environ:
151 config_options = {'wallet_path':"/sdcard/electrum.dat", 'blockchain_headers_path':'/sdcard/sl4a/scripts/e4a-%s'%ELECTRUM_VERSION, 'gui':'android'}
153 config_options = eval(str(options))
154 for k, v in config_options.items():
155 if v is None: config_options.pop(k)
157 config = SimpleConfig(config_options)
158 wallet = Wallet(config)
163 elif len(args)==1 and re.match('^bitcoin:', args[0]):
168 firstarg = args[1] if len(args) > 1 else ''
170 #this entire if/else block is just concerned with importing the
171 #right GUI toolkit based the GUI command line option given
173 pref_gui = config.get('gui','classic')
174 if pref_gui == 'gtk':
176 import lib.gui as gui
178 import electrum.gui as gui
179 elif pref_gui in ['classic', 'qt']:
181 import lib.gui_qt as gui
183 import electrum.gui_qt as gui
184 elif pref_gui == 'lite':
186 import lib.gui_lite as gui
188 import electrum.gui_lite as gui
189 elif pref_gui == 'text':
191 import lib.gui_text as gui
193 import electrum.gui_text as gui
194 elif pref_gui == 'android':
196 import lib.gui_android as gui
198 import electrum.gui_android as gui
200 sys.exit("Error: Unknown GUI: " + pref_gui )
203 interface = Interface(config, True)
204 wallet.interface = interface
206 interface.send([('server.peers.subscribe',[])])
208 gui = gui.ElectrumGui(wallet, config)
210 found = config.wallet_file_exists
212 a = gui.restore_or_create()
215 s = gui.network_dialog()
218 wallet.new_seed(None)
219 wallet.init_mpk( wallet.seed )
221 # ask for seed and gap.
222 if not gui.seed_dialog(): exit()
223 wallet.init_mpk( wallet.seed )
225 # generate the first addresses, in case we are offline
226 if s is None or a == 'create':
232 verifier = WalletVerifier(interface, config)
233 wallet.set_verifier(verifier)
234 synchronizer = WalletSynchronizer(wallet, config)
237 if not found and a == 'restore' and s is not None:
239 keep_it = gui.restore_wallet()
240 wallet.fill_addressbook()
243 traceback.print_exc(file=sys.stdout)
246 if not keep_it: exit()
249 gui.password_dialog()
260 # we use daemon threads, their termination is enforced.
261 # this sleep command gives them time to terminate cleanly.
265 if cmd not in known_commands:
268 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
269 print_msg("Error: Wallet file not found.")
270 print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
273 if cmd in ['create', 'restore']:
274 if config.wallet_file_exists:
275 sys.exit("Error: Remove the existing wallet first!")
276 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
278 server = config.get('server')
279 if not server: server = pick_random_server()
280 w_host, w_port, w_protocol = server.split(':')
281 host = raw_input("server (default:%s):"%w_host)
282 port = raw_input("port (default:%s):"%w_port)
283 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
284 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
285 gap = raw_input("gap limit (default 5):")
286 if host: w_host = host
287 if port: w_port = port
288 if protocol: w_protocol = protocol
289 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
290 if fee: wallet.fee = float(fee)
291 if gap: wallet.gap_limit = int(gap)
294 seed = raw_input("seed:")
298 print_error("Warning: Not hex, trying decode.")
299 seed = mnemonic_decode( seed.split(' ') )
301 sys.exit("Error: No seed")
303 wallet.seed = str(seed)
304 wallet.init_mpk( wallet.seed )
305 if not options.offline:
307 interface = Interface(config)
309 wallet.interface = interface
311 verifier = WalletVerifier(interface, config)
312 wallet.set_verifier(verifier)
314 print_msg("Recovering wallet...")
315 WalletSynchronizer(wallet, config).start()
317 if wallet.is_found():
318 print_msg("Recovery successful")
320 print_msg("Warning: Found no history for this wallet")
323 wallet.fill_addressbook()
325 print_msg("Wallet saved in '%s'"%wallet.config.path)
327 wallet.new_seed(None)
328 wallet.init_mpk( wallet.seed )
329 wallet.synchronize() # there is no wallet thread
331 print_msg("Your wallet generation seed is: " + wallet.seed)
332 print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
333 print_msg("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:")
334 print_msg("\""+' '.join(mnemonic_encode(wallet.seed))+"\"")
335 print_msg("Wallet saved in '%s'"%wallet.config.path)
338 wallet.update_password(wallet.seed, None, password)
341 if cmd in ['payto', 'mktx']:
344 amount = int( 100000000 * Decimal(args[2]) )
346 label = ' '.join(args[3:])
348 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
354 if cmd not in offline_commands and not options.offline:
355 interface = Interface(config)
356 interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n"))
358 wallet.interface = interface
359 synchronizer = WalletSynchronizer(wallet, config)
364 # check if --from_addr not in wallet (for mktx/payto)
367 if options.from_addr:
368 from_addr = options.from_addr
369 if from_addr not in wallet.all_addresses():
373 if cmd=='addresses' and options.show_keys:
374 print_msg("WARNING: ALL your private keys are secret.")
375 print_msg("Exposing a single private key can compromise your entire wallet!")
376 print_msg("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
378 # commands needing password
379 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
380 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
383 wallet.pw_decode( wallet.seed, password)
385 print_msg("Error: This password does not decode this wallet.")
389 # See if they specificed a key on the cmd line, if not prompt
393 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
395 wallet.import_key(keypair,password)
397 print_msg("Keypair imported")
398 except BaseException as e:
399 print_msg("Error: Keypair import failed: " + str(e))
403 if cmd2 not in known_commands:
405 print_msg("Type 'electrum help <command>' to see the help for a specific command")
406 print_msg("Type 'electrum --help' to see the list of options")
407 print_msg("List of commands:", ', '.join(known_commands))
409 print_msg(known_commands[cmd2])
412 seed = wallet.pw_decode( wallet.seed, password)
413 print_msg(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"')
415 elif cmd == 'deseed':
417 print_msg("Error: This wallet has no seed")
418 elif wallet.use_encryption:
419 print_msg("Error: This wallet is encrypted")
421 ns = wallet.config.path + '.seed'
422 print_msg("Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.config.path,ns))
423 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
425 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
428 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
432 print_msg("Action canceled.")
434 elif cmd == 'reseed':
436 print_msg("Warning: This wallet already has a seed", wallet.seed)
438 ns = wallet.config.path + '.seed'
444 sys.exit("Error: Seed file not found")
447 d = ast.literal_eval( data )
449 imported_keys = d.get('imported_keys',{})
451 sys.exit("Error: Error with seed file")
453 mpk = wallet.master_public_key
455 wallet.imported_keys = imported_keys
456 wallet.use_encryption = False
457 wallet.init_mpk(seed)
458 if mpk == wallet.master_public_key:
460 print_msg("Done: " + wallet.config.path)
462 print_msg("Error: Master public key does not match")
464 elif cmd == 'validateaddress':
466 print_msg(wallet.is_valid(addr))
468 elif cmd == 'balance':
474 c, u = wallet.get_balance()
476 print_msg(Decimal( c ) / 100000000 , Decimal( u ) / 100000000)
478 print_msg(Decimal( c ) / 100000000)
481 c, u = wallet.get_addr_balance(addr)
483 print_msg("%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000)))
485 print_msg("%s %s" % (addr, str(Decimal(c)/100000000)))
487 elif cmd in [ 'contacts']:
488 for addr in wallet.addressbook:
489 print_msg(addr, " ", wallet.labels.get(addr))
492 print_msg(eval(args[1]))
497 print_msg(wallet.config.get(key))
500 key, value = args[1:3]
501 if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
502 wallet.config.set_key(key, value, True)
507 elif cmd in [ 'addresses']:
508 for addr in wallet.all_addresses():
509 if options.show_all or not wallet.is_change(addr):
511 flags = wallet.get_address_flags(addr)
512 label = wallet.labels.get(addr,'')
514 if label: label = "\"%s\""%label
516 if options.show_balance:
517 h = wallet.history.get(addr,[])
520 # if item['is_input']: ni += 1
522 b = format_satoshis(wallet.get_addr_balance(addr)[0])
525 if options.show_keys:
526 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
527 print_msg(flags, m_addr, b, label)
531 for item in wallet.get_tx_history():
532 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
534 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
538 label, is_default_label = wallet.get_label(tx_hash)
539 if not label: label = tx_hash
540 else: label = label + ' '*(64 - len(label) )
542 print_msg("%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance))
543 print_msg("# balance: ", format_satoshis(balance))
548 label = ' '.join(args[2:])
550 print_msg("Error. Syntax: label <tx_hash> <text>")
552 wallet.labels[tx] = label
555 elif cmd in ['payto', 'mktx']:
556 if from_addr and is_temporary:
557 if from_addr.find(":") == -1:
558 keypair = from_addr + ":" + prompt_password('Private key:', False)
561 from_addr = keypair.split(':')[0]
562 if not wallet.import_key(keypair,password):
563 print_msg("Error: Invalid key pair")
565 wallet.history[from_addr] = interface.retrieve_history(from_addr)
566 wallet.update_tx_history()
567 change_addr = from_addr
569 if options.change_addr:
570 change_addr = options.change_addr
572 for k, v in wallet.labels.items():
575 print_msg("alias", to_address)
577 if change_addr and v == change_addr:
580 tx = wallet.mktx( [(to_address, amount)], label, password,
581 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
584 traceback.print_exc(file=sys.stdout)
587 if tx and cmd=='payto':
588 r, h = wallet.sendtx( tx )
594 wallet.imported_keys.pop(from_addr)
595 del(wallet.history[from_addr])
598 elif cmd == 'signtx':
601 f = open(filename, 'r')
602 d = ast.literal_eval(f.read())
605 outputs = d['outputs']
606 tx = wallet.signed_tx( inputs, outputs, password )
609 elif cmd == 'sendtx':
611 r, h = wallet.sendtx( tx )
614 elif cmd == 'password':
616 seed = wallet.pw_decode( wallet.seed, password)
618 sys.exit("Error: Password does not decrypt this wallet.")
620 new_password = prompt_password('New password:')
621 wallet.update_password(seed, password, new_password)
623 elif cmd == 'signmessage':
625 print_msg("Error: Invalid usage of signmessage.")
626 print_msg(known_commands[cmd])
629 message = ' '.join(args[2:])
631 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
632 print_msg(wallet.sign_message(address, message, password))
634 elif cmd == 'verifymessage':
638 message = ' '.join(args[3:])
640 print_msg("Error: Not all parameters were given, displaying help instead.")
641 print_msg(known_commands[cmd])
644 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
646 wallet.verify_message(address, signature, message)
648 except BaseException as e:
649 print_error("Verification error: {0}".format(e))
652 elif cmd == 'freeze':
654 print_msg(wallet.freeze(addr))
656 elif cmd == 'unfreeze':
658 print_msg(wallet.unfreeze(addr))
660 elif cmd == 'prioritize':
662 print_msg(wallet.prioritize(addr))
664 elif cmd == 'unprioritize':
666 print_msg(wallet.unprioritize(addr))
669 if cmd not in offline_commands and not options.offline: