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")
139 return parser.parse_args()
142 if __name__ == '__main__':
144 options, args = parse_args()
145 set_verbosity(options.verbose)
147 # config is an object passed to the various constructors (wallet, interface, gui)
148 if 'ANDROID_DATA' in os.environ:
149 config_options = {'wallet_path':"/sdcard/electrum.dat", 'blockchain_headers_path':'/sdcard/sl4a/e4a-%s'%ELECTRUM_VERSION, 'gui':'android'}
151 config_options = eval(str(options))
152 for k, v in config_options.items():
153 if v is None: config_options.pop(k)
155 config = SimpleConfig(config_options)
156 wallet = Wallet(config)
161 elif len(args)==1 and re.match('^bitcoin:', args[0]):
166 firstarg = args[1] if len(args) > 1 else ''
168 #this entire if/else block is just concerned with importing the
169 #right GUI toolkit based the GUI command line option given
171 pref_gui = config.get('gui','classic')
172 if pref_gui == 'gtk':
174 import lib.gui as gui
176 import electrum.gui as gui
177 elif pref_gui in ['classic', 'qt']:
179 import lib.gui_qt as gui
181 import electrum.gui_qt as gui
182 elif pref_gui == 'lite':
184 import lib.gui_lite as gui
186 import electrum.gui_lite as gui
187 elif pref_gui == 'text':
189 import lib.gui_text as gui
191 import electrum.gui_text as gui
192 elif pref_gui == 'android':
194 import lib.gui_android as gui
196 import electrum.gui_android as gui
198 sys.exit("Error: Unknown GUI: " + pref_gui )
201 interface = Interface(config, True)
202 wallet.interface = interface
204 interface.send([('server.peers.subscribe',[])])
206 gui = gui.ElectrumGui(wallet, config)
208 found = config.wallet_file_exists
210 a = gui.restore_or_create()
214 wallet.new_seed(None)
215 wallet.init_mpk( wallet.seed )
217 # ask for seed and gap.
218 if not gui.seed_dialog(): exit()
219 wallet.init_mpk( wallet.seed )
222 s = gui.network_dialog()
227 interface.register_callback('peers', gui.server_list_changed)
229 verifier = WalletVerifier(interface, config)
230 wallet.set_verifier(verifier)
231 WalletSynchronizer(wallet, config).start()
233 if not found and a == 'restore' and s is not None:
235 ok = gui.restore_wallet()
238 traceback.print_exc(file=sys.stdout)
248 if cmd not in known_commands:
251 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
252 print("Error: Wallet file not found.")
253 print("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
256 if cmd in ['create', 'restore']:
257 if config.wallet_file_exists:
258 sys.exit("Error: Remove the existing wallet first!")
259 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
261 server = config.get('server')
262 if not server: server = pick_random_server()
263 w_host, w_port, w_protocol = server.split(':')
264 host = raw_input("server (default:%s):"%w_host)
265 port = raw_input("port (default:%s):"%w_port)
266 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
267 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
268 gap = raw_input("gap limit (default 5):")
269 if host: w_host = host
270 if port: w_port = port
271 if protocol: w_protocol = protocol
272 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
273 if fee: wallet.fee = float(fee)
274 if gap: wallet.gap_limit = int(gap)
277 seed = raw_input("seed:")
281 print_error("Warning: Not hex, trying decode.")
282 seed = mnemonic_decode( seed.split(' ') )
284 sys.exit("Error: No seed")
286 wallet.seed = str(seed)
287 wallet.init_mpk( wallet.seed )
288 if not options.offline:
290 interface = Interface(config)
292 wallet.interface = interface
294 verifier = WalletVerifier(interface, config)
295 wallet.set_verifier(verifier)
297 print("Recovering wallet...")
298 WalletSynchronizer(wallet, config).start()
299 wallet.up_to_date_event.clear()
300 wallet.up_to_date = False
302 if wallet.is_found():
303 print("Recovery successful")
305 print("Warning: Found no history for this wallet")
308 wallet.fill_addressbook()
310 print("Wallet saved in '%s'"%wallet.config.path)
312 wallet.new_seed(None)
313 wallet.init_mpk( wallet.seed )
314 wallet.synchronize() # there is no wallet thread
316 print("Your wallet generation seed is: " + wallet.seed)
317 print("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
318 print("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:")
319 print("\""+' '.join(mnemonic_encode(wallet.seed))+"\"")
320 print("Wallet saved in '%s'"%wallet.config.path)
323 wallet.update_password(wallet.seed, None, password)
326 if cmd in ['payto', 'mktx']:
329 amount = int( 100000000 * Decimal(args[2]) )
331 label = ' '.join(args[3:])
333 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
339 if cmd not in offline_commands and not options.offline:
340 interface = Interface(config)
341 interface.register_callback('connected', lambda: print_error("Connected to " + interface.connection_msg))
343 wallet.interface = interface
344 WalletSynchronizer(wallet, config).start()
348 # check if --from_addr not in wallet (for mktx/payto)
351 if options.from_addr:
352 from_addr = options.from_addr
353 if from_addr not in wallet.all_addresses():
357 if cmd=='addresses' and options.show_keys:
358 print("WARNING: ALL your private keys are secret.")
359 print("Exposing a single private key can compromise your entire wallet!")
360 print("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
362 # commands needing password
363 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
364 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
367 wallet.pw_decode( wallet.seed, password)
369 print_error("Error: This password does not decode this wallet.")
373 # See if they specificed a key on the cmd line, if not prompt
377 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
379 wallet.import_key(keypair,password)
381 print("Keypair imported")
382 except BaseException(e):
383 print_error("Error: Keypair import failed: " + str(e))
387 if cmd2 not in known_commands:
389 print("Type 'electrum help <command>' to see the help for a specific command")
390 print("Type 'electrum --help' to see the list of options")
391 print("List of commands:", ', '.join(known_commands))
393 print(known_commands[cmd2])
396 seed = wallet.pw_decode( wallet.seed, password)
397 print(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"')
399 elif cmd == 'deseed':
401 print_error("Error: This wallet has no seed")
402 elif wallet.use_encryption:
403 print_error("Error: This wallet is encrypted")
405 ns = wallet.path + '.seed'
406 print("Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns))
407 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
409 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
412 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
416 print_error("Action canceled.")
418 elif cmd == 'reseed':
420 print("Warning: This wallet already has a seed", wallet.seed)
422 ns = wallet.path + '.seed'
428 sys.exit("Error: Seed file not found")
431 d = ast.literal_eval( data )
433 imported_keys = d.get('imported_keys',{})
435 sys.exit("Error: Error with seed file")
437 mpk = wallet.master_public_key
439 wallet.imported_keys = imported_keys
440 wallet.use_encryption = False
441 wallet.init_mpk(seed)
442 if mpk == wallet.master_public_key:
444 print("Done: " + wallet.path)
446 print_error("Error: Master public key does not match")
448 elif cmd == 'validateaddress':
450 print(wallet.is_valid(addr))
452 elif cmd == 'balance':
458 c, u = wallet.get_balance()
460 print(Decimal( c ) / 100000000 , Decimal( u ) / 100000000)
462 print(Decimal( c ) / 100000000)
465 c, u = wallet.get_addr_balance(addr)
467 print("%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000)))
469 print("%s %s" % (addr, str(Decimal(c)/100000000)))
471 elif cmd in [ 'contacts']:
472 for addr in wallet.addressbook:
473 print(addr, " ", wallet.labels.get(addr))
481 print(wallet.config.get(key))
484 key, value = args[1:3]
485 if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
486 wallet.config.set_key(key, value, True)
491 elif cmd in [ 'addresses']:
492 for addr in wallet.all_addresses():
493 if options.show_all or not wallet.is_change(addr):
495 flags = wallet.get_address_flags(addr)
496 label = wallet.labels.get(addr,'')
498 if label: label = "\"%s\""%label
500 if options.show_balance:
501 h = wallet.history.get(addr,[])
504 # if item['is_input']: ni += 1
506 b = format_satoshis(wallet.get_addr_balance(addr)[0])
509 if options.show_keys:
510 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
511 print(flags, m_addr, b, label)
515 for item in wallet.get_tx_history():
516 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
518 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
522 label, is_default_label = wallet.get_label(tx_hash)
523 if not label: label = tx_hash
524 else: label = label + ' '*(64 - len(label) )
526 print("%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance))
527 print("# balance: ", format_satoshis(balance))
532 label = ' '.join(args[2:])
534 print_error("Error. Syntax: label <tx_hash> <text>")
536 wallet.labels[tx] = label
539 elif cmd in ['payto', 'mktx']:
540 if from_addr and is_temporary:
541 if from_addr.find(":") == -1:
542 keypair = from_addr + ":" + prompt_password('Private key:', False)
545 from_addr = keypair.split(':')[0]
546 if not wallet.import_key(keypair,password):
547 print_error("Error: Invalid key pair")
549 wallet.history[from_addr] = interface.retrieve_history(from_addr)
550 wallet.update_tx_history()
551 change_addr = from_addr
553 if options.change_addr:
554 change_addr = options.change_addr
556 for k, v in wallet.labels.items():
559 print("alias", to_address)
561 if change_addr and v == change_addr:
564 tx = wallet.mktx( to_address, amount, label, password,
565 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
568 traceback.print_exc(file=sys.stdout)
571 if tx and cmd=='payto':
572 r, h = wallet.sendtx( tx )
578 wallet.imported_keys.pop(from_addr)
579 del(wallet.history[from_addr])
582 elif cmd == 'sendtx':
584 r, h = wallet.sendtx( tx )
587 elif cmd == 'password':
589 seed = wallet.pw_decode( wallet.seed, password)
591 sys.exit("Error: Password does not decrypt this wallet.")
593 new_password = prompt_password('New password:')
594 wallet.update_password(seed, password, new_password)
596 elif cmd == 'signmessage':
598 print_error("Error: Invalid usage of signmessage.")
599 print(known_commands[cmd])
602 message = ' '.join(args[2:])
604 print("Warning: Message was reconstructed from several arguments:", repr(message))
605 print(wallet.sign_message(address, message, password))
607 elif cmd == 'verifymessage':
611 message = ' '.join(args[3:])
613 print_error("Error: Not all parameters were given, displaying help instead.")
614 print(known_commands[cmd])
617 print("Warning: Message was reconstructed from several arguments:", repr(message))
619 wallet.verify_message(address, signature, message)
621 except BaseException as e:
622 print_error("Verification error: {0}".format(e))
625 elif cmd == 'freeze':
627 print(wallet.freeze(addr))
629 elif cmd == 'unfreeze':
631 print(wallet.unfreeze(addr))
633 elif cmd == 'prioritize':
635 print(wallet.prioritize(addr))
637 elif cmd == 'unprioritize':
639 print(wallet.unprioritize(addr))