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()
213 s = gui.network_dialog()
216 wallet.new_seed(None)
217 wallet.init_mpk( wallet.seed )
219 # ask for seed and gap.
220 if not gui.seed_dialog(): exit()
221 wallet.init_mpk( wallet.seed )
223 # generate the first addresses
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 keep_it = gui.restore_wallet()
236 wallet.fill_addressbook()
239 traceback.print_exc(file=sys.stdout)
242 if not keep_it: exit()
245 gui.password_dialog()
253 if cmd not in known_commands:
256 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
257 print("Error: Wallet file not found.")
258 print("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
261 if cmd in ['create', 'restore']:
262 if config.wallet_file_exists:
263 sys.exit("Error: Remove the existing wallet first!")
264 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
266 server = config.get('server')
267 if not server: server = pick_random_server()
268 w_host, w_port, w_protocol = server.split(':')
269 host = raw_input("server (default:%s):"%w_host)
270 port = raw_input("port (default:%s):"%w_port)
271 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
272 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
273 gap = raw_input("gap limit (default 5):")
274 if host: w_host = host
275 if port: w_port = port
276 if protocol: w_protocol = protocol
277 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
278 if fee: wallet.fee = float(fee)
279 if gap: wallet.gap_limit = int(gap)
282 seed = raw_input("seed:")
286 print_error("Warning: Not hex, trying decode.")
287 seed = mnemonic_decode( seed.split(' ') )
289 sys.exit("Error: No seed")
291 wallet.seed = str(seed)
292 wallet.init_mpk( wallet.seed )
293 if not options.offline:
295 interface = Interface(config)
297 wallet.interface = interface
299 verifier = WalletVerifier(interface, config)
300 wallet.set_verifier(verifier)
302 print("Recovering wallet...")
303 WalletSynchronizer(wallet, config).start()
305 if wallet.is_found():
306 print("Recovery successful")
308 print("Warning: Found no history for this wallet")
311 wallet.fill_addressbook()
313 print("Wallet saved in '%s'"%wallet.config.path)
315 wallet.new_seed(None)
316 wallet.init_mpk( wallet.seed )
317 wallet.synchronize() # there is no wallet thread
319 print("Your wallet generation seed is: " + wallet.seed)
320 print("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
321 print("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:")
322 print("\""+' '.join(mnemonic_encode(wallet.seed))+"\"")
323 print("Wallet saved in '%s'"%wallet.config.path)
326 wallet.update_password(wallet.seed, None, password)
329 if cmd in ['payto', 'mktx']:
332 amount = int( 100000000 * Decimal(args[2]) )
334 label = ' '.join(args[3:])
336 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
342 if cmd not in offline_commands and not options.offline:
343 interface = Interface(config)
344 interface.register_callback('connected', lambda: print_error("Connected to " + interface.connection_msg))
346 wallet.interface = interface
347 WalletSynchronizer(wallet, config).start()
351 # check if --from_addr not in wallet (for mktx/payto)
354 if options.from_addr:
355 from_addr = options.from_addr
356 if from_addr not in wallet.all_addresses():
360 if cmd=='addresses' and options.show_keys:
361 print("WARNING: ALL your private keys are secret.")
362 print("Exposing a single private key can compromise your entire wallet!")
363 print("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
365 # commands needing password
366 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
367 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
370 wallet.pw_decode( wallet.seed, password)
372 print_error("Error: This password does not decode this wallet.")
376 # See if they specificed a key on the cmd line, if not prompt
380 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
382 wallet.import_key(keypair,password)
384 print("Keypair imported")
385 except BaseException(e):
386 print_error("Error: Keypair import failed: " + str(e))
390 if cmd2 not in known_commands:
392 print("Type 'electrum help <command>' to see the help for a specific command")
393 print("Type 'electrum --help' to see the list of options")
394 print("List of commands:", ', '.join(known_commands))
396 print(known_commands[cmd2])
399 seed = wallet.pw_decode( wallet.seed, password)
400 print(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"')
402 elif cmd == 'deseed':
404 print_error("Error: This wallet has no seed")
405 elif wallet.use_encryption:
406 print_error("Error: This wallet is encrypted")
408 ns = wallet.config.path + '.seed'
409 print("Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.config.path,ns))
410 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
412 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
415 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
419 print_error("Action canceled.")
421 elif cmd == 'reseed':
423 print("Warning: This wallet already has a seed", wallet.seed)
425 ns = wallet.config.path + '.seed'
431 sys.exit("Error: Seed file not found")
434 d = ast.literal_eval( data )
436 imported_keys = d.get('imported_keys',{})
438 sys.exit("Error: Error with seed file")
440 mpk = wallet.master_public_key
442 wallet.imported_keys = imported_keys
443 wallet.use_encryption = False
444 wallet.init_mpk(seed)
445 if mpk == wallet.master_public_key:
447 print("Done: " + wallet.config.path)
449 print_error("Error: Master public key does not match")
451 elif cmd == 'validateaddress':
453 print(wallet.is_valid(addr))
455 elif cmd == 'balance':
461 c, u = wallet.get_balance()
463 print(Decimal( c ) / 100000000 , Decimal( u ) / 100000000)
465 print(Decimal( c ) / 100000000)
468 c, u = wallet.get_addr_balance(addr)
470 print("%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000)))
472 print("%s %s" % (addr, str(Decimal(c)/100000000)))
474 elif cmd in [ 'contacts']:
475 for addr in wallet.addressbook:
476 print(addr, " ", wallet.labels.get(addr))
484 print(wallet.config.get(key))
487 key, value = args[1:3]
488 if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
489 wallet.config.set_key(key, value, True)
494 elif cmd in [ 'addresses']:
495 for addr in wallet.all_addresses():
496 if options.show_all or not wallet.is_change(addr):
498 flags = wallet.get_address_flags(addr)
499 label = wallet.labels.get(addr,'')
501 if label: label = "\"%s\""%label
503 if options.show_balance:
504 h = wallet.history.get(addr,[])
507 # if item['is_input']: ni += 1
509 b = format_satoshis(wallet.get_addr_balance(addr)[0])
512 if options.show_keys:
513 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
514 print(flags, m_addr, b, label)
518 for item in wallet.get_tx_history():
519 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
521 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
525 label, is_default_label = wallet.get_label(tx_hash)
526 if not label: label = tx_hash
527 else: label = label + ' '*(64 - len(label) )
529 print("%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance))
530 print("# balance: ", format_satoshis(balance))
535 label = ' '.join(args[2:])
537 print_error("Error. Syntax: label <tx_hash> <text>")
539 wallet.labels[tx] = label
542 elif cmd in ['payto', 'mktx']:
543 if from_addr and is_temporary:
544 if from_addr.find(":") == -1:
545 keypair = from_addr + ":" + prompt_password('Private key:', False)
548 from_addr = keypair.split(':')[0]
549 if not wallet.import_key(keypair,password):
550 print_error("Error: Invalid key pair")
552 wallet.history[from_addr] = interface.retrieve_history(from_addr)
553 wallet.update_tx_history()
554 change_addr = from_addr
556 if options.change_addr:
557 change_addr = options.change_addr
559 for k, v in wallet.labels.items():
562 print("alias", to_address)
564 if change_addr and v == change_addr:
567 tx = wallet.mktx( to_address, amount, label, password,
568 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
571 traceback.print_exc(file=sys.stdout)
574 if tx and cmd=='payto':
575 r, h = wallet.sendtx( tx )
581 wallet.imported_keys.pop(from_addr)
582 del(wallet.history[from_addr])
585 elif cmd == 'sendtx':
587 r, h = wallet.sendtx( tx )
590 elif cmd == 'password':
592 seed = wallet.pw_decode( wallet.seed, password)
594 sys.exit("Error: Password does not decrypt this wallet.")
596 new_password = prompt_password('New password:')
597 wallet.update_password(seed, password, new_password)
599 elif cmd == 'signmessage':
601 print_error("Error: Invalid usage of signmessage.")
602 print(known_commands[cmd])
605 message = ' '.join(args[2:])
607 print("Warning: Message was reconstructed from several arguments:", repr(message))
608 print(wallet.sign_message(address, message, password))
610 elif cmd == 'verifymessage':
614 message = ' '.join(args[3:])
616 print_error("Error: Not all parameters were given, displaying help instead.")
617 print(known_commands[cmd])
620 print("Warning: Message was reconstructed from several arguments:", repr(message))
622 wallet.verify_message(address, signature, message)
624 except BaseException as e:
625 print_error("Verification error: {0}".format(e))
628 elif cmd == 'freeze':
630 print(wallet.freeze(addr))
632 elif cmd == 'unfreeze':
634 print(wallet.unfreeze(addr))
636 elif cmd == 'prioritize':
638 print(wallet.prioritize(addr))
640 elif cmd == 'unprioritize':
642 print(wallet.unprioritize(addr))