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()
255 if cmd not in known_commands:
258 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
259 print_msg("Error: Wallet file not found.")
260 print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
263 if cmd in ['create', 'restore']:
264 if config.wallet_file_exists:
265 sys.exit("Error: Remove the existing wallet first!")
266 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
268 server = config.get('server')
269 if not server: server = pick_random_server()
270 w_host, w_port, w_protocol = server.split(':')
271 host = raw_input("server (default:%s):"%w_host)
272 port = raw_input("port (default:%s):"%w_port)
273 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
274 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
275 gap = raw_input("gap limit (default 5):")
276 if host: w_host = host
277 if port: w_port = port
278 if protocol: w_protocol = protocol
279 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
280 if fee: wallet.fee = float(fee)
281 if gap: wallet.gap_limit = int(gap)
284 seed = raw_input("seed:")
288 print_error("Warning: Not hex, trying decode.")
289 seed = mnemonic_decode( seed.split(' ') )
291 sys.exit("Error: No seed")
293 wallet.seed = str(seed)
294 wallet.init_mpk( wallet.seed )
295 if not options.offline:
297 interface = Interface(config)
299 wallet.interface = interface
301 verifier = WalletVerifier(interface, config)
302 wallet.set_verifier(verifier)
304 print_msg("Recovering wallet...")
305 WalletSynchronizer(wallet, config).start()
307 if wallet.is_found():
308 print_msg("Recovery successful")
310 print_msg("Warning: Found no history for this wallet")
313 wallet.fill_addressbook()
315 print_msg("Wallet saved in '%s'"%wallet.config.path)
317 wallet.new_seed(None)
318 wallet.init_mpk( wallet.seed )
319 wallet.synchronize() # there is no wallet thread
321 print_msg("Your wallet generation seed is: " + wallet.seed)
322 print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
323 print_msg("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:")
324 print_msg("\""+' '.join(mnemonic_encode(wallet.seed))+"\"")
325 print_msg("Wallet saved in '%s'"%wallet.config.path)
328 wallet.update_password(wallet.seed, None, password)
331 if cmd in ['payto', 'mktx']:
334 amount = int( 100000000 * Decimal(args[2]) )
336 label = ' '.join(args[3:])
338 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
344 if cmd not in offline_commands and not options.offline:
345 interface = Interface(config)
346 interface.register_callback('connected', lambda: print_error("Connected to " + interface.connection_msg))
348 wallet.interface = interface
349 WalletSynchronizer(wallet, config).start()
353 # check if --from_addr not in wallet (for mktx/payto)
356 if options.from_addr:
357 from_addr = options.from_addr
358 if from_addr not in wallet.all_addresses():
362 if cmd=='addresses' and options.show_keys:
363 print_msg("WARNING: ALL your private keys are secret.")
364 print_msg("Exposing a single private key can compromise your entire wallet!")
365 print_msg("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
367 # commands needing password
368 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
369 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
372 wallet.pw_decode( wallet.seed, password)
374 print_error("Error: This password does not decode this wallet.")
378 # See if they specificed a key on the cmd line, if not prompt
382 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
384 wallet.import_key(keypair,password)
386 print_msg("Keypair imported")
387 except BaseException(e):
388 print_error("Error: Keypair import failed: " + str(e))
392 if cmd2 not in known_commands:
394 print_msg("Type 'electrum help <command>' to see the help for a specific command")
395 print_msg("Type 'electrum --help' to see the list of options")
396 print_msg("List of commands:", ', '.join(known_commands))
398 print_msg(known_commands[cmd2])
401 seed = wallet.pw_decode( wallet.seed, password)
402 print_msg(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"')
404 elif cmd == 'deseed':
406 print_error("Error: This wallet has no seed")
407 elif wallet.use_encryption:
408 print_error("Error: This wallet is encrypted")
410 ns = wallet.config.path + '.seed'
411 print_msg("Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.config.path,ns))
412 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
414 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
417 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
421 print_error("Action canceled.")
423 elif cmd == 'reseed':
425 print_msg("Warning: This wallet already has a seed", wallet.seed)
427 ns = wallet.config.path + '.seed'
433 sys.exit("Error: Seed file not found")
436 d = ast.literal_eval( data )
438 imported_keys = d.get('imported_keys',{})
440 sys.exit("Error: Error with seed file")
442 mpk = wallet.master_public_key
444 wallet.imported_keys = imported_keys
445 wallet.use_encryption = False
446 wallet.init_mpk(seed)
447 if mpk == wallet.master_public_key:
449 print_msg("Done: " + wallet.config.path)
451 print_error("Error: Master public key does not match")
453 elif cmd == 'validateaddress':
455 print_msg(wallet.is_valid(addr))
457 elif cmd == 'balance':
463 c, u = wallet.get_balance()
465 print_msg(Decimal( c ) / 100000000 , Decimal( u ) / 100000000)
467 print_msg(Decimal( c ) / 100000000)
470 c, u = wallet.get_addr_balance(addr)
472 print_msg("%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000)))
474 print_msg("%s %s" % (addr, str(Decimal(c)/100000000)))
476 elif cmd in [ 'contacts']:
477 for addr in wallet.addressbook:
478 print_msg(addr, " ", wallet.labels.get(addr))
481 print_msg(eval(args[1]))
486 print_msg(wallet.config.get(key))
489 key, value = args[1:3]
490 if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
491 wallet.config.set_key(key, value, True)
496 elif cmd in [ 'addresses']:
497 for addr in wallet.all_addresses():
498 if options.show_all or not wallet.is_change(addr):
500 flags = wallet.get_address_flags(addr)
501 label = wallet.labels.get(addr,'')
503 if label: label = "\"%s\""%label
505 if options.show_balance:
506 h = wallet.history.get(addr,[])
509 # if item['is_input']: ni += 1
511 b = format_satoshis(wallet.get_addr_balance(addr)[0])
514 if options.show_keys:
515 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
516 print_msg(flags, m_addr, b, label)
520 for item in wallet.get_tx_history():
521 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
523 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
527 label, is_default_label = wallet.get_label(tx_hash)
528 if not label: label = tx_hash
529 else: label = label + ' '*(64 - len(label) )
531 print_msg("%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance))
532 print_msg("# balance: ", format_satoshis(balance))
537 label = ' '.join(args[2:])
539 print_error("Error. Syntax: label <tx_hash> <text>")
541 wallet.labels[tx] = label
544 elif cmd in ['payto', 'mktx']:
545 if from_addr and is_temporary:
546 if from_addr.find(":") == -1:
547 keypair = from_addr + ":" + prompt_password('Private key:', False)
550 from_addr = keypair.split(':')[0]
551 if not wallet.import_key(keypair,password):
552 print_error("Error: Invalid key pair")
554 wallet.history[from_addr] = interface.retrieve_history(from_addr)
555 wallet.update_tx_history()
556 change_addr = from_addr
558 if options.change_addr:
559 change_addr = options.change_addr
561 for k, v in wallet.labels.items():
564 print_msg("alias", to_address)
566 if change_addr and v == change_addr:
569 tx = wallet.mktx( to_address, amount, label, password,
570 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
573 traceback.print_exc(file=sys.stdout)
576 if tx and cmd=='payto':
577 r, h = wallet.sendtx( tx )
583 wallet.imported_keys.pop(from_addr)
584 del(wallet.history[from_addr])
587 elif cmd == 'sendtx':
589 r, h = wallet.sendtx( tx )
592 elif cmd == 'password':
594 seed = wallet.pw_decode( wallet.seed, password)
596 sys.exit("Error: Password does not decrypt this wallet.")
598 new_password = prompt_password('New password:')
599 wallet.update_password(seed, password, new_password)
601 elif cmd == 'signmessage':
603 print_error("Error: Invalid usage of signmessage.")
604 print_msg(known_commands[cmd])
607 message = ' '.join(args[2:])
609 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
610 print_msg(wallet.sign_message(address, message, password))
612 elif cmd == 'verifymessage':
616 message = ' '.join(args[3:])
618 print_error("Error: Not all parameters were given, displaying help instead.")
619 print_msg(known_commands[cmd])
622 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
624 wallet.verify_message(address, signature, message)
626 except BaseException as e:
627 print_error("Verification error: {0}".format(e))
630 elif cmd == 'freeze':
632 print_msg(wallet.freeze(addr))
634 elif cmd == 'unfreeze':
636 print_msg(wallet.unfreeze(addr))
638 elif cmd == 'prioritize':
640 print_msg(wallet.prioritize(addr))
642 elif cmd == 'unprioritize':
644 print_msg(wallet.unprioritize(addr))