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/>.
24 from lib.util import print_error
26 from electrum.util import print_error
31 sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
36 sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
39 from lib import Wallet, Interface, WalletSynchronizer, WalletVerifier, format_satoshis, mnemonic, SimpleConfig, pick_random_server
41 from electrum import Wallet, Interface, WalletSynchronizer, WalletVerifier, format_satoshis, mnemonic, SimpleConfig, pick_random_server
43 from decimal import Decimal
46 'help':'Prints this help',
47 'validateaddress':'Check that the address is valid',
48 'balance': "Display the balance of your wallet or of an address.\nSyntax: balance [<address>]",
49 'contacts': "Show your list of contacts",
50 'create':'Create a wallet',
51 'restore':'Restore a wallet',
52 'payto':"""Create and broadcast a transaction.
53 Syntax: payto <recipient> <amount> [label]
54 <recipient> can be a bitcoin address or a label
55 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
58 'Broadcasts a transaction to the network. \nSyntax: sendtx <tx>\n<tx> must be in hexadecimal.',
60 "Changes your password",
62 """Shows your list of addresses.
64 -a: show all addresses, including change addresses
66 -b: show the balance of addresses""",
68 'history':"Shows the transaction history",
69 'label':'Assign a label to an item\nSyntax: label <tx_hash> <label>',
71 """Create a signed transaction, password protected.
72 Syntax: mktx <recipient> <amount> [label]
73 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
76 "Print the generation seed of your wallet.",
78 'Imports a key pair\nSyntax: import <address>:<privatekey>',
80 '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 "',
82 '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 "',
84 "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
86 "Set wallet parameter. (gui)",
88 "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
90 "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
99 offline_commands = [ 'password', 'mktx',
101 'help', 'validateaddress',
102 'signmessage', 'verifymessage',
103 'eval', 'set', 'create', 'addresses',
107 'prioritize','unprioritize']
110 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
112 # get password routine
113 def prompt_password(prompt, confirm=True):
115 if sys.stdin.isatty():
116 password = getpass.getpass(prompt)
117 if password and confirm:
118 password2 = getpass.getpass("Confirm: ")
119 if password != password2:
120 sys.exit("Error: Passwords do not match.")
122 password = raw_input(prompt)
129 if __name__ == '__main__':
131 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
132 parser = optparse.OptionParser(prog=usage)
133 parser.add_option("-g", "--gui", dest="gui", help="User interface: qt, lite, gtk or text")
134 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
135 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
136 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
137 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
138 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
139 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
140 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.")
141 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")
142 parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is t or h")
143 parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
144 options, args = parser.parse_args()
146 # config is an object passed to the various constructors (wallet, interface, gui)
147 config = SimpleConfig(options)
148 wallet = Wallet(config)
153 elif len(args)==1 and re.match('^bitcoin:', args[0]):
158 firstarg = args[1] if len(args) > 1 else ''
160 #this entire if/else block is just concerned with importing the
161 #right GUI toolkit based the GUI command line option given
163 pref_gui = config.get('gui','classic')
164 if pref_gui == 'gtk':
166 import lib.gui as gui
168 import electrum.gui as gui
169 elif pref_gui in ['classic', 'qt']:
171 import lib.gui_qt as gui
173 import electrum.gui_qt as gui
174 elif pref_gui == 'lite':
176 import lib.gui_lite as gui
178 import electrum.gui_lite as gui
179 elif pref_gui == 'text':
181 import lib.gui_text as gui
183 import electrum.gui_text as gui
185 sys.exit("Error: Unknown GUI: " + pref_gui )
187 interface = Interface(config, True)
188 wallet.interface = interface
189 gui = gui.ElectrumGui(wallet, config)
190 interface.register_callback('peers', gui.server_list_changed)
193 WalletSynchronizer(wallet, config).start()
195 verifier = WalletVerifier(interface, config, wallet.get_tx_hashes)
197 wallet.verifier = verifier
200 found = config.wallet_file_exists
202 found = gui.restore_or_create()
203 except SystemExit, e:
205 except BaseException, e:
207 traceback.print_exc(file=sys.stdout)
208 #gui.show_message(e.message)
217 if cmd not in known_commands:
220 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
221 print "Error: Wallet file not found."
222 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
225 if cmd in ['create', 'restore']:
226 if config.wallet_file_exists:
227 sys.exit("Error: Remove the existing wallet first!")
228 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
230 server = config.get('server')
231 if not server: server = pick_random_server()
232 w_host, w_port, w_protocol = server.split(':')
233 host = raw_input("server (default:%s):"%w_host)
234 port = raw_input("port (default:%s):"%w_port)
235 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
236 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
237 gap = raw_input("gap limit (default 5):")
238 if host: w_host = host
239 if port: w_port = port
240 if protocol: w_protocol = protocol
241 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
242 if fee: wallet.fee = float(fee)
243 if gap: wallet.gap_limit = int(gap)
246 seed = raw_input("seed:")
250 print_error("Warning: Not hex, trying decode.")
251 seed = mnemonic.mn_decode( seed.split(' ') )
253 sys.exit("Error: No seed")
255 wallet.seed = str(seed)
256 wallet.init_mpk( wallet.seed )
257 if not options.offline:
258 WalletSynchronizer(wallet, config).start()
259 print "Recovering wallet..."
260 wallet.up_to_date_event.clear()
261 wallet.up_to_date = False
263 if wallet.is_found():
264 print "Recovery successful"
266 print_error("Warning: Found no history for this wallet")
269 wallet.fill_addressbook()
271 print_error("Wallet saved in '" + wallet.path)
273 wallet.new_seed(None)
274 wallet.init_mpk( wallet.seed )
275 wallet.synchronize() # there is no wallet thread
277 print "Your wallet generation seed is: " + wallet.seed
278 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
279 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
280 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
281 print "Wallet saved in '%s'"%wallet.config.path
284 wallet.update_password(wallet.seed, None, password)
287 if cmd in ['payto', 'mktx']:
290 amount = int( 100000000 * Decimal(args[2]) )
292 label = ' '.join(args[3:])
294 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
300 if cmd not in offline_commands and not options.offline:
301 interface = Interface(config)
302 interface.register_callback('connected', lambda: print_error("Connected to " + interface.connection_msg))
304 wallet.interface = interface
305 WalletSynchronizer(wallet, config).start()
309 # check if --from_addr not in wallet (for mktx/payto)
312 if options.from_addr:
313 from_addr = options.from_addr
314 if from_addr not in wallet.all_addresses():
318 if cmd=='addresses' and options.show_keys:
319 print "WARNING: ALL your private keys are secret."
320 print "Exposing a single private key can compromise your entire wallet!"
321 print "In particular, DO NOT use 'redeem private key' services proposed by third parties."
323 # commands needing password
324 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
325 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
328 wallet.pw_decode( wallet.seed, password)
330 print_error("Error: This password does not decode this wallet.")
334 # See if they specificed a key on the cmd line, if not prompt
338 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
340 wallet.import_key(keypair,password)
342 print "Keypair imported"
343 except BaseException, e:
344 print_error("Error: Keypair import failed: " + str(e))
348 if cmd2 not in known_commands:
350 print "Type 'electrum help <command>' to see the help for a specific command"
351 print "Type 'electrum --help' to see the list of options"
352 print "List of commands:", ', '.join(known_commands)
354 print known_commands[cmd2]
357 seed = wallet.pw_decode( wallet.seed, password)
358 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
360 elif cmd == 'deseed':
362 print_error("Error: This wallet has no seed")
363 elif wallet.use_encryption:
364 print_error("Error: This wallet is encrypted")
366 ns = wallet.path + '.seed'
367 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
368 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
370 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
373 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
377 print_error("Action canceled.")
379 elif cmd == 'reseed':
381 print "Warning: This wallet already has a seed", wallet.seed
383 ns = wallet.path + '.seed'
389 sys.exit("Error: Seed file not found")
392 d = ast.literal_eval( data )
394 imported_keys = d.get('imported_keys',{})
396 sys.exit("Error: Error with seed file")
398 mpk = wallet.master_public_key
400 wallet.imported_keys = imported_keys
401 wallet.use_encryption = False
402 wallet.init_mpk(seed)
403 if mpk == wallet.master_public_key:
405 print "Done: " + wallet.path
407 print_error("Error: Master public key does not match")
409 elif cmd == 'validateaddress':
411 print wallet.is_valid(addr)
413 elif cmd == 'balance':
419 c, u = wallet.get_balance()
421 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
423 print Decimal( c ) / 100000000
426 c, u = wallet.get_addr_balance(addr)
428 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
430 print "%s %s" % (addr, str(Decimal(c)/100000000))
432 elif cmd in [ 'contacts']:
433 for addr in wallet.addressbook:
434 print addr, " ", wallet.labels.get(addr)
441 key, value = args[1:3]
442 if key not in ['seed_version', 'master_public_key', 'use_encryption']:
443 wallet.config.set_key(key, value, True)
448 elif cmd in [ 'addresses']:
449 for addr in wallet.all_addresses():
450 if options.show_all or not wallet.is_change(addr):
452 flags = wallet.get_address_flags(addr)
453 label = wallet.labels.get(addr,'')
455 if label: label = "\"%s\""%label
457 if options.show_balance:
458 h = wallet.history.get(addr,[])
461 # if item['is_input']: ni += 1
463 b = format_satoshis(wallet.get_addr_balance(addr)[0])
466 if options.show_keys:
467 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
468 print flags, m_addr, b, label
471 lines = wallet.get_tx_history()
478 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
480 print line['timestamp']
482 label = line.get('label')
483 if not label: label = line['tx_hash']
484 else: label = label + ' '*(64 - len(label) )
486 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
487 print "# balance: ", format_satoshis(b)
492 label = ' '.join(args[2:])
494 print_error("Error. Syntax: label <tx_hash> <text>")
496 wallet.labels[tx] = label
499 elif cmd in ['payto', 'mktx']:
500 if from_addr and is_temporary:
501 if from_addr.find(":") == -1:
502 keypair = from_addr + ":" + prompt_password('Private key:', False)
505 from_addr = keypair.split(':')[0]
506 if not wallet.import_key(keypair,password):
507 print_error("Error: Invalid key pair")
509 wallet.history[from_addr] = interface.retrieve_history(from_addr)
510 wallet.update_tx_history()
511 change_addr = from_addr
513 if options.change_addr:
514 change_addr = options.change_addr
516 for k, v in wallet.labels.items():
519 print "alias", to_address
521 if change_addr and v == change_addr:
524 tx = wallet.mktx( to_address, amount, label, password,
525 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
528 traceback.print_exc(file=sys.stdout)
531 if tx and cmd=='payto':
532 r, h = wallet.sendtx( tx )
538 wallet.imported_keys.pop(from_addr)
539 del(wallet.history[from_addr])
542 elif cmd == 'sendtx':
544 r, h = wallet.sendtx( tx )
547 elif cmd == 'password':
549 seed = wallet.pw_decode( wallet.seed, password)
551 sys.exit("Error: Password does not decrypt this wallet.")
553 new_password = prompt_password('New password:')
554 wallet.update_password(seed, password, new_password)
556 elif cmd == 'signmessage':
558 print_error("Error: Invalid usage of signmessage.")
559 print known_commands[cmd]
562 message = ' '.join(args[2:])
564 print "Warning: Message was reconstructed from several arguments:", repr(message)
565 print wallet.sign_message(address, message, password)
567 elif cmd == 'verifymessage':
571 message = ' '.join(args[3:])
573 print_error("Error: Not all parameters were given, displaying help instead.")
574 print known_commands[cmd]
577 print "Warning: Message was reconstructed from several arguments:", repr(message)
579 wallet.verify_message(address, signature, message)
581 except BaseException as e:
582 print "Verification error: {0}".format(e)
585 elif cmd == 'freeze':
587 print wallet.freeze(addr)
589 elif cmd == 'unfreeze':
591 print wallet.unfreeze(addr)
593 elif cmd == 'prioritize':
595 print wallet.prioritize(addr)
597 elif cmd == 'unprioritize':
599 print wallet.unprioritize(addr)