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 "Get config parameter.",
88 "Set config parameter.",
90 "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
92 "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
101 offline_commands = [ 'password', 'mktx',
103 'help', 'validateaddress',
104 'signmessage', 'verifymessage',
105 'eval', 'set', 'get', 'create', 'addresses',
109 'prioritize','unprioritize']
112 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
114 # get password routine
115 def prompt_password(prompt, confirm=True):
117 if sys.stdin.isatty():
118 password = getpass.getpass(prompt)
119 if password and confirm:
120 password2 = getpass.getpass("Confirm: ")
121 if password != password2:
122 sys.exit("Error: Passwords do not match.")
124 password = raw_input(prompt)
131 if __name__ == '__main__':
133 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
134 parser = optparse.OptionParser(prog=usage)
135 parser.add_option("-g", "--gui", dest="gui", help="User interface: qt, lite, gtk or text")
136 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
137 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
138 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
139 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
140 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
141 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
142 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.")
143 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")
144 parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is t or h")
145 parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
146 options, args = parser.parse_args()
148 # config is an object passed to the various constructors (wallet, interface, gui)
149 config = SimpleConfig(options)
150 wallet = Wallet(config)
155 elif len(args)==1 and re.match('^bitcoin:', args[0]):
160 firstarg = args[1] if len(args) > 1 else ''
162 #this entire if/else block is just concerned with importing the
163 #right GUI toolkit based the GUI command line option given
165 pref_gui = config.get('gui','classic')
166 if pref_gui == 'gtk':
168 import lib.gui as gui
170 import electrum.gui as gui
171 elif pref_gui in ['classic', 'qt']:
173 import lib.gui_qt as gui
175 import electrum.gui_qt as gui
176 elif pref_gui == 'lite':
178 import lib.gui_lite as gui
180 import electrum.gui_lite as gui
181 elif pref_gui == 'text':
183 import lib.gui_text as gui
185 import electrum.gui_text as gui
187 sys.exit("Error: Unknown GUI: " + pref_gui )
189 interface = Interface(config, True)
192 wallet.interface = interface
193 WalletSynchronizer(wallet, config).start()
195 gui = gui.ElectrumGui(wallet, config)
196 interface.register_callback('peers', gui.server_list_changed)
198 found = config.wallet_file_exists
200 found = gui.restore_or_create()
201 except SystemExit, e:
203 except BaseException, e:
205 traceback.print_exc(file=sys.stdout)
206 #gui.show_message(e.message)
212 verifier = WalletVerifier(interface, config)
213 wallet.set_verifier(verifier)
220 if cmd not in known_commands:
223 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
224 print "Error: Wallet file not found."
225 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
228 if cmd in ['create', 'restore']:
229 if config.wallet_file_exists:
230 sys.exit("Error: Remove the existing wallet first!")
231 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
233 server = config.get('server')
234 if not server: server = pick_random_server()
235 w_host, w_port, w_protocol = server.split(':')
236 host = raw_input("server (default:%s):"%w_host)
237 port = raw_input("port (default:%s):"%w_port)
238 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
239 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
240 gap = raw_input("gap limit (default 5):")
241 if host: w_host = host
242 if port: w_port = port
243 if protocol: w_protocol = protocol
244 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
245 if fee: wallet.fee = float(fee)
246 if gap: wallet.gap_limit = int(gap)
249 seed = raw_input("seed:")
253 print_error("Warning: Not hex, trying decode.")
254 seed = mnemonic.mn_decode( seed.split(' ') )
256 sys.exit("Error: No seed")
258 wallet.seed = str(seed)
259 wallet.init_mpk( wallet.seed )
260 if not options.offline:
261 WalletSynchronizer(wallet, config).start()
262 print "Recovering wallet..."
263 wallet.up_to_date_event.clear()
264 wallet.up_to_date = False
266 if wallet.is_found():
267 print "Recovery successful"
269 print_error("Warning: Found no history for this wallet")
272 wallet.fill_addressbook()
274 print_error("Wallet saved in '" + wallet.path)
276 wallet.new_seed(None)
277 wallet.init_mpk( wallet.seed )
278 wallet.synchronize() # there is no wallet thread
280 print "Your wallet generation seed is: " + wallet.seed
281 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
282 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
283 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
284 print "Wallet saved in '%s'"%wallet.config.path
287 wallet.update_password(wallet.seed, None, password)
290 if cmd in ['payto', 'mktx']:
293 amount = int( 100000000 * Decimal(args[2]) )
295 label = ' '.join(args[3:])
297 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
303 if cmd not in offline_commands and not options.offline:
304 interface = Interface(config)
305 interface.register_callback('connected', lambda: print_error("Connected to " + interface.connection_msg))
307 wallet.interface = interface
308 WalletSynchronizer(wallet, config).start()
312 # check if --from_addr not in wallet (for mktx/payto)
315 if options.from_addr:
316 from_addr = options.from_addr
317 if from_addr not in wallet.all_addresses():
321 if cmd=='addresses' and options.show_keys:
322 print "WARNING: ALL your private keys are secret."
323 print "Exposing a single private key can compromise your entire wallet!"
324 print "In particular, DO NOT use 'redeem private key' services proposed by third parties."
326 # commands needing password
327 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
328 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
331 wallet.pw_decode( wallet.seed, password)
333 print_error("Error: This password does not decode this wallet.")
337 # See if they specificed a key on the cmd line, if not prompt
341 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
343 wallet.import_key(keypair,password)
345 print "Keypair imported"
346 except BaseException, e:
347 print_error("Error: Keypair import failed: " + str(e))
351 if cmd2 not in known_commands:
353 print "Type 'electrum help <command>' to see the help for a specific command"
354 print "Type 'electrum --help' to see the list of options"
355 print "List of commands:", ', '.join(known_commands)
357 print known_commands[cmd2]
360 seed = wallet.pw_decode( wallet.seed, password)
361 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
363 elif cmd == 'deseed':
365 print_error("Error: This wallet has no seed")
366 elif wallet.use_encryption:
367 print_error("Error: This wallet is encrypted")
369 ns = wallet.path + '.seed'
370 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
371 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
373 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
376 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
380 print_error("Action canceled.")
382 elif cmd == 'reseed':
384 print "Warning: This wallet already has a seed", wallet.seed
386 ns = wallet.path + '.seed'
392 sys.exit("Error: Seed file not found")
395 d = ast.literal_eval( data )
397 imported_keys = d.get('imported_keys',{})
399 sys.exit("Error: Error with seed file")
401 mpk = wallet.master_public_key
403 wallet.imported_keys = imported_keys
404 wallet.use_encryption = False
405 wallet.init_mpk(seed)
406 if mpk == wallet.master_public_key:
408 print "Done: " + wallet.path
410 print_error("Error: Master public key does not match")
412 elif cmd == 'validateaddress':
414 print wallet.is_valid(addr)
416 elif cmd == 'balance':
422 c, u = wallet.get_balance()
424 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
426 print Decimal( c ) / 100000000
429 c, u = wallet.get_addr_balance(addr)
431 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
433 print "%s %s" % (addr, str(Decimal(c)/100000000))
435 elif cmd in [ 'contacts']:
436 for addr in wallet.addressbook:
437 print addr, " ", wallet.labels.get(addr)
445 print wallet.config.get(key)
448 key, value = args[1:3]
449 if key not in ['seed_version', 'master_public_key', 'use_encryption']:
450 wallet.config.set_key(key, value, True)
455 elif cmd in [ 'addresses']:
456 for addr in wallet.all_addresses():
457 if options.show_all or not wallet.is_change(addr):
459 flags = wallet.get_address_flags(addr)
460 label = wallet.labels.get(addr,'')
462 if label: label = "\"%s\""%label
464 if options.show_balance:
465 h = wallet.history.get(addr,[])
468 # if item['is_input']: ni += 1
470 b = format_satoshis(wallet.get_addr_balance(addr)[0])
473 if options.show_keys:
474 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
475 print flags, m_addr, b, label
478 lines = wallet.get_tx_history()
485 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
487 print line['timestamp']
489 label = line.get('label')
490 if not label: label = line['tx_hash']
491 else: label = label + ' '*(64 - len(label) )
493 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
494 print "# balance: ", format_satoshis(b)
499 label = ' '.join(args[2:])
501 print_error("Error. Syntax: label <tx_hash> <text>")
503 wallet.labels[tx] = label
506 elif cmd in ['payto', 'mktx']:
507 if from_addr and is_temporary:
508 if from_addr.find(":") == -1:
509 keypair = from_addr + ":" + prompt_password('Private key:', False)
512 from_addr = keypair.split(':')[0]
513 if not wallet.import_key(keypair,password):
514 print_error("Error: Invalid key pair")
516 wallet.history[from_addr] = interface.retrieve_history(from_addr)
517 wallet.update_tx_history()
518 change_addr = from_addr
520 if options.change_addr:
521 change_addr = options.change_addr
523 for k, v in wallet.labels.items():
526 print "alias", to_address
528 if change_addr and v == change_addr:
531 tx = wallet.mktx( to_address, amount, label, password,
532 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
535 traceback.print_exc(file=sys.stdout)
538 if tx and cmd=='payto':
539 r, h = wallet.sendtx( tx )
545 wallet.imported_keys.pop(from_addr)
546 del(wallet.history[from_addr])
549 elif cmd == 'sendtx':
551 r, h = wallet.sendtx( tx )
554 elif cmd == 'password':
556 seed = wallet.pw_decode( wallet.seed, password)
558 sys.exit("Error: Password does not decrypt this wallet.")
560 new_password = prompt_password('New password:')
561 wallet.update_password(seed, password, new_password)
563 elif cmd == 'signmessage':
565 print_error("Error: Invalid usage of signmessage.")
566 print known_commands[cmd]
569 message = ' '.join(args[2:])
571 print "Warning: Message was reconstructed from several arguments:", repr(message)
572 print wallet.sign_message(address, message, password)
574 elif cmd == 'verifymessage':
578 message = ' '.join(args[3:])
580 print_error("Error: Not all parameters were given, displaying help instead.")
581 print known_commands[cmd]
584 print "Warning: Message was reconstructed from several arguments:", repr(message)
586 wallet.verify_message(address, signature, message)
588 except BaseException as e:
589 print "Verification error: {0}".format(e)
592 elif cmd == 'freeze':
594 print wallet.freeze(addr)
596 elif cmd == 'unfreeze':
598 print wallet.unfreeze(addr)
600 elif cmd == 'prioritize':
602 print wallet.prioritize(addr)
604 elif cmd == 'unprioritize':
606 print wallet.unprioritize(addr)