#!/usr/bin/env python # # Electrum - lightweight Bitcoin client # Copyright (C) 2011 thomasv@gitorious # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import re, sys, getpass try: import ecdsa except: print "python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'" sys.exit(1) try: import aes except: print "AES does not seem to be installed. Try 'sudo pip install slowaes'" sys.exit(1) import electrum from optparse import OptionParser from decimal import Decimal from electrum import Wallet, SecretToASecret, WalletSynchronizer, format_satoshis known_commands = ['help', 'validateaddress', 'balance', 'contacts', 'create', 'restore', 'payto', 'sendtx', 'password', 'addresses', 'history', 'label', 'mktx','seed','import','signmessage','verifymessage','eval','deseed','reseed'] offline_commands = ['password', 'mktx', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed','deseed','reseed'] protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ] if __name__ == '__main__': usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands)) parser = OptionParser(usage=usage) parser.add_option("-g", "--gui", dest="gui", default="qt", help="gui") parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)") parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline") parser.add_option("-m", "--mnemonic", action="store_true", dest="show_mnemonic", default=False, help="[seed] print the seed as mnemonic") parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses") parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses") parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses") parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee") parser.add_option("-s", "--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.") 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") parser.add_option("-r", "--remote", dest="remote_url", default=None, help="URL of a remote wallet") options, args = parser.parse_args() wallet = Wallet() wallet.set_path(options.wallet_path) wallet.read() wallet.remote_url = options.remote_url if len(args)==0: url = None cmd = 'gui' elif len(args)==1 and re.match('^bitcoin:', args[0]): url = args[0] cmd = 'gui' else: cmd = args[0] firstarg = args[1] if len(args) > 1 else '' if cmd == 'gui': if options.gui=='gtk': import electrum.gui as gui elif options.gui=='qt': import electrum.gui_qt as gui else: print "unknown gui", options.gui exit(1) gui = gui.ElectrumGui(wallet) WalletSynchronizer(wallet,True).start() try: found = wallet.file_exists if not found: found = gui.restore_or_create() except SystemExit, e: exit(e) except BaseException, e: import traceback traceback.print_exc(file=sys.stdout) #gui.show_message(e.message) exit(1) if not found: exit(1) gui.main(url) wallet.save() sys.exit(0) if cmd not in known_commands: cmd = 'help' if not wallet.file_exists and cmd not in ['help','create','restore']: print "Wallet file not found." print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option" sys.exit(0) if cmd in ['create', 'restore']: from electrum import mnemonic if wallet.file_exists: print "remove the existing wallet first!" sys.exit(0) password = getpass.getpass("Password (hit return if you do not wish to encrypt your wallet):") if password: password2 = getpass.getpass("Confirm password:") if password != password2: print "error" sys.exit(1) else: password = None w_host, w_port, w_protocol = wallet.server.split(':') host = raw_input("server (default:%s):"%w_host) port = raw_input("port (default:%s):"%w_port) protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol) fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) ) gap = raw_input("gap limit (default 5):") if host: w_host = host if port: w_port = port if protocol: w_protocol = protocol wallet.server = w_host + ':' + w_port + ':' +w_protocol if fee: wallet.fee = float(fee) if gap: wallet.gap_limit = int(gap) if cmd == 'restore': seed = raw_input("seed:") try: seed.decode('hex') except: print "not hex, trying decode" seed = mnemonic.mn_decode( seed.split(' ') ) if not seed: print "no seed" sys.exit(1) wallet.seed = str(seed) wallet.init_mpk( wallet.seed ) if not options.offline: WalletSynchronizer(wallet).start() print "recovering wallet..." wallet.up_to_date_event.clear() wallet.up_to_date = False wallet.update() if wallet.is_found(): print "recovery successful" else: print "found no history for this wallet" wallet.fill_addressbook() wallet.save() print "Wallet saved in '%s'"%options.wallet_path else: wallet.new_seed(None) wallet.init_mpk( wallet.seed ) wallet.synchronize() # there is no wallet thread wallet.save() print "Your wallet generation seed is: " + wallet.seed print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet." print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:" print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\"" print "Wallet saved in '%s'"%options.wallet_path # check syntax if cmd in ['payto', 'mktx']: try: to_address = args[1] amount = int( 100000000 * Decimal(args[2]) ) change_addr = None label = ' '.join(args[3:]) if options.tx_fee: options.tx_fee = int( 100000000 * Decimal(options.tx_fee) ) except: firstarg = cmd cmd = 'help' # open session if cmd not in offline_commands and not options.offline: WalletSynchronizer(wallet).start() wallet.update() wallet.save() # check if --from_addr not in wallet (for mktx/payto) is_temporary = False from_addr = None if options.from_addr: from_addr = options.from_addr if from_addr not in wallet.all_addresses(): is_temporary = True # commands needing password if cmd in protected_commands or ( cmd=='addresses' and options.show_keys): password = getpass.getpass('Password:') if wallet.use_encryption and not is_temporary else None # check password try: wallet.pw_decode( wallet.seed, password) except: print "invalid password" exit(1) if cmd == 'import': keypair = args[1] if wallet.import_key(keypair,password): print "keypair imported" else: print "error" wallet.save() if cmd=='help': cmd2 = firstarg if cmd2 not in known_commands: print "type 'electrum help ' to see the help for a specific command" print "type 'electrum --help' to see the list of options" print "list of commands:", ', '.join(known_commands) elif cmd2 == 'balance': print "Display the balance of your wallet or of an address." print "syntax: balance [
]" elif cmd2 == 'contacts': print "show your list of contacts" elif cmd2 == 'payto': print "payto [label]" print "create and broadcast a transaction." print " can be a bitcoin address or a label" print "options:\n--fee, -f: set transaction fee\n--fromaddr, -s: send from address -\n--changeaddr, -c: send change to address" elif cmd2== 'sendtx': print "sendtx " print "broadcast a transaction to the network. must be in hexadecimal" elif cmd2 == 'password': print "change your password" elif cmd2 == 'addresses': print "show your list of addresses." print "options:\n -a: show all addresses, including change addresses\n-k: show private keys\n-b: show the balance of addresses" elif cmd2 == 'history': print "show the transaction history" elif cmd2 == 'label': print "assign a label to an item" elif cmd2 == 'gtk': print "start the GUI" elif cmd2 == 'mktx': print "create a signed transaction. password protected" print "syntax: mktx [label]" print "options:\n--fee, -f: set transaction fee\n--fromaddr, -s: send from address -\n--changeaddr, -c: send change to address" elif cmd2 == 'seed': print "print the generation seed of your wallet." print "options:\n-m, --mnemonic : print the seed as mnemonic" elif cmd2 == 'deseed': print "remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'" elif cmd2 == 'reseed': print "restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key." elif cmd2 == 'eval': print "Run python eval() on an object\nSyntax: eval \nExample: eval \"wallet.aliases\"" elif cmd == 'seed': from electrum import mnemonic seed = wallet.pw_decode( wallet.seed, password) if options.show_mnemonic: print ' '.join(mnemonic.mn_encode(seed)) else: print seed elif cmd == 'deseed': if not wallet.seed: print "Error: This wallet has no seed" elif wallet.use_encryption: print "Error: This wallet is encrypted" else: ns = options.wallet_path+'.seed' print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(options.wallet_path,ns) if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']: f = open(ns,'w') f.write(wallet.seed) f.close() wallet.seed = '' wallet.save() print "Done." else: print "Action canceled." elif cmd == 'reseed': if wallet.seed: print "This wallet already has a seed" else: ns = options.wallet_path+'.seed' try: f = open(ns,'r') seed = f.read() f.close() except: print "seed file not found" sys.exit() mpk = wallet.master_public_key wallet.seed = seed wallet.use_encryption = False wallet.init_mpk(seed) if mpk == wallet.master_public_key: wallet.save() print "done" else: print "error: master public key does not match" elif cmd == 'validateaddress': addr = args[1] print wallet.is_valid(addr) elif cmd == 'balance': try: addrs = args[1:] except: pass if addrs == []: c, u = wallet.get_balance() if u: print Decimal( c ) / 100000000 , Decimal( u ) / 100000000 else: print Decimal( c ) / 100000000 else: for addr in addrs: c, u = wallet.get_addr_balance(addr) if u: print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000)) else: print "%s %s" % (addr, str(Decimal(c)/100000000)) elif cmd in [ 'contacts']: for addr in wallet.addressbook: print addr, " ", wallet.labels.get(addr) elif cmd == 'eval': print eval(args[1]) wallet.save() elif cmd in [ 'addresses']: for addr in wallet.all_addresses(): if options.show_all or not wallet.is_change(addr): label = wallet.labels.get(addr) _type = '' if wallet.is_change(addr): _type = "[change]" if addr in wallet.imported_keys.keys(): _type = "[imported]" if label is None: label = '' if options.show_balance: h = wallet.history.get(addr,[]) ni = no = 0 for item in h: if item['is_input']: ni += 1 else: no += 1 b = "%d %d %s"%(no, ni, str(Decimal(wallet.get_addr_balance(addr)[0])/100000000)) else: b='' if options.show_keys: pk = wallet.get_private_key(addr, password) addr = addr + ':' + SecretToASecret(pk) print addr, b, _type, label if cmd == 'history': lines = wallet.get_tx_history() b = 0 for line in lines: import datetime v = line['value'] b += v try: time_str = str( datetime.datetime.fromtimestamp( line['timestamp'])) except: print line['timestamp'] time_str = 'pending' label = line.get('label') if not label: label = line['tx_hash'] else: label = label + ' '*(64 - len(label) ) print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b) print "# balance: ", format_satoshis(b) elif cmd == 'label': try: tx = args[1] label = ' '.join(args[2:]) except: print "syntax: label " sys.exit(1) wallet.labels[tx] = label wallet.save() elif cmd in ['payto', 'mktx']: if from_addr and is_temporary: if from_addr.find(":") == -1: keypair = from_addr + ":" + getpass.getpass('Private key:') else: keypair = from_addr from_addr = keypair.split(':')[0] if not wallet.import_key(keypair,password): print "invalid key pair" exit(1) wallet.history[from_addr] = interface.retrieve_history(from_addr) wallet.update_tx_history() change_addr = from_addr if options.change_addr: change_addr = options.change_addr for k, v in wallet.labels.items(): if v == to_address: to_address = k print "alias", to_address break if change_addr and v == change_addr: change_addr = k try: tx = wallet.mktx( to_address, amount, label, password, fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr ) except: import traceback traceback.print_exc(file=sys.stdout) tx = None if tx and cmd=='payto': r, h = wallet.sendtx( tx ) print h else: print tx if is_temporary: wallet.imported_keys.pop(from_addr) del(wallet.history[from_addr]) wallet.save() elif cmd == 'sendtx': tx = args[1] r, h = wallet.sendtx( tx ) print h elif cmd == 'password': try: seed = wallet.pw_decode( wallet.seed, password) except: print "sorry" sys.exit(1) new_password = getpass.getpass('New password:') if new_password == getpass.getpass('Confirm new password:'): wallet.use_encryption = (new_password != '') wallet.seed = wallet.pw_encode( seed, new_password) for k in wallet.imported_keys.keys(): a = wallet.imported_keys[k] b = wallet.pw_decode(a, password) c = wallet.pw_encode(b, new_password) wallet.imported_keys[k] = c wallet.save() else: print "error: mismatch" elif cmd == 'signmessage': address, message = args[1:3] print wallet.sign_message(address, message, password) elif cmd == 'verifymessage': address, signature, message = args[1:4] try: wallet.verify_message(address, signature, message) print True except: print False