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, in case we are offline
225 if s is None or a == 'create':
231 verifier = WalletVerifier(interface, config)
232 wallet.set_verifier(verifier)
233 WalletSynchronizer(wallet, config).start()
235 if not found and a == 'restore' and s is not None:
237 keep_it = gui.restore_wallet()
238 wallet.fill_addressbook()
241 traceback.print_exc(file=sys.stdout)
244 if not keep_it: exit()
247 gui.password_dialog()
258 if cmd not in known_commands:
261 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
262 print_msg("Error: Wallet file not found.")
263 print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
266 if cmd in ['create', 'restore']:
267 if config.wallet_file_exists:
268 sys.exit("Error: Remove the existing wallet first!")
269 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
271 server = config.get('server')
272 if not server: server = pick_random_server()
273 w_host, w_port, w_protocol = server.split(':')
274 host = raw_input("server (default:%s):"%w_host)
275 port = raw_input("port (default:%s):"%w_port)
276 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
277 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
278 gap = raw_input("gap limit (default 5):")
279 if host: w_host = host
280 if port: w_port = port
281 if protocol: w_protocol = protocol
282 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
283 if fee: wallet.fee = float(fee)
284 if gap: wallet.gap_limit = int(gap)
287 seed = raw_input("seed:")
291 print_error("Warning: Not hex, trying decode.")
292 seed = mnemonic_decode( seed.split(' ') )
294 sys.exit("Error: No seed")
296 wallet.seed = str(seed)
297 wallet.init_mpk( wallet.seed )
298 if not options.offline:
300 interface = Interface(config)
302 wallet.interface = interface
304 verifier = WalletVerifier(interface, config)
305 wallet.set_verifier(verifier)
307 print_msg("Recovering wallet...")
308 WalletSynchronizer(wallet, config).start()
310 if wallet.is_found():
311 print_msg("Recovery successful")
313 print_msg("Warning: Found no history for this wallet")
316 wallet.fill_addressbook()
318 print_msg("Wallet saved in '%s'"%wallet.config.path)
320 wallet.new_seed(None)
321 wallet.init_mpk( wallet.seed )
322 wallet.synchronize() # there is no wallet thread
324 print_msg("Your wallet generation seed is: " + wallet.seed)
325 print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
326 print_msg("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:")
327 print_msg("\""+' '.join(mnemonic_encode(wallet.seed))+"\"")
328 print_msg("Wallet saved in '%s'"%wallet.config.path)
331 wallet.update_password(wallet.seed, None, password)
334 if cmd in ['payto', 'mktx']:
337 amount = int( 100000000 * Decimal(args[2]) )
339 label = ' '.join(args[3:])
341 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
347 if cmd not in offline_commands and not options.offline:
348 interface = Interface(config)
349 interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n"))
351 wallet.interface = interface
352 synchronizer = WalletSynchronizer(wallet, config)
357 # check if --from_addr not in wallet (for mktx/payto)
360 if options.from_addr:
361 from_addr = options.from_addr
362 if from_addr not in wallet.all_addresses():
366 if cmd=='addresses' and options.show_keys:
367 print_msg("WARNING: ALL your private keys are secret.")
368 print_msg("Exposing a single private key can compromise your entire wallet!")
369 print_msg("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
371 # commands needing password
372 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
373 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
376 wallet.pw_decode( wallet.seed, password)
378 print_error("Error: This password does not decode this wallet.")
382 # See if they specificed a key on the cmd line, if not prompt
386 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
388 wallet.import_key(keypair,password)
390 print_msg("Keypair imported")
391 except BaseException(e):
392 print_error("Error: Keypair import failed: " + str(e))
396 if cmd2 not in known_commands:
398 print_msg("Type 'electrum help <command>' to see the help for a specific command")
399 print_msg("Type 'electrum --help' to see the list of options")
400 print_msg("List of commands:", ', '.join(known_commands))
402 print_msg(known_commands[cmd2])
405 seed = wallet.pw_decode( wallet.seed, password)
406 print_msg(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"')
408 elif cmd == 'deseed':
410 print_error("Error: This wallet has no seed")
411 elif wallet.use_encryption:
412 print_error("Error: This wallet is encrypted")
414 ns = wallet.config.path + '.seed'
415 print_msg("Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.config.path,ns))
416 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
418 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
421 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
425 print_error("Action canceled.")
427 elif cmd == 'reseed':
429 print_msg("Warning: This wallet already has a seed", wallet.seed)
431 ns = wallet.config.path + '.seed'
437 sys.exit("Error: Seed file not found")
440 d = ast.literal_eval( data )
442 imported_keys = d.get('imported_keys',{})
444 sys.exit("Error: Error with seed file")
446 mpk = wallet.master_public_key
448 wallet.imported_keys = imported_keys
449 wallet.use_encryption = False
450 wallet.init_mpk(seed)
451 if mpk == wallet.master_public_key:
453 print_msg("Done: " + wallet.config.path)
455 print_error("Error: Master public key does not match")
457 elif cmd == 'validateaddress':
459 print_msg(wallet.is_valid(addr))
461 elif cmd == 'balance':
467 c, u = wallet.get_balance()
469 print_msg(Decimal( c ) / 100000000 , Decimal( u ) / 100000000)
471 print_msg(Decimal( c ) / 100000000)
474 c, u = wallet.get_addr_balance(addr)
476 print_msg("%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000)))
478 print_msg("%s %s" % (addr, str(Decimal(c)/100000000)))
480 elif cmd in [ 'contacts']:
481 for addr in wallet.addressbook:
482 print_msg(addr, " ", wallet.labels.get(addr))
485 print_msg(eval(args[1]))
490 print_msg(wallet.config.get(key))
493 key, value = args[1:3]
494 if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
495 wallet.config.set_key(key, value, True)
500 elif cmd in [ 'addresses']:
501 for addr in wallet.all_addresses():
502 if options.show_all or not wallet.is_change(addr):
504 flags = wallet.get_address_flags(addr)
505 label = wallet.labels.get(addr,'')
507 if label: label = "\"%s\""%label
509 if options.show_balance:
510 h = wallet.history.get(addr,[])
513 # if item['is_input']: ni += 1
515 b = format_satoshis(wallet.get_addr_balance(addr)[0])
518 if options.show_keys:
519 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
520 print_msg(flags, m_addr, b, label)
524 for item in wallet.get_tx_history():
525 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
527 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
531 label, is_default_label = wallet.get_label(tx_hash)
532 if not label: label = tx_hash
533 else: label = label + ' '*(64 - len(label) )
535 print_msg("%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance))
536 print_msg("# balance: ", format_satoshis(balance))
541 label = ' '.join(args[2:])
543 print_error("Error. Syntax: label <tx_hash> <text>")
545 wallet.labels[tx] = label
548 elif cmd in ['payto', 'mktx']:
549 if from_addr and is_temporary:
550 if from_addr.find(":") == -1:
551 keypair = from_addr + ":" + prompt_password('Private key:', False)
554 from_addr = keypair.split(':')[0]
555 if not wallet.import_key(keypair,password):
556 print_error("Error: Invalid key pair")
558 wallet.history[from_addr] = interface.retrieve_history(from_addr)
559 wallet.update_tx_history()
560 change_addr = from_addr
562 if options.change_addr:
563 change_addr = options.change_addr
565 for k, v in wallet.labels.items():
568 print_msg("alias", to_address)
570 if change_addr and v == change_addr:
573 tx = wallet.mktx( to_address, amount, label, password,
574 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
577 traceback.print_exc(file=sys.stdout)
580 if tx and cmd=='payto':
581 r, h = wallet.sendtx( tx )
587 wallet.imported_keys.pop(from_addr)
588 del(wallet.history[from_addr])
591 elif cmd == 'sendtx':
593 r, h = wallet.sendtx( tx )
596 elif cmd == 'password':
598 seed = wallet.pw_decode( wallet.seed, password)
600 sys.exit("Error: Password does not decrypt this wallet.")
602 new_password = prompt_password('New password:')
603 wallet.update_password(seed, password, new_password)
605 elif cmd == 'signmessage':
607 print_error("Error: Invalid usage of signmessage.")
608 print_msg(known_commands[cmd])
611 message = ' '.join(args[2:])
613 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
614 print_msg(wallet.sign_message(address, message, password))
616 elif cmd == 'verifymessage':
620 message = ' '.join(args[3:])
622 print_error("Error: Not all parameters were given, displaying help instead.")
623 print_msg(known_commands[cmd])
626 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
628 wallet.verify_message(address, signature, message)
630 except BaseException as e:
631 print_error("Verification error: {0}".format(e))
634 elif cmd == 'freeze':
636 print_msg(wallet.freeze(addr))
638 elif cmd == 'unfreeze':
640 print_msg(wallet.unfreeze(addr))
642 elif cmd == 'prioritize':
644 print_msg(wallet.prioritize(addr))
646 elif cmd == 'unprioritize':
648 print_msg(wallet.unprioritize(addr))
651 if cmd not in offline_commands and not options.offline: