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
192 WalletSynchronizer(wallet, config).start()
194 gui = gui.ElectrumGui(wallet, config)
195 interface.register_callback('peers', gui.server_list_changed)
197 found = config.wallet_file_exists
199 found = gui.restore_or_create()
200 except SystemExit, e:
202 except BaseException, e:
204 traceback.print_exc(file=sys.stdout)
205 #gui.show_message(e.message)
211 verifier = WalletVerifier(interface, config)
212 wallet.set_verifier(verifier)
213 if not config.get('disable_spv'):
216 print "warning: SPV is disabled"
222 if cmd not in known_commands:
225 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
226 print "Error: Wallet file not found."
227 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
230 if cmd in ['create', 'restore']:
231 if config.wallet_file_exists:
232 sys.exit("Error: Remove the existing wallet first!")
233 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
235 server = config.get('server')
236 if not server: server = pick_random_server()
237 w_host, w_port, w_protocol = server.split(':')
238 host = raw_input("server (default:%s):"%w_host)
239 port = raw_input("port (default:%s):"%w_port)
240 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
241 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
242 gap = raw_input("gap limit (default 5):")
243 if host: w_host = host
244 if port: w_port = port
245 if protocol: w_protocol = protocol
246 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
247 if fee: wallet.fee = float(fee)
248 if gap: wallet.gap_limit = int(gap)
251 seed = raw_input("seed:")
255 print_error("Warning: Not hex, trying decode.")
256 seed = mnemonic.mn_decode( seed.split(' ') )
258 sys.exit("Error: No seed")
260 wallet.seed = str(seed)
261 wallet.init_mpk( wallet.seed )
262 if not options.offline:
263 WalletSynchronizer(wallet, config).start()
264 print "Recovering wallet..."
265 wallet.up_to_date_event.clear()
266 wallet.up_to_date = False
268 if wallet.is_found():
269 print "Recovery successful"
271 print_error("Warning: Found no history for this wallet")
274 wallet.fill_addressbook()
276 print_error("Wallet saved in '" + wallet.path)
278 wallet.new_seed(None)
279 wallet.init_mpk( wallet.seed )
280 wallet.synchronize() # there is no wallet thread
282 print "Your wallet generation seed is: " + wallet.seed
283 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
284 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
285 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
286 print "Wallet saved in '%s'"%wallet.config.path
289 wallet.update_password(wallet.seed, None, password)
292 if cmd in ['payto', 'mktx']:
295 amount = int( 100000000 * Decimal(args[2]) )
297 label = ' '.join(args[3:])
299 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
305 if cmd not in offline_commands and not options.offline:
306 interface = Interface(config)
307 interface.register_callback('connected', lambda: print_error("Connected to " + interface.connection_msg))
309 wallet.interface = interface
310 WalletSynchronizer(wallet, config).start()
314 # check if --from_addr not in wallet (for mktx/payto)
317 if options.from_addr:
318 from_addr = options.from_addr
319 if from_addr not in wallet.all_addresses():
323 if cmd=='addresses' and options.show_keys:
324 print "WARNING: ALL your private keys are secret."
325 print "Exposing a single private key can compromise your entire wallet!"
326 print "In particular, DO NOT use 'redeem private key' services proposed by third parties."
328 # commands needing password
329 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
330 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
333 wallet.pw_decode( wallet.seed, password)
335 print_error("Error: This password does not decode this wallet.")
339 # See if they specificed a key on the cmd line, if not prompt
343 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
345 wallet.import_key(keypair,password)
347 print "Keypair imported"
348 except BaseException, e:
349 print_error("Error: Keypair import failed: " + str(e))
353 if cmd2 not in known_commands:
355 print "Type 'electrum help <command>' to see the help for a specific command"
356 print "Type 'electrum --help' to see the list of options"
357 print "List of commands:", ', '.join(known_commands)
359 print known_commands[cmd2]
362 seed = wallet.pw_decode( wallet.seed, password)
363 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
365 elif cmd == 'deseed':
367 print_error("Error: This wallet has no seed")
368 elif wallet.use_encryption:
369 print_error("Error: This wallet is encrypted")
371 ns = wallet.path + '.seed'
372 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
373 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
375 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
378 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
382 print_error("Action canceled.")
384 elif cmd == 'reseed':
386 print "Warning: This wallet already has a seed", wallet.seed
388 ns = wallet.path + '.seed'
394 sys.exit("Error: Seed file not found")
397 d = ast.literal_eval( data )
399 imported_keys = d.get('imported_keys',{})
401 sys.exit("Error: Error with seed file")
403 mpk = wallet.master_public_key
405 wallet.imported_keys = imported_keys
406 wallet.use_encryption = False
407 wallet.init_mpk(seed)
408 if mpk == wallet.master_public_key:
410 print "Done: " + wallet.path
412 print_error("Error: Master public key does not match")
414 elif cmd == 'validateaddress':
416 print wallet.is_valid(addr)
418 elif cmd == 'balance':
424 c, u = wallet.get_balance()
426 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
428 print Decimal( c ) / 100000000
431 c, u = wallet.get_addr_balance(addr)
433 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
435 print "%s %s" % (addr, str(Decimal(c)/100000000))
437 elif cmd in [ 'contacts']:
438 for addr in wallet.addressbook:
439 print addr, " ", wallet.labels.get(addr)
447 print wallet.config.get(key)
450 key, value = args[1:3]
451 if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
452 wallet.config.set_key(key, value, True)
457 elif cmd in [ 'addresses']:
458 for addr in wallet.all_addresses():
459 if options.show_all or not wallet.is_change(addr):
461 flags = wallet.get_address_flags(addr)
462 label = wallet.labels.get(addr,'')
464 if label: label = "\"%s\""%label
466 if options.show_balance:
467 h = wallet.history.get(addr,[])
470 # if item['is_input']: ni += 1
472 b = format_satoshis(wallet.get_addr_balance(addr)[0])
475 if options.show_keys:
476 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
477 print flags, m_addr, b, label
480 lines = wallet.get_tx_history()
484 v = wallet.get_tx_value(line['tx_hash'])
487 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
489 print line['timestamp']
491 label = line.get('label')
492 if not label: label = line['tx_hash']
493 else: label = label + ' '*(64 - len(label) )
495 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
496 print "# balance: ", format_satoshis(b)
501 label = ' '.join(args[2:])
503 print_error("Error. Syntax: label <tx_hash> <text>")
505 wallet.labels[tx] = label
508 elif cmd in ['payto', 'mktx']:
509 if from_addr and is_temporary:
510 if from_addr.find(":") == -1:
511 keypair = from_addr + ":" + prompt_password('Private key:', False)
514 from_addr = keypair.split(':')[0]
515 if not wallet.import_key(keypair,password):
516 print_error("Error: Invalid key pair")
518 wallet.history[from_addr] = interface.retrieve_history(from_addr)
519 wallet.update_tx_history()
520 change_addr = from_addr
522 if options.change_addr:
523 change_addr = options.change_addr
525 for k, v in wallet.labels.items():
528 print "alias", to_address
530 if change_addr and v == change_addr:
533 tx = wallet.mktx( to_address, amount, label, password,
534 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
537 traceback.print_exc(file=sys.stdout)
540 if tx and cmd=='payto':
541 r, h = wallet.sendtx( tx )
547 wallet.imported_keys.pop(from_addr)
548 del(wallet.history[from_addr])
551 elif cmd == 'sendtx':
553 r, h = wallet.sendtx( tx )
556 elif cmd == 'password':
558 seed = wallet.pw_decode( wallet.seed, password)
560 sys.exit("Error: Password does not decrypt this wallet.")
562 new_password = prompt_password('New password:')
563 wallet.update_password(seed, password, new_password)
565 elif cmd == 'signmessage':
567 print_error("Error: Invalid usage of signmessage.")
568 print known_commands[cmd]
571 message = ' '.join(args[2:])
573 print "Warning: Message was reconstructed from several arguments:", repr(message)
574 print wallet.sign_message(address, message, password)
576 elif cmd == 'verifymessage':
580 message = ' '.join(args[3:])
582 print_error("Error: Not all parameters were given, displaying help instead.")
583 print known_commands[cmd]
586 print "Warning: Message was reconstructed from several arguments:", repr(message)
588 wallet.verify_message(address, signature, message)
590 except BaseException as e:
591 print "Verification error: {0}".format(e)
594 elif cmd == 'freeze':
596 print wallet.freeze(addr)
598 elif cmd == 'unfreeze':
600 print wallet.unfreeze(addr)
602 elif cmd == 'prioritize':
604 print wallet.prioritize(addr)
606 elif cmd == 'unprioritize':
608 print wallet.unprioritize(addr)