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 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()
261 if cmd not in known_commands:
264 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
265 print_msg("Error: Wallet file not found.")
266 print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
269 if cmd in ['create', 'restore']:
270 if config.wallet_file_exists:
271 sys.exit("Error: Remove the existing wallet first!")
272 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
274 server = config.get('server')
275 if not server: server = pick_random_server()
276 w_host, w_port, w_protocol = server.split(':')
277 host = raw_input("server (default:%s):"%w_host)
278 port = raw_input("port (default:%s):"%w_port)
279 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
280 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
281 gap = raw_input("gap limit (default 5):")
282 if host: w_host = host
283 if port: w_port = port
284 if protocol: w_protocol = protocol
285 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
286 if fee: wallet.fee = float(fee)
287 if gap: wallet.gap_limit = int(gap)
290 seed = raw_input("seed:")
294 print_error("Warning: Not hex, trying decode.")
295 seed = mnemonic_decode( seed.split(' ') )
297 sys.exit("Error: No seed")
299 wallet.seed = str(seed)
300 wallet.init_mpk( wallet.seed )
301 if not options.offline:
303 interface = Interface(config)
305 wallet.interface = interface
307 verifier = WalletVerifier(interface, config)
308 wallet.set_verifier(verifier)
310 print_msg("Recovering wallet...")
311 WalletSynchronizer(wallet, config).start()
313 if wallet.is_found():
314 print_msg("Recovery successful")
316 print_msg("Warning: Found no history for this wallet")
319 wallet.fill_addressbook()
321 print_msg("Wallet saved in '%s'"%wallet.config.path)
323 wallet.new_seed(None)
324 wallet.init_mpk( wallet.seed )
325 wallet.synchronize() # there is no wallet thread
327 print_msg("Your wallet generation seed is: " + wallet.seed)
328 print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
329 print_msg("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:")
330 print_msg("\""+' '.join(mnemonic_encode(wallet.seed))+"\"")
331 print_msg("Wallet saved in '%s'"%wallet.config.path)
334 wallet.update_password(wallet.seed, None, password)
337 if cmd in ['payto', 'mktx']:
340 amount = int( 100000000 * Decimal(args[2]) )
342 label = ' '.join(args[3:])
344 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
350 if cmd not in offline_commands and not options.offline:
351 interface = Interface(config)
352 interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n"))
354 wallet.interface = interface
355 synchronizer = WalletSynchronizer(wallet, config)
360 # check if --from_addr not in wallet (for mktx/payto)
363 if options.from_addr:
364 from_addr = options.from_addr
365 if from_addr not in wallet.all_addresses():
369 if cmd=='addresses' and options.show_keys:
370 print_msg("WARNING: ALL your private keys are secret.")
371 print_msg("Exposing a single private key can compromise your entire wallet!")
372 print_msg("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
374 # commands needing password
375 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
376 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
379 wallet.pw_decode( wallet.seed, password)
381 print_msg("Error: This password does not decode this wallet.")
385 # See if they specificed a key on the cmd line, if not prompt
389 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
391 wallet.import_key(keypair,password)
393 print_msg("Keypair imported")
394 except BaseException as e:
395 print_msg("Error: Keypair import failed: " + str(e))
399 if cmd2 not in known_commands:
401 print_msg("Type 'electrum help <command>' to see the help for a specific command")
402 print_msg("Type 'electrum --help' to see the list of options")
403 print_msg("List of commands:", ', '.join(known_commands))
405 print_msg(known_commands[cmd2])
408 seed = wallet.pw_decode( wallet.seed, password)
409 print_msg(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"')
411 elif cmd == 'deseed':
413 print_msg("Error: This wallet has no seed")
414 elif wallet.use_encryption:
415 print_msg("Error: This wallet is encrypted")
417 ns = wallet.config.path + '.seed'
418 print_msg("Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.config.path,ns))
419 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
421 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
424 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
428 print_msg("Action canceled.")
430 elif cmd == 'reseed':
432 print_msg("Warning: This wallet already has a seed", wallet.seed)
434 ns = wallet.config.path + '.seed'
440 sys.exit("Error: Seed file not found")
443 d = ast.literal_eval( data )
445 imported_keys = d.get('imported_keys',{})
447 sys.exit("Error: Error with seed file")
449 mpk = wallet.master_public_key
451 wallet.imported_keys = imported_keys
452 wallet.use_encryption = False
453 wallet.init_mpk(seed)
454 if mpk == wallet.master_public_key:
456 print_msg("Done: " + wallet.config.path)
458 print_msg("Error: Master public key does not match")
460 elif cmd == 'validateaddress':
462 print_msg(wallet.is_valid(addr))
464 elif cmd == 'balance':
470 c, u = wallet.get_balance()
472 print_msg(Decimal( c ) / 100000000 , Decimal( u ) / 100000000)
474 print_msg(Decimal( c ) / 100000000)
477 c, u = wallet.get_addr_balance(addr)
479 print_msg("%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000)))
481 print_msg("%s %s" % (addr, str(Decimal(c)/100000000)))
483 elif cmd in [ 'contacts']:
484 for addr in wallet.addressbook:
485 print_msg(addr, " ", wallet.labels.get(addr))
488 print_msg(eval(args[1]))
493 print_msg(wallet.config.get(key))
496 key, value = args[1:3]
497 if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
498 wallet.config.set_key(key, value, True)
503 elif cmd in [ 'addresses']:
504 for addr in wallet.all_addresses():
505 if options.show_all or not wallet.is_change(addr):
507 flags = wallet.get_address_flags(addr)
508 label = wallet.labels.get(addr,'')
510 if label: label = "\"%s\""%label
512 if options.show_balance:
513 h = wallet.history.get(addr,[])
516 # if item['is_input']: ni += 1
518 b = format_satoshis(wallet.get_addr_balance(addr)[0])
521 if options.show_keys:
522 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
523 print_msg(flags, m_addr, b, label)
527 for item in wallet.get_tx_history():
528 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
530 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
534 label, is_default_label = wallet.get_label(tx_hash)
535 if not label: label = tx_hash
536 else: label = label + ' '*(64 - len(label) )
538 print_msg("%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance))
539 print_msg("# balance: ", format_satoshis(balance))
544 label = ' '.join(args[2:])
546 print_msg("Error. Syntax: label <tx_hash> <text>")
548 wallet.labels[tx] = label
551 elif cmd in ['payto', 'mktx']:
552 if from_addr and is_temporary:
553 if from_addr.find(":") == -1:
554 keypair = from_addr + ":" + prompt_password('Private key:', False)
557 from_addr = keypair.split(':')[0]
558 if not wallet.import_key(keypair,password):
559 print_msg("Error: Invalid key pair")
561 wallet.history[from_addr] = interface.retrieve_history(from_addr)
562 wallet.update_tx_history()
563 change_addr = from_addr
565 if options.change_addr:
566 change_addr = options.change_addr
568 for k, v in wallet.labels.items():
571 print_msg("alias", to_address)
573 if change_addr and v == change_addr:
576 tx = wallet.mktx( to_address, amount, label, password,
577 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
580 traceback.print_exc(file=sys.stdout)
583 if tx and cmd=='payto':
584 r, h = wallet.sendtx( tx )
590 wallet.imported_keys.pop(from_addr)
591 del(wallet.history[from_addr])
594 elif cmd == 'sendtx':
596 r, h = wallet.sendtx( tx )
599 elif cmd == 'password':
601 seed = wallet.pw_decode( wallet.seed, password)
603 sys.exit("Error: Password does not decrypt this wallet.")
605 new_password = prompt_password('New password:')
606 wallet.update_password(seed, password, new_password)
608 elif cmd == 'signmessage':
610 print_msg("Error: Invalid usage of signmessage.")
611 print_msg(known_commands[cmd])
614 message = ' '.join(args[2:])
616 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
617 print_msg(wallet.sign_message(address, message, password))
619 elif cmd == 'verifymessage':
623 message = ' '.join(args[3:])
625 print_msg("Error: Not all parameters were given, displaying help instead.")
626 print_msg(known_commands[cmd])
629 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
631 wallet.verify_message(address, signature, message)
633 except BaseException as e:
634 print_error("Verification error: {0}".format(e))
637 elif cmd == 'freeze':
639 print_msg(wallet.freeze(addr))
641 elif cmd == 'unfreeze':
643 print_msg(wallet.unfreeze(addr))
645 elif cmd == 'prioritize':
647 print_msg(wallet.prioritize(addr))
649 elif cmd == 'unprioritize':
651 print_msg(wallet.unprioritize(addr))
654 if cmd not in offline_commands and not options.offline: