Browse Source

Merge branch 'master' of gitorious.org:electrum/electrum

283
ThomasV 13 years ago
parent
commit
eb060854a1
  1. 77
      client/electrum
  2. 310
      client/gui.py
  3. 3
      client/interface.py
  4. 94
      client/msqr.py
  5. 2
      client/version.py
  6. 161
      client/wallet.py

77
client/electrum

@ -16,15 +16,22 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import re,sys import re, sys, getpass
from optparse import OptionParser from optparse import OptionParser
from wallet import Wallet from wallet import Wallet
from interface import Interface from interface import Interface
from decimal import Decimal
# URL decode
_ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE)
urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x)
from wallet import format_satoshis
if __name__ == '__main__': if __name__ == '__main__':
known_commands = ['help', 'validateaddress', 'balance', 'contacts', 'create', 'payto', 'sendtx', 'password', 'newaddress', 'addresses', 'history', 'label', 'gui', 'mktx','seed','import'] known_commands = ['help', 'validateaddress', 'balance', 'contacts', 'create', 'payto', 'sendtx', 'password', 'newaddress', 'addresses', 'history', 'label', 'gui', 'mktx','seed','import','signmessage','verifymessage','eval']
usage = "usage: %prog [options] command args\nCommands: "+ (', '.join(known_commands)) usage = "usage: %prog [options] command args\nCommands: "+ (', '.join(known_commands))
@ -52,24 +59,30 @@ if __name__ == '__main__':
import gui import gui
gui.init_wallet(wallet) gui.init_wallet(wallet)
gui = gui.BitcoinGUI(wallet) gui = gui.BitcoinGUI(wallet)
if re.match('^bitcoin:', cmd): if re.match('^bitcoin:', cmd):
o = cmd[8:].split('?') o = cmd[8:].split('?')
address = o[0] address = o[0]
if len(o)>1: if len(o)>1:
params = o[1].split('&') params = o[1].split('&')
else: else:
params = [] params = []
cmd = 'gui'
amount = '' amount = label = message = signature = identity = ''
label = ''
for p in params: for p in params:
k,v = p.split('=') k,v = p.split('=')
v = urldecode(v) uv = urldecode(v)
if k=='amount': amount = v if k == 'amount': amount = uv
elif k=='label': label = v elif k == 'message': message = uv
else: print k,v elif k == 'label': label = uv
elif k == 'signature':
identity, signature = uv.split(':')
cmd = cmd.replace('&%s=%s'%(k,v),'')
else:
print k,v
gui.set_send_tab(address, amount, label) gui.set_send_tab(address, amount, message, label, identity, signature, cmd)
gui.main() gui.main()
wallet.save() wallet.save()
@ -98,7 +111,7 @@ if __name__ == '__main__':
host = raw_input("server (default:%s):"%wallet.interface.host) host = raw_input("server (default:%s):"%wallet.interface.host)
port = raw_input("port (default:%d):"%wallet.interface.port) port = raw_input("port (default:%d):"%wallet.interface.port)
fee = raw_input("fee (default:%f):"%(wallet.fee*1e-8)) fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
if fee: wallet.fee = float(fee) if fee: wallet.fee = float(fee)
if host: wallet.interface.host = host if host: wallet.interface.host = host
if port: wallet.interface.port = int(port) if port: wallet.interface.port = int(port)
@ -136,13 +149,13 @@ if __name__ == '__main__':
cmd = 'help' cmd = 'help'
# open session # open session
if cmd not in ['password', 'mktx', 'history', 'label', 'contacts', 'help', 'validateaddress']: if cmd not in ['password', 'mktx', 'history', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval']:
wallet.interface.new_session(wallet.all_addresses(), wallet.electrum_version) wallet.interface.new_session(wallet.all_addresses(), wallet.electrum_version)
wallet.update() wallet.update()
wallet.save() wallet.save()
# commands needing password # commands needing password
if cmd in ['payto', 'password', 'mktx', 'seed', 'import' ] or ( cmd=='addresses' and options.show_keys): if cmd in ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ] or ( cmd=='addresses' and options.show_keys):
password = getpass.getpass('Password:') if wallet.use_encryption else None password = getpass.getpass('Password:') if wallet.use_encryption else None
# check password # check password
try: try:
@ -193,6 +206,8 @@ if __name__ == '__main__':
print "syntax: mktx <recipient> <amount> [label]" print "syntax: mktx <recipient> <amount> [label]"
elif cmd2 == 'seed': elif cmd2 == 'seed':
print "show generation seed of your wallet. password protected." print "show generation seed of your wallet. password protected."
elif cmd2 == 'eval':
print "run python eval on an object"
elif cmd == 'seed': elif cmd == 'seed':
import mnemonic import mnemonic
@ -211,21 +226,25 @@ if __name__ == '__main__':
if addrs == []: if addrs == []:
c, u = wallet.get_balance() c, u = wallet.get_balance()
if u: if u:
print c*1e-8, u*1e-8 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
else: else:
print c*1e-8 print Decimal( c ) / 100000000
else: else:
for addr in addrs: for addr in addrs:
c, u = wallet.get_addr_balance(addr) c, u = wallet.get_addr_balance(addr)
if u: if u:
print "%s %s, %s" % (addr, c*1e-8, u*1e-8) print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
else: else:
print "%s %s" % (addr, c*1e-8) print "%s %s" % (addr, str(Decimal(c)/100000000))
elif cmd in [ 'contacts']: elif cmd in [ 'contacts']:
for addr in wallet.addressbook: for addr in wallet.addressbook:
print addr, " ", wallet.labels.get(addr) print addr, " ", wallet.labels.get(addr)
elif cmd == 'eval':
print eval(args[1])
wallet.save()
elif cmd in [ 'addresses']: elif cmd in [ 'addresses']:
for addr in wallet.all_addresses(): for addr in wallet.all_addresses():
if options.show_all or not wallet.is_change(addr): if options.show_all or not wallet.is_change(addr):
@ -240,7 +259,7 @@ if __name__ == '__main__':
for item in h: for item in h:
if item['is_in']: ni += 1 if item['is_in']: ni += 1
else: no += 1 else: no += 1
b = "%d %d %f"%(no, ni, wallet.get_addr_balance(addr)[0]*1e-8) b = "%d %d %s"%(no, ni, str(Decimal(wallet.get_addr_balance(addr)[0])/100000000))
else: b='' else: b=''
if options.show_keys: if options.show_keys:
pk = wallet.get_private_key2(addr, password) pk = wallet.get_private_key2(addr, password)
@ -252,9 +271,8 @@ if __name__ == '__main__':
b = 0 b = 0
for line in lines: for line in lines:
import datetime import datetime
v = 1.*line['value']/1e8 v = line['value']
b += v b += v
v_str = "%f"%v if v<0 else "+%f"%v
try: try:
time_str = datetime.datetime.fromtimestamp( line['nTime']) time_str = datetime.datetime.fromtimestamp( line['nTime'])
except: except:
@ -264,8 +282,8 @@ if __name__ == '__main__':
if not label: label = line['tx_hash'] if not label: label = line['tx_hash']
else: label = label + ' '*(64 - len(label) ) else: label = label + ' '*(64 - len(label) )
print time_str, " ", label, " ", v_str, " ", "%f"%b print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
print "# balance: ", b print "# balance: ", format_satoshis(b)
elif cmd == 'label': elif cmd == 'label':
try: try:
@ -323,3 +341,16 @@ if __name__ == '__main__':
else: else:
print "error: mismatch" 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

310
client/gui.py

@ -17,7 +17,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import datetime import datetime
import thread, time, ast, sys import thread, time, ast, sys, re
import socket, traceback import socket, traceback
import pygtk import pygtk
pygtk.require('2.0') pygtk.require('2.0')
@ -28,12 +28,7 @@ from decimal import Decimal
gtk.gdk.threads_init() gtk.gdk.threads_init()
APP_NAME = "Electrum" APP_NAME = "Electrum"
def format_satoshis(x): from wallet import format_satoshis
s = str( Decimal(x) /100000000 )
if not '.' in s: s += '.'
p = s.find('.')
s += " "*( 9 - ( len(s) - p ))
return s
def numbify(entry, is_int = False): def numbify(entry, is_int = False):
text = entry.get_text().strip() text = entry.get_text().strip()
@ -124,6 +119,7 @@ def init_wallet(wallet):
else: else:
# ask for the server. # ask for the server.
wallet.interface.get_servers()
run_network_dialog( wallet, parent=None ) run_network_dialog( wallet, parent=None )
# ask for seed and gap. # ask for seed and gap.
@ -370,8 +366,9 @@ def run_network_dialog( wallet, parent ):
if host!= wallet.interface.host or port!=wallet.interface.port: if host!= wallet.interface.host or port!=wallet.interface.port:
wallet.interface.host = host wallet.interface.host = host
wallet.interface.set_port( port ) wallet.interface.set_port( port )
wallet.save()
wallet.interface.is_connected = False wallet.interface.is_connected = False
if parent:
wallet.save()
@ -399,8 +396,8 @@ def password_line(label):
password.show() password.show()
return password, password_entry return password, password_entry
def password_dialog(): def password_dialog(parent):
dialog = gtk.MessageDialog( None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, "Please enter your password.") gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, "Please enter your password.")
dialog.get_image().set_visible(False) dialog.get_image().set_visible(False)
current_pw, current_pw_entry = password_line('Password:') current_pw, current_pw_entry = password_line('Password:')
@ -529,7 +526,7 @@ class BitcoinGUI:
def seedb(w, wallet): def seedb(w, wallet):
if wallet.use_encryption: if wallet.use_encryption:
password = password_dialog() password = password_dialog(self.window)
if not password: return if not password: return
else: password = None else: password = None
show_seed_dialog(wallet, password, self.window) show_seed_dialog(wallet, password, self.window)
@ -568,7 +565,7 @@ class BitcoinGUI:
self.window.add(vbox) self.window.add(vbox)
self.window.show_all() self.window.show_all()
self.fee_box.hide() #self.fee_box.hide()
self.context_id = self.status_bar.get_context_id("statusbar") self.context_id = self.status_bar.get_context_id("statusbar")
self.update_status_bar() self.update_status_bar()
@ -578,6 +575,27 @@ class BitcoinGUI:
gobject.idle_add( self.update_status_bar ) gobject.idle_add( self.update_status_bar )
time.sleep(0.5) time.sleep(0.5)
def check_recipient_thread():
old_r = ''
while True:
time.sleep(0.5)
if self.payto_entry.is_focus():
continue
r = self.payto_entry.get_text()
if r != old_r:
old_r = r
r = r.strip()
if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
try:
to_address = self.get_alias(r, interactive=False)
except:
continue
if to_address:
s = r + ' <' + to_address + '>'
gobject.idle_add( lambda: self.payto_entry.set_text(s) )
def update_wallet_thread(): def update_wallet_thread():
while True: while True:
try: try:
@ -625,6 +643,7 @@ class BitcoinGUI:
thread.start_new_thread(update_wallet_thread, ()) thread.start_new_thread(update_wallet_thread, ())
thread.start_new_thread(update_status_bar_thread, ()) thread.start_new_thread(update_status_bar_thread, ())
thread.start_new_thread(check_recipient_thread, ())
self.notebook.set_current_page(0) self.notebook.set_current_page(0)
@ -641,54 +660,62 @@ class BitcoinGUI:
payto = gtk.HBox() payto = gtk.HBox()
payto_label = gtk.Label('Pay to:') payto_label = gtk.Label('Pay to:')
payto_label.set_size_request(100,10) payto_label.set_size_request(100,-1)
payto_label.show()
payto.pack_start(payto_label, False) payto.pack_start(payto_label, False)
payto_entry = gtk.Entry() payto_entry = gtk.Entry()
payto_entry.set_size_request(350, 26) payto_entry.set_size_request(450, 26)
payto_entry.show()
payto.pack_start(payto_entry, False) payto.pack_start(payto_entry, False)
vbox.pack_start(payto, False, False, 5) vbox.pack_start(payto, False, False, 5)
label = gtk.HBox() message = gtk.HBox()
label_label = gtk.Label('Label:') message_label = gtk.Label('Description:')
label_label.set_size_request(100,10) message_label.set_size_request(100,-1)
label_label.show() message.pack_start(message_label, False)
label.pack_start(label_label, False) message_entry = gtk.Entry()
label_entry = gtk.Entry() message_entry.set_size_request(450, 26)
label_entry.set_size_request(350, 26) message.pack_start(message_entry, False)
label_entry.show() vbox.pack_start(message, False, False, 5)
label.pack_start(label_entry, False)
vbox.pack_start(label, False, False, 5)
amount_box = gtk.HBox() amount_box = gtk.HBox()
amount_label = gtk.Label('Amount:') amount_label = gtk.Label('Amount:')
amount_label.set_size_request(100,-1) amount_label.set_size_request(100,-1)
amount_label.show()
amount_box.pack_start(amount_label, False) amount_box.pack_start(amount_label, False)
amount_entry = gtk.Entry() amount_entry = gtk.Entry()
amount_entry.set_size_request(120, -1) amount_entry.set_size_request(120, -1)
amount_entry.show()
amount_box.pack_start(amount_entry, False) amount_box.pack_start(amount_entry, False)
vbox.pack_start(amount_box, False, False, 5) vbox.pack_start(amount_box, False, False, 5)
send_button = gtk.Button("Send")
send_button.show()
amount_box.pack_start(send_button, False, False, 5)
self.fee_box = fee_box = gtk.HBox() self.fee_box = fee_box = gtk.HBox()
fee_label = gtk.Label('Fee:') fee_label = gtk.Label('Fee:')
fee_label.set_size_request(100,10) fee_label.set_size_request(100,-1)
fee_box.pack_start(fee_label, False) fee_box.pack_start(fee_label, False)
fee_entry = gtk.Entry() fee_entry = gtk.Entry()
fee_entry.set_size_request(120, 26) fee_entry.set_size_request(60, 26)
fee_entry.set_has_frame(False)
fee_entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#eeeeee"))
fee_box.pack_start(fee_entry, False) fee_box.pack_start(fee_entry, False)
send_button.connect("clicked", self.do_send, (payto_entry, label_entry, amount_entry, fee_entry))
vbox.pack_start(fee_box, False, False, 5) vbox.pack_start(fee_box, False, False, 5)
end_box = gtk.HBox()
empty_label = gtk.Label('')
empty_label.set_size_request(100,-1)
end_box.pack_start(empty_label, False)
send_button = gtk.Button("Send")
send_button.show()
end_box.pack_start(send_button, False, False, 0)
clear_button = gtk.Button("Clear")
clear_button.show()
end_box.pack_start(clear_button, False, False, 15)
send_button.connect("clicked", self.do_send, (payto_entry, message_entry, amount_entry, fee_entry))
clear_button.connect("clicked", self.do_clear, (payto_entry, message_entry, amount_entry, fee_entry))
vbox.pack_start(end_box, False, False, 5)
# display this line only if there is a signature
payto_sig = gtk.HBox()
payto_sig_id = gtk.Label('')
payto_sig.pack_start(payto_sig_id, False)
vbox.pack_start(payto_sig, True, True, 5)
self.user_fee = False self.user_fee = False
def entry_changed( entry, is_fee ): def entry_changed( entry, is_fee ):
@ -696,7 +723,8 @@ class BitcoinGUI:
fee = numbify(fee_entry) fee = numbify(fee_entry)
if not is_fee: fee = None if not is_fee: fee = None
if amount is None: if amount is None:
self.fee_box.hide(); return #self.fee_box.hide();
return
inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee ) inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
if not is_fee: if not is_fee:
fee_entry.set_text( str( Decimal( fee ) / 100000000 ) ) fee_entry.set_text( str( Decimal( fee ) / 100000000 ) )
@ -716,22 +744,65 @@ class BitcoinGUI:
fee_entry.connect('changed', entry_changed, True) fee_entry.connect('changed', entry_changed, True)
self.payto_entry = payto_entry self.payto_entry = payto_entry
self.payto_amount_entry = amount_entry self.payto_fee_entry = fee_entry
self.payto_label_entry = label_entry self.payto_sig_id = payto_sig_id
self.payto_sig = payto_sig
self.amount_entry = amount_entry
self.message_entry = message_entry
self.add_tab(page, 'Send') self.add_tab(page, 'Send')
def set_send_tab(self, address, amount, label): def set_frozen(self,entry,frozen):
if frozen:
entry.set_editable(False)
entry.set_has_frame(False)
entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#eeeeee"))
else:
entry.set_editable(True)
entry.set_has_frame(True)
entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#ffffff"))
def set_send_tab(self, payto, amount, message, label, identity, signature, cmd):
self.notebook.set_current_page(1) self.notebook.set_current_page(1)
self.payto_entry.set_text(address)
self.payto_label_entry.set_text(label) if signature:
self.payto_amount_entry.set_text(amount) if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
signing_address = self.get_alias(identity, interactive = True)
elif self.wallet.is_valid(identity):
signing_address = identity
else:
signing_address = None
if not signing_address:
return
try:
self.wallet.verify_message(signing_address, signature, cmd )
self.wallet.receipt = (signing_address, signature, cmd)
except:
self.show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.')
payto = amount = label = identity = message = ''
# redundant with aliases
#if label and payto:
# self.labels[payto] = label
if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', payto):
payto_address = self.get_alias(payto, interactive=True)
if payto_address:
payto = payto + ' <' + payto_address + '>'
self.payto_entry.set_text(payto)
self.message_entry.set_text(message)
self.amount_entry.set_text(amount)
if identity:
self.set_frozen(self.payto_entry,True)
self.set_frozen(self.amount_entry,True)
self.set_frozen(self.message_entry,True)
self.payto_sig_id.set_text( ' The bitcoin URI was signed by ' + identity )
else:
self.payto_sig.set_visible(False)
def create_about_tab(self): def create_about_tab(self):
page = gtk.VBox() page = gtk.VBox()
page.show() page.show()
#self.info = gtk.Label('')
#self.info.set_selectable(True)
#page.pack_start(self.info)
tv = gtk.TextView() tv = gtk.TextView()
tv.set_editable(False) tv.set_editable(False)
tv.set_cursor_visible(False) tv.set_cursor_visible(False)
@ -739,14 +810,84 @@ class BitcoinGUI:
self.info = tv.get_buffer() self.info = tv.get_buffer()
self.add_tab(page, 'Wall') self.add_tab(page, 'Wall')
def do_clear(self, w, data):
self.payto_sig.set_visible(False)
self.payto_fee_entry.set_text('')
for entry in [self.payto_entry,self.amount_entry,self.message_entry]:
self.set_frozen(entry,False)
entry.set_text('')
def question(self,msg):
dialog = gtk.MessageDialog( self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, msg)
dialog.show()
result = dialog.run()
dialog.destroy()
return result == gtk.RESPONSE_OK
def get_alias(self, alias, interactive = False):
try:
target, signing_address, auth_name = self.wallet.read_alias(alias)
except BaseException, e:
# raise exception if verify fails (verify the chain)
if interactive:
self.show_message("Alias error: " + e.message)
return
print target, signing_address, auth_name
if auth_name is None:
a = self.wallet.aliases.get(alias)
if not a:
msg = "Warning: the alias '%s' is self-signed. The signing address is %s. Do you want to trust this alias?"%(alias,signing_address)
if interactive and self.question( msg ):
self.wallet.aliases[alias] = (signing_address, target)
else:
target = None
else:
if signing_address != a[0]:
msg = "Warning: the key of alias '%s' has changed since your last visit! It is possible that someone is trying to do something nasty!!!\nDo you accept to change your trusted key?"%alias
if interactive and self.question( msg ):
self.wallet.aliases[alias] = (signing_address, target)
else:
target = None
else:
if signing_address not in self.wallet.authorities.keys():
msg = "The alias: '%s' links to %s\n\nWarning: this alias was signed by an unknown key.\nSigning authority: %s\nSigning address: %s\n\nDo you want to add this key to your list of trusted keys?"%(alias,target,auth_name,signing_address)
if interactive and self.question( msg ):
self.wallet.authorities[signing_address] = auth_name
else:
target = None
if target:
self.wallet.aliases[alias] = (signing_address, target)
self.update_sending_tab()
return target
def do_send(self, w, data): def do_send(self, w, data):
payto_entry, label_entry, amount_entry, fee_entry = data payto_entry, label_entry, amount_entry, fee_entry = data
label = label_entry.get_text() label = label_entry.get_text()
r = payto_entry.get_text()
r = r.strip()
m1 = re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r)
m2 = re.match('(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+) \<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
if m1:
to_address = self.get_alias(r, interactive = True)
if not to_address:
return
elif m2:
to_address = m2.group(5)
else:
to_address = r
to_address = payto_entry.get_text()
if not self.wallet.is_valid(to_address): if not self.wallet.is_valid(to_address):
self.show_message( "invalid bitcoin address") self.show_message( "invalid bitcoin address:\n"+to_address)
return return
try: try:
@ -761,7 +902,7 @@ class BitcoinGUI:
return return
if self.wallet.use_encryption: if self.wallet.use_encryption:
password = password_dialog() password = password_dialog(self.window)
if not password: if not password:
return return
else: else:
@ -782,7 +923,7 @@ class BitcoinGUI:
label_entry.set_text("") label_entry.set_text("")
amount_entry.set_text("") amount_entry.set_text("")
fee_entry.set_text("") fee_entry.set_text("")
self.fee_box.hide() #self.fee_box.hide()
self.update_sending_tab() self.update_sending_tab()
else: else:
self.show_message( msg ) self.show_message( msg )
@ -791,8 +932,20 @@ class BitcoinGUI:
def treeview_button_press(self, treeview, event): def treeview_button_press(self, treeview, event):
if event.type == gtk.gdk._2BUTTON_PRESS: if event.type == gtk.gdk._2BUTTON_PRESS:
c = treeview.get_cursor()[0] c = treeview.get_cursor()[0]
if treeview == self.history_treeview:
tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8) tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8)
self.show_message(tx_details) self.show_message(tx_details)
elif treeview == self.contacts_treeview:
m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0)
a = self.wallet.aliases.get(m)
if a:
if a[0] in self.wallet.authorities.keys():
s = self.wallet.authorities.get(a[0])
else:
s = "self"
msg = 'Alias:'+ m + '\n\nTarget: '+ a[1] + '\nSigned by: ' + s + '\nSigning address:' + a[0]
self.show_message(msg)
def treeview_key_press(self, treeview, event): def treeview_key_press(self, treeview, event):
c = treeview.get_cursor()[0] c = treeview.get_cursor()[0]
@ -800,9 +953,21 @@ class BitcoinGUI:
if c and c[0] == 0: if c and c[0] == 0:
treeview.parent.grab_focus() treeview.parent.grab_focus()
treeview.set_cursor((0,)) treeview.set_cursor((0,))
elif event.keyval == gtk.keysyms.Return and treeview == self.history_treeview: elif event.keyval == gtk.keysyms.Return:
if treeview == self.history_treeview:
tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8) tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8)
self.show_message(tx_details) self.show_message(tx_details)
elif treeview == self.contacts_treeview:
m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0)
a = self.wallet.aliases.get(m)
if a:
if a[0] in self.wallet.authorities.keys():
s = self.wallet.authorities.get(a[0])
else:
s = "self"
msg = 'Alias:'+ m + '\n\nTarget: '+ a[1] + '\nSigned by: ' + s + '\nSigning address:' + a[0]
self.show_message(msg)
return False return False
def create_history_tab(self): def create_history_tab(self):
@ -827,7 +992,7 @@ class BitcoinGUI:
tvcolumn.pack_start(cell, False) tvcolumn.pack_start(cell, False)
tvcolumn.add_attribute(cell, 'text', 2) tvcolumn.add_attribute(cell, 'text', 2)
tvcolumn = gtk.TreeViewColumn('Label') tvcolumn = gtk.TreeViewColumn('Description')
treeview.append_column(tvcolumn) treeview.append_column(tvcolumn)
cell = gtk.CellRendererText() cell = gtk.CellRendererText()
cell.set_property('foreground', 'grey') cell.set_property('foreground', 'grey')
@ -892,7 +1057,10 @@ class BitcoinGUI:
liststore = self.recv_list if is_recv else self.addressbook_list liststore = self.recv_list if is_recv else self.addressbook_list
treeview = gtk.TreeView(model= liststore) treeview = gtk.TreeView(model= liststore)
treeview.connect('key-press-event', self.treeview_key_press) treeview.connect('key-press-event', self.treeview_key_press)
treeview.connect('button-press-event', self.treeview_button_press)
treeview.show() treeview.show()
if not is_recv:
self.contacts_treeview = treeview
tvcolumn = gtk.TreeViewColumn('Address') tvcolumn = gtk.TreeViewColumn('Address')
treeview.append_column(tvcolumn) treeview.append_column(tvcolumn)
@ -993,7 +1161,7 @@ class BitcoinGUI:
address = liststore.get_value( liststore.get_iter(path), 0) address = liststore.get_value( liststore.get_iter(path), 0)
self.payto_entry.set_text( address ) self.payto_entry.set_text( address )
self.notebook.set_current_page(1) self.notebook.set_current_page(1)
self.payto_amount_entry.grab_focus() self.amount_entry.grab_focus()
button.connect("clicked", payto, treeview, liststore) button.connect("clicked", payto, treeview, liststore)
button.show() button.show()
@ -1013,7 +1181,7 @@ class BitcoinGUI:
self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU) self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
self.network_button.set_tooltip_text("Trying to contact %s.\n%d blocks"%(self.wallet.interface.host, self.wallet.interface.blocks)) self.network_button.set_tooltip_text("Trying to contact %s.\n%d blocks"%(self.wallet.interface.host, self.wallet.interface.blocks))
text = "Balance: %s "%( format_satoshis(c) ) text = "Balance: %s "%( format_satoshis(c) )
if u: text += "[+ %s unconfirmed]"%( format_satoshis(u) ) if u: text += "[%s unconfirmed]"%( format_satoshis(u,True) )
if self.error: text = self.error if self.error: text = self.error
self.status_bar.pop(self.context_id) self.status_bar.pop(self.context_id)
self.status_bar.push(self.context_id, text) self.status_bar.push(self.context_id, text)
@ -1033,6 +1201,11 @@ class BitcoinGUI:
def update_sending_tab(self): def update_sending_tab(self):
# detect addresses that are not mine in history, add them here... # detect addresses that are not mine in history, add them here...
self.addressbook_list.clear() self.addressbook_list.clear()
for alias, v in self.wallet.aliases.items():
s, target = v
label = self.wallet.labels.get(alias)
self.addressbook_list.append((alias, label, '-'))
for address in self.wallet.addressbook: for address in self.wallet.addressbook:
label = self.wallet.labels.get(address) label = self.wallet.labels.get(address)
n = 0 n = 0
@ -1062,16 +1235,23 @@ class BitcoinGUI:
if is_default_label: label = tx['default_label'] if is_default_label: label = tx['default_label']
tooltip = tx_hash + "\n%d confirmations"%conf tooltip = tx_hash + "\n%d confirmations"%conf
tx = self.wallet.tx_history.get(tx_hash) # tx = self.wallet.tx_history.get(tx_hash)
details = "Transaction Details:\n\n" details = "Transaction Details:\n\n" \
details+= "Transaction ID:\n" + tx_hash + "\n\n" + "Transaction ID:\n" + tx_hash + "\n\n" \
details+= "Status: %d confirmations\n\n"%conf + "Status: %d confirmations\n\n"%conf \
details+= "Date: %s\n\n"%time_str + "Date: %s\n\n"%time_str \
details+= "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" + "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" \
details+= "Outputs:\n-"+ '\n-'.join(tx['outputs']) + "Outputs:\n-"+ '\n-'.join(tx['outputs'])
r = self.wallet.receipts.get(tx_hash)
if r:
details += "\n_______________________________________" \
+ '\n\nSigned URI: ' + r[2] \
+ "\n\nSigned by: " + r[0] \
+ '\n\nSignature: ' + r[1]
self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label, self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label,
('+' if v>0 else '') + format_satoshis(v), format_satoshis(balance), tooltip, details] ) format_satoshis(v,True), format_satoshis(balance), tooltip, details] )
if cursor: self.history_treeview.set_cursor( cursor ) if cursor: self.history_treeview.set_cursor( cursor )

3
client/interface.py

@ -19,6 +19,9 @@
import random, socket, ast import random, socket, ast
class Interface: class Interface:
def __init__(self): def __init__(self):
self.servers = ['ecdsa.org','electrum.novit.ro'] # list of default servers self.servers = ['ecdsa.org','electrum.novit.ro'] # list of default servers

94
client/msqr.py

@ -0,0 +1,94 @@
# from http://eli.thegreenplace.net/2009/03/07/computing-modular-square-roots-in-python/
def modular_sqrt(a, p):
""" Find a quadratic residue (mod p) of 'a'. p
must be an odd prime.
Solve the congruence of the form:
x^2 = a (mod p)
And returns x. Note that p - x is also a root.
0 is returned is no square root exists for
these a and p.
The Tonelli-Shanks algorithm is used (except
for some simple cases in which the solution
is known from an identity). This algorithm
runs in polynomial time (unless the
generalized Riemann hypothesis is false).
"""
# Simple cases
#
if legendre_symbol(a, p) != 1:
return 0
elif a == 0:
return 0
elif p == 2:
return p
elif p % 4 == 3:
return pow(a, (p + 1) / 4, p)
# Partition p-1 to s * 2^e for an odd s (i.e.
# reduce all the powers of 2 from p-1)
#
s = p - 1
e = 0
while s % 2 == 0:
s /= 2
e += 1
# Find some 'n' with a legendre symbol n|p = -1.
# Shouldn't take long.
#
n = 2
while legendre_symbol(n, p) != -1:
n += 1
# Here be dragons!
# Read the paper "Square roots from 1; 24, 51,
# 10 to Dan Shanks" by Ezra Brown for more
# information
#
# x is a guess of the square root that gets better
# with each iteration.
# b is the "fudge factor" - by how much we're off
# with the guess. The invariant x^2 = ab (mod p)
# is maintained throughout the loop.
# g is used for successive powers of n to update
# both a and b
# r is the exponent - decreases with each update
#
x = pow(a, (s + 1) / 2, p)
b = pow(a, s, p)
g = pow(n, s, p)
r = e
while True:
t = b
m = 0
for m in xrange(r):
if t == 1:
break
t = pow(t, 2, p)
if m == 0:
return x
gs = pow(g, 2 ** (r - m - 1), p)
g = (gs * gs) % p
x = (x * gs) % p
b = (b * g) % p
r = m
def legendre_symbol(a, p):
""" Compute the Legendre symbol a|p using
Euler's criterion. p is a prime, a is
relatively prime to p (if p divides
a, then a|p = 0)
Returns 1 if a has a square root modulo
p, -1 otherwise.
"""
ls = pow(a, (p - 1) / 2, p)
return -1 if ls == p - 1 else ls

2
client/version.py

@ -1,2 +1,2 @@
ELECTRUM_VERSION = "0.37" ELECTRUM_VERSION = "0.38"
SEED_VERSION = 4 # bump this everytime the seed generation is modified SEED_VERSION = 4 # bump this everytime the seed generation is modified

161
client/wallet.py

@ -17,8 +17,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys, base64, os, re, hashlib, socket, getpass, copy, operator, ast, random import sys, base64, os, re, hashlib, copy, operator, ast
from decimal import Decimal
try: try:
import ecdsa import ecdsa
@ -151,10 +150,6 @@ def int_to_hex(i, length=1):
return s.decode('hex')[::-1].encode('hex') return s.decode('hex')[::-1].encode('hex')
# URL decode
_ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE)
urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x)
# AES # AES
EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s)) EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e)) DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e))
@ -218,9 +213,19 @@ def raw_tx( inputs, outputs, for_sig = None ):
from version import ELECTRUM_VERSION, SEED_VERSION def format_satoshis(x, is_diff=False):
from decimal import Decimal
s = str( Decimal(x) /100000000 )
if is_diff and x>0:
s = "+" + s
if not '.' in s: s += '.'
p = s.find('.')
s += " "*( 9 - ( len(s) - p ))
s = " "*( 5 - ( p )) + s
return s
from version import ELECTRUM_VERSION, SEED_VERSION
@ -242,6 +247,11 @@ class Wallet:
self.status = {} # current status of addresses self.status = {} # current status of addresses
self.history = {} self.history = {}
self.labels = {} # labels for addresses and transactions self.labels = {} # labels for addresses and transactions
self.aliases = {} # aliases for addresses
self.authorities = {} # trusted addresses
self.receipts = {} # signed URIs
self.receipt = None # next receipt
self.addressbook = [] # outgoing addresses, for payments self.addressbook = [] # outgoing addresses, for payments
# not saved # not saved
@ -287,7 +297,7 @@ class Wallet:
seed = "%032x"%ecdsa.util.randrange( pow(2,128) ) seed = "%032x"%ecdsa.util.randrange( pow(2,128) )
self.init_mpk(seed) self.init_mpk(seed)
# encrypt # encrypt
self.seed = wallet.pw_encode( seed, password ) self.seed = self.pw_encode( seed, password )
def init_mpk(self,seed): def init_mpk(self,seed):
# public key # public key
@ -323,7 +333,7 @@ class Wallet:
def get_sequence(self,n,for_change): def get_sequence(self,n,for_change):
return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.master_public_key ) ) return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.master_public_key ) )
def get_private_key2(self, address, password): def get_private_key(self, address, password):
""" Privatekey(type,n) = Master_private_key + H(n|S|type) """ """ Privatekey(type,n) = Master_private_key + H(n|S|type) """
order = generator_secp256k1.order() order = generator_secp256k1.order()
@ -347,9 +357,64 @@ class Wallet:
pk = number_to_string(secexp,order) pk = number_to_string(secexp,order)
return pk return pk
def msg_magic(self, message):
return "\x18Bitcoin Signed Message:\n" + chr( len(message) ) + message
def sign_message(self, address, message, password):
def create_new_address2(self, for_change): private_key = ecdsa.SigningKey.from_string( self.get_private_key(address, password), curve = SECP256k1 )
public_key = private_key.get_verifying_key()
signature = private_key.sign_digest( Hash( self.msg_magic( message ) ), sigencode = ecdsa.util.sigencode_string )
assert public_key.verify_digest( signature, Hash( self.msg_magic( message ) ), sigdecode = ecdsa.util.sigdecode_string)
for i in range(4):
sig = base64.b64encode( chr(27+i) + signature )
try:
self.verify_message( address, sig, message)
return sig
except:
continue
else:
raise BaseException("error: cannot sign message")
def verify_message(self, address, signature, message):
""" See http://www.secg.org/download/aid-780/sec1-v2.pdf for the math """
from ecdsa import numbertheory, ellipticcurve, util
import msqr
curve = curve_secp256k1
G = generator_secp256k1
order = G.order()
# extract r,s from signature
sig = base64.b64decode(signature)
if len(sig) != 65: raise BaseException("Wrong encoding")
r,s = util.sigdecode_string(sig[1:], order)
recid = ord(sig[0]) - 27
# 1.1
x = r + (recid/2) * order
# 1.3
alpha = ( x * x * x + curve.a() * x + curve.b() ) % curve.p()
beta = msqr.modular_sqrt(alpha, curve.p())
y = beta if (beta - recid) % 2 == 0 else curve.p() - beta
# 1.4 the constructor checks that nR is at infinity
R = ellipticcurve.Point(curve, x, y, order)
# 1.5 compute e from message:
h = Hash( self.msg_magic( message ) )
e = string_to_number(h)
minus_e = -e % order
# 1.6 compute Q = r^-1 (sR - eG)
inv_r = numbertheory.inverse_mod(r,order)
Q = inv_r * ( s * R + minus_e * G )
public_key = ecdsa.VerifyingKey.from_public_point( Q, curve = SECP256k1 )
# check that Q is the public key
public_key.verify_digest( sig[1:], h, sigdecode = ecdsa.util.sigdecode_string)
# check that we get the original signing address
addr = public_key_to_bc_address( '04'.decode('hex') + public_key.to_string() )
# print addr
if address != addr:
print "bad signature"
raise BaseException("Bad signature")
def create_new_address(self, for_change):
""" Publickey(type,n) = Master_public_key + H(n|S|type)*point """ """ Publickey(type,n) = Master_public_key + H(n|S|type)*point """
curve = SECP256k1 curve = SECP256k1
n = len(self.change_addresses) if for_change else len(self.addresses) n = len(self.change_addresses) if for_change else len(self.addresses)
@ -374,12 +439,12 @@ class Wallet:
is_new = False is_new = False
while True: while True:
if self.change_addresses == []: if self.change_addresses == []:
self.create_new_address2(True) self.create_new_address(True)
is_new = True is_new = True
continue continue
a = self.change_addresses[-1] a = self.change_addresses[-1]
if self.history.get(a): if self.history.get(a):
self.create_new_address2(True) self.create_new_address(True)
is_new = True is_new = True
else: else:
break break
@ -387,13 +452,13 @@ class Wallet:
n = self.gap_limit n = self.gap_limit
while True: while True:
if len(self.addresses) < n: if len(self.addresses) < n:
self.create_new_address2(False) self.create_new_address(False)
is_new = True is_new = True
continue continue
if map( lambda a: self.history.get(a), self.addresses[-n:] ) == n*[[]]: if map( lambda a: self.history.get(a), self.addresses[-n:] ) == n*[[]]:
break break
else: else:
self.create_new_address2(False) self.create_new_address(False)
is_new = True is_new = True
@ -427,6 +492,9 @@ class Wallet:
'labels':self.labels, 'labels':self.labels,
'contacts':self.addressbook, 'contacts':self.addressbook,
'imported_keys':self.imported_keys, 'imported_keys':self.imported_keys,
'aliases':self.aliases,
'authorities':self.authorities,
'receipts':self.receipts,
} }
f = open(self.path,"w") f = open(self.path,"w")
f.write( repr(s) ) f.write( repr(s) )
@ -457,6 +525,9 @@ class Wallet:
self.labels = d.get('labels') self.labels = d.get('labels')
self.addressbook = d.get('contacts') self.addressbook = d.get('contacts')
self.imported_keys = d.get('imported_keys',{}) self.imported_keys = d.get('imported_keys',{})
self.aliases = d.get('aliases',{})
self.authorities = d.get('authorities',{})
self.receipts = d.get('receipts',{})
except: except:
raise BaseException(upgrade_msg) raise BaseException(upgrade_msg)
@ -473,7 +544,7 @@ class Wallet:
if not self.history.get(addr): if not self.history.get(addr):
n = n + 1 n = n + 1
if n < self.gap_limit: if n < self.gap_limit:
new_address = self.create_new_address2(False) new_address = self.create_new_address(False)
self.history[new_address] = [] #get from server self.history[new_address] = [] #get from server
return True, new_address return True, new_address
else: else:
@ -559,7 +630,7 @@ class Wallet:
s_inputs = [] s_inputs = []
for i in range(len(inputs)): for i in range(len(inputs)):
addr, v, p_hash, p_pos, p_scriptPubKey, _, _ = inputs[i] addr, v, p_hash, p_pos, p_scriptPubKey, _, _ = inputs[i]
private_key = ecdsa.SigningKey.from_string( self.get_private_key2(addr, password), curve = SECP256k1 ) private_key = ecdsa.SigningKey.from_string( self.get_private_key(addr, password), curve = SECP256k1 )
public_key = private_key.get_verifying_key() public_key = private_key.get_verifying_key()
pubkey = public_key.to_string() pubkey = public_key.to_string()
tx = filter( raw_tx( inputs, outputs, for_sig = i ) ) tx = filter( raw_tx( inputs, outputs, for_sig = i ) )
@ -634,19 +705,19 @@ class Wallet:
def mktx(self, to_address, amount, label, password, fee=None): def mktx(self, to_address, amount, label, password, fee=None):
if not self.is_valid(to_address): if not self.is_valid(to_address):
raise BaseException("Invalid address") raise BaseException("Invalid address")
inputs, total, fee = wallet.choose_tx_inputs( amount, fee ) inputs, total, fee = self.choose_tx_inputs( amount, fee )
if not inputs: if not inputs:
raise BaseException("Not enough funds") raise BaseException("Not enough funds")
outputs = wallet.choose_tx_outputs( to_address, amount, fee, total ) outputs = self.choose_tx_outputs( to_address, amount, fee, total )
s_inputs = wallet.sign_inputs( inputs, outputs, password ) s_inputs = self.sign_inputs( inputs, outputs, password )
tx = filter( raw_tx( s_inputs, outputs ) ) tx = filter( raw_tx( s_inputs, outputs ) )
if to_address not in self.addressbook: if to_address not in self.addressbook:
self.addressbook.append(to_address) self.addressbook.append(to_address)
if label: if label:
tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex') tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex')
wallet.labels[tx_hash] = label self.labels[tx_hash] = label
wallet.save() self.save()
return tx return tx
def sendtx(self, tx): def sendtx(self, tx):
@ -654,5 +725,51 @@ class Wallet:
out = self.interface.send_tx(tx) out = self.interface.send_tx(tx)
if out != tx_hash: if out != tx_hash:
return False, "error: " + out return False, "error: " + out
if self.receipt:
self.receipts[tx_hash] = self.receipt
self.receipt = None
return True, out return True, out
def read_alias(self, alias):
# this might not be the right place for this function.
import urllib
m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias)
m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias)
if m1:
url = 'http://' + m1.group(2) + '/bitcoin.id/' + m1.group(1)
elif m2:
url = 'http://' + alias + '/bitcoin.id'
else:
return ''
try:
lines = urllib.urlopen(url).readlines()
except:
return ''
# line 0
line = lines[0].strip().split(':')
if len(line) == 1:
auth_name = None
target = signing_addr = line[0]
else:
target, auth_name, signing_addr, signature = line
msg = "alias:%s:%s:%s"%(alias,target,auth_name)
print msg, signature
self.verify_message(signing_addr, signature, msg)
# other lines are signed updates
for line in lines[1:]:
line = line.strip()
if not line: continue
line = line.split(':')
previous = target
print repr(line)
target, signature = line
self.verify_message(previous, signature, "alias:%s:%s"%(alias,target))
if not self.is_valid(target):
raise BaseException("Invalid bitcoin address")
return target, signing_addr, auth_name

Loading…
Cancel
Save