You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

477 lines
18 KiB

13 years ago
#!/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 <http://www.gnu.org/licenses/>.
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
13 years ago
13 years ago
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']
13 years ago
protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
13 years ago
if __name__ == '__main__':
usage = "usage: %prog [options] command args\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()
13 years ago
wallet = Wallet()
wallet.set_path(options.wallet_path)
wallet.read()
13 years ago
wallet.remote_url = options.remote_url
13 years ago
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
13 years ago
elif options.gui=='qt':
import electrum.gui_qt as gui
13 years ago
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()
13 years ago
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)
13 years ago
gui.main(url)
13 years ago
wallet.save()
sys.exit(0)
13 years ago
13 years ago
if cmd not in known_commands:
cmd = 'help'
if not wallet.file_exists and cmd not in ['help','create','restore']:
13 years ago
print "Wallet file not found."
13 years ago
print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
13 years ago
sys.exit(0)
if cmd in ['create', 'restore']:
13 years ago
from electrum import mnemonic
if wallet.file_exists:
13 years ago
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)
13 years ago
else:
password = None
13 years ago
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):")
13 years ago
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
13 years ago
# 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:
13 years ago
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
13 years ago
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()
13 years ago
if cmd=='help':
cmd2 = firstarg
if cmd2 not in known_commands:
print "known commands:", ', '.join(known_commands)
13 years ago
print "'electrum help <command>' shows the help on a specific command"
print "'electrum --help' shows the list of options"
13 years ago
elif cmd2 == 'balance':
print "Display the balance of your wallet or of an address."
print "syntax: balance [<address>]"
13 years ago
elif cmd2 == 'contacts':
print "show your list of contacts"
elif cmd2 == 'payto':
print "payto <recipient> <amount> [label]"
print "create and broadcast a transaction."
print "<recipient> 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"
13 years ago
elif cmd2== 'sendtx':
print "sendtx <tx>"
print "broadcast a transaction to the network. <tx> 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"
13 years ago
elif cmd2 == 'history':
print "show the transaction history"
elif cmd2 == 'label':
print "assign a label to an item"
elif cmd2 == 'gtk':
13 years ago
print "start the GUI"
elif cmd2 == 'mktx':
print "create a signed transaction. password protected"
print "syntax: mktx <recipient> <amount> [label]"
print "options:\n--fee, -f: set transaction fee\n--fromaddr, -s: send from address -\n--changeaddr, -c: send change to address"
13 years ago
elif cmd2 == 'seed':
print "print the generation seed of your wallet."
print "options:\n-m, --mnemonic : print the seed as mnemonic"
13 years ago
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':
13 years ago
print "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\""
13 years ago
elif cmd == 'seed':
13 years ago
from electrum import mnemonic
13 years ago
seed = wallet.pw_decode( wallet.seed, password)
if options.show_mnemonic:
print ' '.join(mnemonic.mn_encode(seed))
else:
print seed
13 years ago
elif cmd == 'deseed':
13 years ago
if not wallet.seed:
print "Eooro: This wallet has no seed"
elif wallet.use_encryption:
print "Error: This wallet is encrypted"
13 years ago
else:
13 years ago
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"
13 years ago
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
13 years ago
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))
13 years ago
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]"
13 years ago
if label is None: label = ''
if options.show_balance:
h = wallet.history.get(addr,[])
ni = no = 0
for item in h:
13 years ago
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:
13 years ago
pk = wallet.get_private_key(addr, password)
addr = addr + ':' + SecretToASecret(pk)
print addr, b, _type, label
13 years ago
if cmd == 'history':
lines = wallet.get_tx_history()
b = 0
for line in lines:
import datetime
v = line['value']
13 years ago
b += v
try:
13 years ago
time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
13 years ago
except:
13 years ago
print line['timestamp']
13 years ago
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)
13 years ago
elif cmd == 'label':
try:
tx = args[1]
label = ' '.join(args[2:])
13 years ago
except:
print "syntax: label <tx_hash> <text>"
sys.exit(1)
13 years ago
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
13 years ago
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)
13 years ago
del(wallet.history[from_addr])
wallet.save()
elif cmd == 'sendtx':
tx = args[1]
r, h = wallet.sendtx( tx )
print h
13 years ago
elif cmd == 'password':
try:
seed = wallet.pw_decode( wallet.seed, password)
13 years ago
except:
print "sorry"
sys.exit(1)
13 years ago
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
13 years ago
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