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
71 "Print the generation seed of your wallet.",
73 'Imports a key pair\nSyntax: import <address>:<privatekey>',
75 '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 "',
77 '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 "',
79 "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
81 "Get config parameter.",
83 "Set config parameter.",
85 "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
87 "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
96 offline_commands = [ 'password', 'mktx',
98 'help', 'validateaddress',
99 'signmessage', 'verifymessage',
100 'eval', 'set', 'get', 'create', 'addresses',
104 'prioritize','unprioritize']
107 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
109 # get password routine
110 def prompt_password(prompt, confirm=True):
112 if sys.stdin.isatty():
113 password = getpass.getpass(prompt)
114 if password and confirm:
115 password2 = getpass.getpass("Confirm: ")
116 if password != password2:
117 sys.exit("Error: Passwords do not match.")
119 password = raw_input(prompt)
125 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
126 parser = optparse.OptionParser(prog=usage)
127 parser.add_option("-g", "--gui", dest="gui", help="User interface: qt, lite, gtk or text")
128 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
129 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
130 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
131 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
132 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
133 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
134 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.")
135 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")
136 parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is t or h")
137 parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
138 parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="show debugging information")
142 if __name__ == '__main__':
144 parser = arg_parser()
145 options, args = parser.parse_args()
146 set_verbosity(options.verbose)
148 # config is an object passed to the various constructors (wallet, interface, gui)
149 if 'ANDROID_DATA' in os.environ:
150 config_options = {'wallet_path':"/sdcard/electrum.dat", 'blockchain_headers_path':'/sdcard/sl4a/scripts/e4a-%s'%ELECTRUM_VERSION, 'gui':'android'}
152 config_options = eval(str(options))
153 for k, v in config_options.items():
154 if v is None: config_options.pop(k)
156 config = SimpleConfig(config_options)
157 wallet = Wallet(config)
162 elif len(args)==1 and re.match('^bitcoin:', args[0]):
167 firstarg = args[1] if len(args) > 1 else ''
169 #this entire if/else block is just concerned with importing the
170 #right GUI toolkit based the GUI command line option given
172 pref_gui = config.get('gui','classic')
173 if pref_gui == 'gtk':
175 import lib.gui as gui
177 import electrum.gui as gui
178 elif pref_gui in ['classic', 'qt']:
180 import lib.gui_qt as gui
182 import electrum.gui_qt as gui
183 elif pref_gui == 'lite':
185 import lib.gui_lite as gui
187 import electrum.gui_lite as gui
188 elif pref_gui == 'text':
190 import lib.gui_text as gui
192 import electrum.gui_text as gui
193 elif pref_gui == 'android':
195 import lib.gui_android as gui
197 import electrum.gui_android as gui
199 sys.exit("Error: Unknown GUI: " + pref_gui )
202 interface = Interface(config, True)
203 wallet.interface = interface
205 interface.send([('server.peers.subscribe',[])])
207 gui = gui.ElectrumGui(wallet, config)
209 found = config.wallet_file_exists
211 a = gui.restore_or_create()
214 s = gui.network_dialog()
217 wallet.new_seed(None)
218 wallet.init_mpk( wallet.seed )
220 # ask for seed and gap.
221 if not gui.seed_dialog(): exit()
222 wallet.init_mpk( wallet.seed )
224 # generate the first addresses, in case we are offline
225 if s is None or a == 'create':
231 verifier = WalletVerifier(interface, config)
232 wallet.set_verifier(verifier)
233 synchronizer = WalletSynchronizer(wallet, config)
236 if not found and a == 'restore' and s is not None:
238 keep_it = gui.restore_wallet()
239 wallet.fill_addressbook()
242 traceback.print_exc(file=sys.stdout)
245 if not keep_it: exit()
248 gui.password_dialog()
259 # we use daemon threads, their termination is enforced.
260 # this sleep command gives them time to terminate cleanly.
264 if cmd not in known_commands:
267 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
268 print_msg("Error: Wallet file not found.")
269 print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
272 if cmd in ['create', 'restore']:
273 if config.wallet_file_exists:
274 sys.exit("Error: Remove the existing wallet first!")
275 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
277 server = config.get('server')
278 if not server: server = pick_random_server()
279 w_host, w_port, w_protocol = server.split(':')
280 host = raw_input("server (default:%s):"%w_host)
281 port = raw_input("port (default:%s):"%w_port)
282 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
283 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
284 gap = raw_input("gap limit (default 5):")
285 if host: w_host = host
286 if port: w_port = port
287 if protocol: w_protocol = protocol
288 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
289 if fee: wallet.fee = float(fee)
290 if gap: wallet.gap_limit = int(gap)
293 seed = raw_input("seed:")
297 print_error("Warning: Not hex, trying decode.")
298 seed = mnemonic_decode( seed.split(' ') )
300 sys.exit("Error: No seed")
302 wallet.seed = str(seed)
303 wallet.init_mpk( wallet.seed )
304 if not options.offline:
306 interface = Interface(config)
308 wallet.interface = interface
310 verifier = WalletVerifier(interface, config)
311 wallet.set_verifier(verifier)
313 print_msg("Recovering wallet...")
314 WalletSynchronizer(wallet, config).start()
316 if wallet.is_found():
317 print_msg("Recovery successful")
319 print_msg("Warning: Found no history for this wallet")
322 wallet.fill_addressbook()
324 print_msg("Wallet saved in '%s'"%wallet.config.path)
326 wallet.new_seed(None)
327 wallet.init_mpk( wallet.seed )
328 wallet.synchronize() # there is no wallet thread
330 print_msg("Your wallet generation seed is: " + wallet.seed)
331 print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
332 print_msg("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:")
333 print_msg("\""+' '.join(mnemonic_encode(wallet.seed))+"\"")
334 print_msg("Wallet saved in '%s'"%wallet.config.path)
337 wallet.update_password(wallet.seed, None, password)
340 if cmd in ['payto', 'mktx']:
343 amount = int( 100000000 * Decimal(args[2]) )
345 label = ' '.join(args[3:])
347 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
353 if cmd not in offline_commands and not options.offline:
354 interface = Interface(config)
355 interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n"))
357 wallet.interface = interface
358 synchronizer = WalletSynchronizer(wallet, config)
363 # check if --from_addr not in wallet (for mktx/payto)
366 if options.from_addr:
367 from_addr = options.from_addr
368 if from_addr not in wallet.all_addresses():
372 if cmd=='addresses' and options.show_keys:
373 print_msg("WARNING: ALL your private keys are secret.")
374 print_msg("Exposing a single private key can compromise your entire wallet!")
375 print_msg("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
377 # commands needing password
378 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
379 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
382 wallet.pw_decode( wallet.seed, password)
384 print_msg("Error: This password does not decode this wallet.")
388 # See if they specificed a key on the cmd line, if not prompt
392 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
394 wallet.import_key(keypair,password)
396 print_msg("Keypair imported")
397 except BaseException as e:
398 print_msg("Error: Keypair import failed: " + str(e))
402 if cmd2 not in known_commands:
404 print_msg("Type 'electrum help <command>' to see the help for a specific command")
405 print_msg("Type 'electrum --help' to see the list of options")
406 print_msg("List of commands:", ', '.join(known_commands))
408 print_msg(known_commands[cmd2])
411 seed = wallet.pw_decode( wallet.seed, password)
412 print_msg(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"')
414 elif cmd == 'deseed':
416 print_msg("Error: This wallet has no seed")
417 elif wallet.use_encryption:
418 print_msg("Error: This wallet is encrypted")
420 ns = wallet.config.path + '.seed'
421 print_msg("Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.config.path,ns))
422 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
424 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
427 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
431 print_msg("Action canceled.")
433 elif cmd == 'reseed':
435 print_msg("Warning: This wallet already has a seed", wallet.seed)
437 ns = wallet.config.path + '.seed'
443 sys.exit("Error: Seed file not found")
446 d = ast.literal_eval( data )
448 imported_keys = d.get('imported_keys',{})
450 sys.exit("Error: Error with seed file")
452 mpk = wallet.master_public_key
454 wallet.imported_keys = imported_keys
455 wallet.use_encryption = False
456 wallet.init_mpk(seed)
457 if mpk == wallet.master_public_key:
459 print_msg("Done: " + wallet.config.path)
461 print_msg("Error: Master public key does not match")
463 elif cmd == 'validateaddress':
465 print_msg(wallet.is_valid(addr))
467 elif cmd == 'balance':
473 c, u = wallet.get_balance()
475 print_msg(Decimal( c ) / 100000000 , Decimal( u ) / 100000000)
477 print_msg(Decimal( c ) / 100000000)
480 c, u = wallet.get_addr_balance(addr)
482 print_msg("%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000)))
484 print_msg("%s %s" % (addr, str(Decimal(c)/100000000)))
486 elif cmd in [ 'contacts']:
487 for addr in wallet.addressbook:
488 print_msg(addr, " ", wallet.labels.get(addr))
491 print_msg(eval(args[1]))
496 print_msg(wallet.config.get(key))
499 key, value = args[1:3]
500 if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
501 wallet.config.set_key(key, value, True)
506 elif cmd in [ 'addresses']:
507 for addr in wallet.all_addresses():
508 if options.show_all or not wallet.is_change(addr):
510 flags = wallet.get_address_flags(addr)
511 label = wallet.labels.get(addr,'')
513 if label: label = "\"%s\""%label
515 if options.show_balance:
516 h = wallet.history.get(addr,[])
519 # if item['is_input']: ni += 1
521 b = format_satoshis(wallet.get_addr_balance(addr)[0])
524 if options.show_keys:
525 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
526 print_msg(flags, m_addr, b, label)
530 for item in wallet.get_tx_history():
531 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
533 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
537 label, is_default_label = wallet.get_label(tx_hash)
538 if not label: label = tx_hash
539 else: label = label + ' '*(64 - len(label) )
541 print_msg("%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance))
542 print_msg("# balance: ", format_satoshis(balance))
547 label = ' '.join(args[2:])
549 print_msg("Error. Syntax: label <tx_hash> <text>")
551 wallet.labels[tx] = label
554 elif cmd in ['payto', 'mktx']:
555 if from_addr and is_temporary:
556 if from_addr.find(":") == -1:
557 keypair = from_addr + ":" + prompt_password('Private key:', False)
560 from_addr = keypair.split(':')[0]
561 if not wallet.import_key(keypair,password):
562 print_msg("Error: Invalid key pair")
564 wallet.history[from_addr] = interface.retrieve_history(from_addr)
565 wallet.update_tx_history()
566 change_addr = from_addr
568 if options.change_addr:
569 change_addr = options.change_addr
571 for k, v in wallet.labels.items():
574 print_msg("alias", to_address)
576 if change_addr and v == change_addr:
579 tx = wallet.mktx( [(to_address, amount)], label, password,
580 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
583 traceback.print_exc(file=sys.stdout)
586 if tx and cmd=='payto':
587 r, h = wallet.sendtx( tx )
593 wallet.imported_keys.pop(from_addr)
594 del(wallet.history[from_addr])
597 elif cmd == 'sendtx':
599 r, h = wallet.sendtx( tx )
602 elif cmd == 'password':
604 seed = wallet.pw_decode( wallet.seed, password)
606 sys.exit("Error: Password does not decrypt this wallet.")
608 new_password = prompt_password('New password:')
609 wallet.update_password(seed, password, new_password)
611 elif cmd == 'signmessage':
613 print_msg("Error: Invalid usage of signmessage.")
614 print_msg(known_commands[cmd])
617 message = ' '.join(args[2:])
619 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
620 print_msg(wallet.sign_message(address, message, password))
622 elif cmd == 'verifymessage':
626 message = ' '.join(args[3:])
628 print_msg("Error: Not all parameters were given, displaying help instead.")
629 print_msg(known_commands[cmd])
632 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
634 wallet.verify_message(address, signature, message)
636 except BaseException as e:
637 print_error("Verification error: {0}".format(e))
640 elif cmd == 'freeze':
642 print_msg(wallet.freeze(addr))
644 elif cmd == 'unfreeze':
646 print_msg(wallet.unfreeze(addr))
648 elif cmd == 'prioritize':
650 print_msg(wallet.prioritize(addr))
652 elif cmd == 'unprioritize':
654 print_msg(wallet.unprioritize(addr))
657 if cmd not in offline_commands and not options.offline: