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 verifier = WalletVerifier(interface, config)
228 wallet.set_verifier(verifier)
229 WalletSynchronizer(wallet, config).start()
231 if not found and a == 'restore' and s is not None:
233 ok = gui.restore_wallet()
236 traceback.print_exc(file=sys.stdout)
246 if cmd not in known_commands:
249 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
250 print("Error: Wallet file not found.")
251 print("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
254 if cmd in ['create', 'restore']:
255 if config.wallet_file_exists:
256 sys.exit("Error: Remove the existing wallet first!")
257 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
259 server = config.get('server')
260 if not server: server = pick_random_server()
261 w_host, w_port, w_protocol = server.split(':')
262 host = raw_input("server (default:%s):"%w_host)
263 port = raw_input("port (default:%s):"%w_port)
264 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
265 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
266 gap = raw_input("gap limit (default 5):")
267 if host: w_host = host
268 if port: w_port = port
269 if protocol: w_protocol = protocol
270 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
271 if fee: wallet.fee = float(fee)
272 if gap: wallet.gap_limit = int(gap)
275 seed = raw_input("seed:")
279 print_error("Warning: Not hex, trying decode.")
280 seed = mnemonic_decode( seed.split(' ') )
282 sys.exit("Error: No seed")
284 wallet.seed = str(seed)
285 wallet.init_mpk( wallet.seed )
286 if not options.offline:
288 interface = Interface(config)
290 wallet.interface = interface
292 verifier = WalletVerifier(interface, config)
293 wallet.set_verifier(verifier)
295 print("Recovering wallet...")
296 WalletSynchronizer(wallet, config).start()
297 wallet.up_to_date_event.clear()
298 wallet.up_to_date = False
300 if wallet.is_found():
301 print("Recovery successful")
303 print("Warning: Found no history for this wallet")
306 wallet.fill_addressbook()
308 print("Wallet saved in '%s'"%wallet.config.path)
310 wallet.new_seed(None)
311 wallet.init_mpk( wallet.seed )
312 wallet.synchronize() # there is no wallet thread
314 print("Your wallet generation seed is: " + wallet.seed)
315 print("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
316 print("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:")
317 print("\""+' '.join(mnemonic_encode(wallet.seed))+"\"")
318 print("Wallet saved in '%s'"%wallet.config.path)
321 wallet.update_password(wallet.seed, None, password)
324 if cmd in ['payto', 'mktx']:
327 amount = int( 100000000 * Decimal(args[2]) )
329 label = ' '.join(args[3:])
331 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
337 if cmd not in offline_commands and not options.offline:
338 interface = Interface(config)
339 interface.register_callback('connected', lambda: print_error("Connected to " + interface.connection_msg))
341 wallet.interface = interface
342 WalletSynchronizer(wallet, config).start()
346 # check if --from_addr not in wallet (for mktx/payto)
349 if options.from_addr:
350 from_addr = options.from_addr
351 if from_addr not in wallet.all_addresses():
355 if cmd=='addresses' and options.show_keys:
356 print("WARNING: ALL your private keys are secret.")
357 print("Exposing a single private key can compromise your entire wallet!")
358 print("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
360 # commands needing password
361 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
362 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
365 wallet.pw_decode( wallet.seed, password)
367 print_error("Error: This password does not decode this wallet.")
371 # See if they specificed a key on the cmd line, if not prompt
375 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
377 wallet.import_key(keypair,password)
379 print("Keypair imported")
380 except BaseException(e):
381 print_error("Error: Keypair import failed: " + str(e))
385 if cmd2 not in known_commands:
387 print("Type 'electrum help <command>' to see the help for a specific command")
388 print("Type 'electrum --help' to see the list of options")
389 print("List of commands:", ', '.join(known_commands))
391 print(known_commands[cmd2])
394 seed = wallet.pw_decode( wallet.seed, password)
395 print(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"')
397 elif cmd == 'deseed':
399 print_error("Error: This wallet has no seed")
400 elif wallet.use_encryption:
401 print_error("Error: This wallet is encrypted")
403 ns = wallet.path + '.seed'
404 print("Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns))
405 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
407 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
410 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
414 print_error("Action canceled.")
416 elif cmd == 'reseed':
418 print("Warning: This wallet already has a seed", wallet.seed)
420 ns = wallet.path + '.seed'
426 sys.exit("Error: Seed file not found")
429 d = ast.literal_eval( data )
431 imported_keys = d.get('imported_keys',{})
433 sys.exit("Error: Error with seed file")
435 mpk = wallet.master_public_key
437 wallet.imported_keys = imported_keys
438 wallet.use_encryption = False
439 wallet.init_mpk(seed)
440 if mpk == wallet.master_public_key:
442 print("Done: " + wallet.path)
444 print_error("Error: Master public key does not match")
446 elif cmd == 'validateaddress':
448 print(wallet.is_valid(addr))
450 elif cmd == 'balance':
456 c, u = wallet.get_balance()
458 print(Decimal( c ) / 100000000 , Decimal( u ) / 100000000)
460 print(Decimal( c ) / 100000000)
463 c, u = wallet.get_addr_balance(addr)
465 print("%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000)))
467 print("%s %s" % (addr, str(Decimal(c)/100000000)))
469 elif cmd in [ 'contacts']:
470 for addr in wallet.addressbook:
471 print(addr, " ", wallet.labels.get(addr))
479 print(wallet.config.get(key))
482 key, value = args[1:3]
483 if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
484 wallet.config.set_key(key, value, True)
489 elif cmd in [ 'addresses']:
490 for addr in wallet.all_addresses():
491 if options.show_all or not wallet.is_change(addr):
493 flags = wallet.get_address_flags(addr)
494 label = wallet.labels.get(addr,'')
496 if label: label = "\"%s\""%label
498 if options.show_balance:
499 h = wallet.history.get(addr,[])
502 # if item['is_input']: ni += 1
504 b = format_satoshis(wallet.get_addr_balance(addr)[0])
507 if options.show_keys:
508 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
509 print(flags, m_addr, b, label)
513 for item in wallet.get_tx_history():
514 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
516 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
520 label, is_default_label = wallet.get_label(tx_hash)
521 if not label: label = tx_hash
522 else: label = label + ' '*(64 - len(label) )
524 print("%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance))
525 print("# balance: ", format_satoshis(balance))
530 label = ' '.join(args[2:])
532 print_error("Error. Syntax: label <tx_hash> <text>")
534 wallet.labels[tx] = label
537 elif cmd in ['payto', 'mktx']:
538 if from_addr and is_temporary:
539 if from_addr.find(":") == -1:
540 keypair = from_addr + ":" + prompt_password('Private key:', False)
543 from_addr = keypair.split(':')[0]
544 if not wallet.import_key(keypair,password):
545 print_error("Error: Invalid key pair")
547 wallet.history[from_addr] = interface.retrieve_history(from_addr)
548 wallet.update_tx_history()
549 change_addr = from_addr
551 if options.change_addr:
552 change_addr = options.change_addr
554 for k, v in wallet.labels.items():
557 print("alias", to_address)
559 if change_addr and v == change_addr:
562 tx = wallet.mktx( to_address, amount, label, password,
563 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
566 traceback.print_exc(file=sys.stdout)
569 if tx and cmd=='payto':
570 r, h = wallet.sendtx( tx )
576 wallet.imported_keys.pop(from_addr)
577 del(wallet.history[from_addr])
580 elif cmd == 'sendtx':
582 r, h = wallet.sendtx( tx )
585 elif cmd == 'password':
587 seed = wallet.pw_decode( wallet.seed, password)
589 sys.exit("Error: Password does not decrypt this wallet.")
591 new_password = prompt_password('New password:')
592 wallet.update_password(seed, password, new_password)
594 elif cmd == 'signmessage':
596 print_error("Error: Invalid usage of signmessage.")
597 print(known_commands[cmd])
600 message = ' '.join(args[2:])
602 print("Warning: Message was reconstructed from several arguments:", repr(message))
603 print(wallet.sign_message(address, message, password))
605 elif cmd == 'verifymessage':
609 message = ' '.join(args[3:])
611 print_error("Error: Not all parameters were given, displaying help instead.")
612 print(known_commands[cmd])
615 print("Warning: Message was reconstructed from several arguments:", repr(message))
617 wallet.verify_message(address, signature, message)
619 except BaseException as e:
620 print_error("Verification error: {0}".format(e))
623 elif cmd == 'freeze':
625 print(wallet.freeze(addr))
627 elif cmd == 'unfreeze':
629 print(wallet.unfreeze(addr))
631 elif cmd == 'prioritize':
633 print(wallet.prioritize(addr))
635 elif cmd == 'unprioritize':
637 print(wallet.unprioritize(addr))