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)
126 if __name__ == '__main__':
128 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
129 parser = optparse.OptionParser(prog=usage)
130 parser.add_option("-g", "--gui", dest="gui", help="User interface: qt, lite, gtk or text")
131 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
132 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
133 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
134 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
135 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
136 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
137 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.")
138 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")
139 parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is t or h")
140 parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
141 parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="show debugging information")
142 options, args = parser.parse_args()
144 set_verbosity(options.verbose)
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)
189 interface.send([('server.peers.subscribe',[])])
191 wallet.interface = interface
193 gui = gui.ElectrumGui(wallet, config)
194 interface.register_callback('peers', gui.server_list_changed)
196 verifier = WalletVerifier(interface, config)
197 wallet.set_verifier(verifier)
199 WalletSynchronizer(wallet, config).start()
201 found = config.wallet_file_exists
203 found = gui.restore_or_create()
204 except SystemExit, e:
206 except BaseException, e:
208 traceback.print_exc(file=sys.stdout)
209 #gui.show_message(e.message)
221 if cmd not in known_commands:
224 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
225 print "Error: Wallet file not found."
226 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
229 if cmd in ['create', 'restore']:
230 if config.wallet_file_exists:
231 sys.exit("Error: Remove the existing wallet first!")
232 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
234 server = config.get('server')
235 if not server: server = pick_random_server()
236 w_host, w_port, w_protocol = server.split(':')
237 host = raw_input("server (default:%s):"%w_host)
238 port = raw_input("port (default:%s):"%w_port)
239 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
240 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
241 gap = raw_input("gap limit (default 5):")
242 if host: w_host = host
243 if port: w_port = port
244 if protocol: w_protocol = protocol
245 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
246 if fee: wallet.fee = float(fee)
247 if gap: wallet.gap_limit = int(gap)
250 seed = raw_input("seed:")
254 print_error("Warning: Not hex, trying decode.")
255 seed = mnemonic_decode( seed.split(' ') )
257 sys.exit("Error: No seed")
259 wallet.seed = str(seed)
260 wallet.init_mpk( wallet.seed )
261 if not options.offline:
263 interface = Interface(config)
265 wallet.interface = interface
267 verifier = WalletVerifier(interface, config)
268 wallet.set_verifier(verifier)
270 print "Recovering wallet..."
271 WalletSynchronizer(wallet, config).start()
272 wallet.up_to_date_event.clear()
273 wallet.up_to_date = False
275 if wallet.is_found():
276 print "Recovery successful"
278 print "Warning: Found no history for this wallet"
281 wallet.fill_addressbook()
283 print "Wallet saved in '%s'"%wallet.config.path
285 wallet.new_seed(None)
286 wallet.init_mpk( wallet.seed )
287 wallet.synchronize() # there is no wallet thread
289 print "Your wallet generation seed is: " + wallet.seed
290 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
291 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
292 print "\""+' '.join(mnemonic_encode(wallet.seed))+"\""
293 print "Wallet saved in '%s'"%wallet.config.path
296 wallet.update_password(wallet.seed, None, password)
299 if cmd in ['payto', 'mktx']:
302 amount = int( 100000000 * Decimal(args[2]) )
304 label = ' '.join(args[3:])
306 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
312 if cmd not in offline_commands and not options.offline:
313 interface = Interface(config)
314 interface.register_callback('connected', lambda: print_error("Connected to " + interface.connection_msg))
316 wallet.interface = interface
317 WalletSynchronizer(wallet, config).start()
321 # check if --from_addr not in wallet (for mktx/payto)
324 if options.from_addr:
325 from_addr = options.from_addr
326 if from_addr not in wallet.all_addresses():
330 if cmd=='addresses' and options.show_keys:
331 print "WARNING: ALL your private keys are secret."
332 print "Exposing a single private key can compromise your entire wallet!"
333 print "In particular, DO NOT use 'redeem private key' services proposed by third parties."
335 # commands needing password
336 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
337 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
340 wallet.pw_decode( wallet.seed, password)
342 print_error("Error: This password does not decode this wallet.")
346 # See if they specificed a key on the cmd line, if not prompt
350 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
352 wallet.import_key(keypair,password)
354 print "Keypair imported"
355 except BaseException, e:
356 print_error("Error: Keypair import failed: " + str(e))
360 if cmd2 not in known_commands:
362 print "Type 'electrum help <command>' to see the help for a specific command"
363 print "Type 'electrum --help' to see the list of options"
364 print "List of commands:", ', '.join(known_commands)
366 print known_commands[cmd2]
369 seed = wallet.pw_decode( wallet.seed, password)
370 print seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"'
372 elif cmd == 'deseed':
374 print_error("Error: This wallet has no seed")
375 elif wallet.use_encryption:
376 print_error("Error: This wallet is encrypted")
378 ns = wallet.path + '.seed'
379 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
380 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
382 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
385 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
389 print_error("Action canceled.")
391 elif cmd == 'reseed':
393 print "Warning: This wallet already has a seed", wallet.seed
395 ns = wallet.path + '.seed'
401 sys.exit("Error: Seed file not found")
404 d = ast.literal_eval( data )
406 imported_keys = d.get('imported_keys',{})
408 sys.exit("Error: Error with seed file")
410 mpk = wallet.master_public_key
412 wallet.imported_keys = imported_keys
413 wallet.use_encryption = False
414 wallet.init_mpk(seed)
415 if mpk == wallet.master_public_key:
417 print "Done: " + wallet.path
419 print_error("Error: Master public key does not match")
421 elif cmd == 'validateaddress':
423 print wallet.is_valid(addr)
425 elif cmd == 'balance':
431 c, u = wallet.get_balance()
433 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
435 print Decimal( c ) / 100000000
438 c, u = wallet.get_addr_balance(addr)
440 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
442 print "%s %s" % (addr, str(Decimal(c)/100000000))
444 elif cmd in [ 'contacts']:
445 for addr in wallet.addressbook:
446 print addr, " ", wallet.labels.get(addr)
454 print wallet.config.get(key)
457 key, value = args[1:3]
458 if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
459 wallet.config.set_key(key, value, True)
464 elif cmd in [ 'addresses']:
465 for addr in wallet.all_addresses():
466 if options.show_all or not wallet.is_change(addr):
468 flags = wallet.get_address_flags(addr)
469 label = wallet.labels.get(addr,'')
471 if label: label = "\"%s\""%label
473 if options.show_balance:
474 h = wallet.history.get(addr,[])
477 # if item['is_input']: ni += 1
479 b = format_satoshis(wallet.get_addr_balance(addr)[0])
482 if options.show_keys:
483 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
484 print flags, m_addr, b, label
488 for item in wallet.get_tx_history():
489 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
491 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
495 label, is_default_label = wallet.get_label(tx_hash)
496 if not label: label = tx_hash
497 else: label = label + ' '*(64 - len(label) )
499 print "%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance)
500 print "# balance: ", format_satoshis(balance)
505 label = ' '.join(args[2:])
507 print_error("Error. Syntax: label <tx_hash> <text>")
509 wallet.labels[tx] = label
512 elif cmd in ['payto', 'mktx']:
513 if from_addr and is_temporary:
514 if from_addr.find(":") == -1:
515 keypair = from_addr + ":" + prompt_password('Private key:', False)
518 from_addr = keypair.split(':')[0]
519 if not wallet.import_key(keypair,password):
520 print_error("Error: Invalid key pair")
522 wallet.history[from_addr] = interface.retrieve_history(from_addr)
523 wallet.update_tx_history()
524 change_addr = from_addr
526 if options.change_addr:
527 change_addr = options.change_addr
529 for k, v in wallet.labels.items():
532 print "alias", to_address
534 if change_addr and v == change_addr:
537 tx = wallet.mktx( to_address, amount, label, password,
538 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
541 traceback.print_exc(file=sys.stdout)
544 if tx and cmd=='payto':
545 r, h = wallet.sendtx( tx )
551 wallet.imported_keys.pop(from_addr)
552 del(wallet.history[from_addr])
555 elif cmd == 'sendtx':
557 r, h = wallet.sendtx( tx )
560 elif cmd == 'password':
562 seed = wallet.pw_decode( wallet.seed, password)
564 sys.exit("Error: Password does not decrypt this wallet.")
566 new_password = prompt_password('New password:')
567 wallet.update_password(seed, password, new_password)
569 elif cmd == 'signmessage':
571 print_error("Error: Invalid usage of signmessage.")
572 print known_commands[cmd]
575 message = ' '.join(args[2:])
577 print "Warning: Message was reconstructed from several arguments:", repr(message)
578 print wallet.sign_message(address, message, password)
580 elif cmd == 'verifymessage':
584 message = ' '.join(args[3:])
586 print_error("Error: Not all parameters were given, displaying help instead.")
587 print known_commands[cmd]
590 print "Warning: Message was reconstructed from several arguments:", repr(message)
592 wallet.verify_message(address, signature, message)
594 except BaseException as e:
595 print "Verification error: {0}".format(e)
598 elif cmd == 'freeze':
600 print wallet.freeze(addr)
602 elif cmd == 'unfreeze':
604 print wallet.unfreeze(addr)
606 elif cmd == 'prioritize':
608 print wallet.prioritize(addr)
610 elif cmd == 'unprioritize':
612 print wallet.unprioritize(addr)