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/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
230 verifier = WalletVerifier(interface, config)
231 wallet.set_verifier(verifier)
232 WalletSynchronizer(wallet, config).start()
234 if not found and a == 'restore' and s is not None:
236 keep_it = gui.restore_wallet()
237 wallet.fill_addressbook()
240 traceback.print_exc(file=sys.stdout)
243 if not keep_it: exit()
246 gui.password_dialog()
254 if cmd not in known_commands:
257 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
258 print_msg("Error: Wallet file not found.")
259 print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
262 if cmd in ['create', 'restore']:
263 if config.wallet_file_exists:
264 sys.exit("Error: Remove the existing wallet first!")
265 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
267 server = config.get('server')
268 if not server: server = pick_random_server()
269 w_host, w_port, w_protocol = server.split(':')
270 host = raw_input("server (default:%s):"%w_host)
271 port = raw_input("port (default:%s):"%w_port)
272 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
273 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
274 gap = raw_input("gap limit (default 5):")
275 if host: w_host = host
276 if port: w_port = port
277 if protocol: w_protocol = protocol
278 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
279 if fee: wallet.fee = float(fee)
280 if gap: wallet.gap_limit = int(gap)
283 seed = raw_input("seed:")
287 print_error("Warning: Not hex, trying decode.")
288 seed = mnemonic_decode( seed.split(' ') )
290 sys.exit("Error: No seed")
292 wallet.seed = str(seed)
293 wallet.init_mpk( wallet.seed )
294 if not options.offline:
296 interface = Interface(config)
298 wallet.interface = interface
300 verifier = WalletVerifier(interface, config)
301 wallet.set_verifier(verifier)
303 print_msg("Recovering wallet...")
304 WalletSynchronizer(wallet, config).start()
306 if wallet.is_found():
307 print_msg("Recovery successful")
309 print_msg("Warning: Found no history for this wallet")
312 wallet.fill_addressbook()
314 print_msg("Wallet saved in '%s'"%wallet.config.path)
316 wallet.new_seed(None)
317 wallet.init_mpk( wallet.seed )
318 wallet.synchronize() # there is no wallet thread
320 print_msg("Your wallet generation seed is: " + wallet.seed)
321 print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
322 print_msg("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:")
323 print_msg("\""+' '.join(mnemonic_encode(wallet.seed))+"\"")
324 print_msg("Wallet saved in '%s'"%wallet.config.path)
327 wallet.update_password(wallet.seed, None, password)
330 if cmd in ['payto', 'mktx']:
333 amount = int( 100000000 * Decimal(args[2]) )
335 label = ' '.join(args[3:])
337 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
343 if cmd not in offline_commands and not options.offline:
344 interface = Interface(config)
345 interface.register_callback('connected', lambda: print_error("Connected to " + interface.connection_msg))
347 wallet.interface = interface
348 WalletSynchronizer(wallet, config).start()
352 # check if --from_addr not in wallet (for mktx/payto)
355 if options.from_addr:
356 from_addr = options.from_addr
357 if from_addr not in wallet.all_addresses():
361 if cmd=='addresses' and options.show_keys:
362 print_msg("WARNING: ALL your private keys are secret.")
363 print_msg("Exposing a single private key can compromise your entire wallet!")
364 print_msg("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
366 # commands needing password
367 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
368 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
371 wallet.pw_decode( wallet.seed, password)
373 print_error("Error: This password does not decode this wallet.")
377 # See if they specificed a key on the cmd line, if not prompt
381 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
383 wallet.import_key(keypair,password)
385 print_msg("Keypair imported")
386 except BaseException(e):
387 print_error("Error: Keypair import failed: " + str(e))
391 if cmd2 not in known_commands:
393 print_msg("Type 'electrum help <command>' to see the help for a specific command")
394 print_msg("Type 'electrum --help' to see the list of options")
395 print_msg("List of commands:", ', '.join(known_commands))
397 print_msg(known_commands[cmd2])
400 seed = wallet.pw_decode( wallet.seed, password)
401 print_msg(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"')
403 elif cmd == 'deseed':
405 print_error("Error: This wallet has no seed")
406 elif wallet.use_encryption:
407 print_error("Error: This wallet is encrypted")
409 ns = wallet.config.path + '.seed'
410 print_msg("Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.config.path,ns))
411 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
413 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
416 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
420 print_error("Action canceled.")
422 elif cmd == 'reseed':
424 print_msg("Warning: This wallet already has a seed", wallet.seed)
426 ns = wallet.config.path + '.seed'
432 sys.exit("Error: Seed file not found")
435 d = ast.literal_eval( data )
437 imported_keys = d.get('imported_keys',{})
439 sys.exit("Error: Error with seed file")
441 mpk = wallet.master_public_key
443 wallet.imported_keys = imported_keys
444 wallet.use_encryption = False
445 wallet.init_mpk(seed)
446 if mpk == wallet.master_public_key:
448 print_msg("Done: " + wallet.config.path)
450 print_error("Error: Master public key does not match")
452 elif cmd == 'validateaddress':
454 print_msg(wallet.is_valid(addr))
456 elif cmd == 'balance':
462 c, u = wallet.get_balance()
464 print_msg(Decimal( c ) / 100000000 , Decimal( u ) / 100000000)
466 print_msg(Decimal( c ) / 100000000)
469 c, u = wallet.get_addr_balance(addr)
471 print_msg("%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000)))
473 print_msg("%s %s" % (addr, str(Decimal(c)/100000000)))
475 elif cmd in [ 'contacts']:
476 for addr in wallet.addressbook:
477 print_msg(addr, " ", wallet.labels.get(addr))
480 print_msg(eval(args[1]))
485 print_msg(wallet.config.get(key))
488 key, value = args[1:3]
489 if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
490 wallet.config.set_key(key, value, True)
495 elif cmd in [ 'addresses']:
496 for addr in wallet.all_addresses():
497 if options.show_all or not wallet.is_change(addr):
499 flags = wallet.get_address_flags(addr)
500 label = wallet.labels.get(addr,'')
502 if label: label = "\"%s\""%label
504 if options.show_balance:
505 h = wallet.history.get(addr,[])
508 # if item['is_input']: ni += 1
510 b = format_satoshis(wallet.get_addr_balance(addr)[0])
513 if options.show_keys:
514 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
515 print_msg(flags, m_addr, b, label)
519 for item in wallet.get_tx_history():
520 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
522 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
526 label, is_default_label = wallet.get_label(tx_hash)
527 if not label: label = tx_hash
528 else: label = label + ' '*(64 - len(label) )
530 print_msg("%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance))
531 print_msg("# balance: ", format_satoshis(balance))
536 label = ' '.join(args[2:])
538 print_error("Error. Syntax: label <tx_hash> <text>")
540 wallet.labels[tx] = label
543 elif cmd in ['payto', 'mktx']:
544 if from_addr and is_temporary:
545 if from_addr.find(":") == -1:
546 keypair = from_addr + ":" + prompt_password('Private key:', False)
549 from_addr = keypair.split(':')[0]
550 if not wallet.import_key(keypair,password):
551 print_error("Error: Invalid key pair")
553 wallet.history[from_addr] = interface.retrieve_history(from_addr)
554 wallet.update_tx_history()
555 change_addr = from_addr
557 if options.change_addr:
558 change_addr = options.change_addr
560 for k, v in wallet.labels.items():
563 print_msg("alias", to_address)
565 if change_addr and v == change_addr:
568 tx = wallet.mktx( to_address, amount, label, password,
569 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
572 traceback.print_exc(file=sys.stdout)
575 if tx and cmd=='payto':
576 r, h = wallet.sendtx( tx )
582 wallet.imported_keys.pop(from_addr)
583 del(wallet.history[from_addr])
586 elif cmd == 'sendtx':
588 r, h = wallet.sendtx( tx )
591 elif cmd == 'password':
593 seed = wallet.pw_decode( wallet.seed, password)
595 sys.exit("Error: Password does not decrypt this wallet.")
597 new_password = prompt_password('New password:')
598 wallet.update_password(seed, password, new_password)
600 elif cmd == 'signmessage':
602 print_error("Error: Invalid usage of signmessage.")
603 print_msg(known_commands[cmd])
606 message = ' '.join(args[2:])
608 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
609 print_msg(wallet.sign_message(address, message, password))
611 elif cmd == 'verifymessage':
615 message = ' '.join(args[3:])
617 print_error("Error: Not all parameters were given, displaying help instead.")
618 print_msg(known_commands[cmd])
621 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
623 wallet.verify_message(address, signature, message)
625 except BaseException as e:
626 print_error("Verification error: {0}".format(e))
629 elif cmd == 'freeze':
631 print_msg(wallet.freeze(addr))
633 elif cmd == 'unfreeze':
635 print_msg(wallet.unfreeze(addr))
637 elif cmd == 'prioritize':
639 print_msg(wallet.prioritize(addr))
641 elif cmd == 'unprioritize':
643 print_msg(wallet.unprioritize(addr))