Browse Source

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

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

79
client/electrum

@ -16,15 +16,22 @@
# 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
import re, sys, getpass
from optparse import OptionParser
from wallet import Wallet
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__':
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))
@ -52,24 +59,30 @@ if __name__ == '__main__':
import gui
gui.init_wallet(wallet)
gui = gui.BitcoinGUI(wallet)
if re.match('^bitcoin:', cmd):
o = cmd[8:].split('?')
address = o[0]
if len(o)>1:
params = o[1].split('&')
else:
params = []
cmd = 'gui'
amount = ''
label = ''
amount = label = message = signature = identity = ''
for p in params:
k,v = p.split('=')
v = urldecode(v)
if k=='amount': amount = v
elif k=='label': label = v
else: print k,v
gui.set_send_tab(address, amount, label)
uv = urldecode(v)
if k == 'amount': amount = uv
elif k == 'message': message = uv
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, message, label, identity, signature, cmd)
gui.main()
wallet.save()
@ -98,7 +111,7 @@ if __name__ == '__main__':
host = raw_input("server (default:%s):"%wallet.interface.host)
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 host: wallet.interface.host = host
if port: wallet.interface.port = int(port)
@ -136,13 +149,13 @@ if __name__ == '__main__':
cmd = 'help'
# 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.update()
wallet.save()
# 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
# check password
try:
@ -193,6 +206,8 @@ if __name__ == '__main__':
print "syntax: mktx <recipient> <amount> [label]"
elif cmd2 == 'seed':
print "show generation seed of your wallet. password protected."
elif cmd2 == 'eval':
print "run python eval on an object"
elif cmd == 'seed':
import mnemonic
@ -211,21 +226,25 @@ if __name__ == '__main__':
if addrs == []:
c, u = wallet.get_balance()
if u:
print c*1e-8, u*1e-8
print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
else:
print c*1e-8
print Decimal( c ) / 100000000
else:
for addr in addrs:
c, u = wallet.get_addr_balance(addr)
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:
print "%s %s" % (addr, c*1e-8)
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):
@ -240,7 +259,7 @@ if __name__ == '__main__':
for item in h:
if item['is_in']: ni += 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=''
if options.show_keys:
pk = wallet.get_private_key2(addr, password)
@ -252,9 +271,8 @@ if __name__ == '__main__':
b = 0
for line in lines:
import datetime
v = 1.*line['value']/1e8
v = line['value']
b += v
v_str = "%f"%v if v<0 else "+%f"%v
try:
time_str = datetime.datetime.fromtimestamp( line['nTime'])
except:
@ -264,8 +282,8 @@ if __name__ == '__main__':
if not label: label = line['tx_hash']
else: label = label + ' '*(64 - len(label) )
print time_str, " ", label, " ", v_str, " ", "%f"%b
print "# balance: ", b
print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
print "# balance: ", format_satoshis(b)
elif cmd == 'label':
try:
@ -323,3 +341,16 @@ if __name__ == '__main__':
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

324
client/gui.py

@ -17,7 +17,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import datetime
import thread, time, ast, sys
import thread, time, ast, sys, re
import socket, traceback
import pygtk
pygtk.require('2.0')
@ -28,12 +28,7 @@ from decimal import Decimal
gtk.gdk.threads_init()
APP_NAME = "Electrum"
def format_satoshis(x):
s = str( Decimal(x) /100000000 )
if not '.' in s: s += '.'
p = s.find('.')
s += " "*( 9 - ( len(s) - p ))
return s
from wallet import format_satoshis
def numbify(entry, is_int = False):
text = entry.get_text().strip()
@ -124,6 +119,7 @@ def init_wallet(wallet):
else:
# ask for the server.
wallet.interface.get_servers()
run_network_dialog( wallet, parent=None )
# 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:
wallet.interface.host = host
wallet.interface.set_port( port )
wallet.save()
wallet.interface.is_connected = False
if parent:
wallet.save()
@ -399,8 +396,8 @@ def password_line(label):
password.show()
return password, password_entry
def password_dialog():
dialog = gtk.MessageDialog( None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
def password_dialog(parent):
dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, "Please enter your password.")
dialog.get_image().set_visible(False)
current_pw, current_pw_entry = password_line('Password:')
@ -529,7 +526,7 @@ class BitcoinGUI:
def seedb(w, wallet):
if wallet.use_encryption:
password = password_dialog()
password = password_dialog(self.window)
if not password: return
else: password = None
show_seed_dialog(wallet, password, self.window)
@ -568,7 +565,7 @@ class BitcoinGUI:
self.window.add(vbox)
self.window.show_all()
self.fee_box.hide()
#self.fee_box.hide()
self.context_id = self.status_bar.get_context_id("statusbar")
self.update_status_bar()
@ -578,6 +575,27 @@ class BitcoinGUI:
gobject.idle_add( self.update_status_bar )
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():
while True:
try:
@ -625,6 +643,7 @@ class BitcoinGUI:
thread.start_new_thread(update_wallet_thread, ())
thread.start_new_thread(update_status_bar_thread, ())
thread.start_new_thread(check_recipient_thread, ())
self.notebook.set_current_page(0)
@ -635,60 +654,68 @@ class BitcoinGUI:
def create_send_tab(self):
page = vbox = gtk.VBox()
page.show()
payto = gtk.HBox()
payto_label = gtk.Label('Pay to:')
payto_label.set_size_request(100,10)
payto_label.show()
payto_label.set_size_request(100,-1)
payto.pack_start(payto_label, False)
payto_entry = gtk.Entry()
payto_entry.set_size_request(350, 26)
payto_entry.show()
payto_entry.set_size_request(450, 26)
payto.pack_start(payto_entry, False)
vbox.pack_start(payto, False, False, 5)
label = gtk.HBox()
label_label = gtk.Label('Label:')
label_label.set_size_request(100,10)
label_label.show()
label.pack_start(label_label, False)
label_entry = gtk.Entry()
label_entry.set_size_request(350, 26)
label_entry.show()
label.pack_start(label_entry, False)
vbox.pack_start(label, False, False, 5)
message = gtk.HBox()
message_label = gtk.Label('Description:')
message_label.set_size_request(100,-1)
message.pack_start(message_label, False)
message_entry = gtk.Entry()
message_entry.set_size_request(450, 26)
message.pack_start(message_entry, False)
vbox.pack_start(message, False, False, 5)
amount_box = gtk.HBox()
amount_label = gtk.Label('Amount:')
amount_label.set_size_request(100,-1)
amount_label.show()
amount_box.pack_start(amount_label, False)
amount_entry = gtk.Entry()
amount_entry.set_size_request(120, -1)
amount_entry.show()
amount_box.pack_start(amount_entry, False)
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()
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_entry = gtk.Entry()
fee_entry.set_size_request(120, 26)
fee_entry.set_has_frame(False)
fee_entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#eeeeee"))
fee_entry.set_size_request(60, 26)
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)
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
def entry_changed( entry, is_fee ):
@ -696,7 +723,8 @@ class BitcoinGUI:
fee = numbify(fee_entry)
if not is_fee: fee = 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 )
if not is_fee:
fee_entry.set_text( str( Decimal( fee ) / 100000000 ) )
@ -713,25 +741,68 @@ class BitcoinGUI:
self.error = 'Not enough funds'
amount_entry.connect('changed', entry_changed, False)
fee_entry.connect('changed', entry_changed, True)
fee_entry.connect('changed', entry_changed, True)
self.payto_entry = payto_entry
self.payto_amount_entry = amount_entry
self.payto_label_entry = label_entry
self.payto_fee_entry = fee_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')
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.payto_entry.set_text(address)
self.payto_label_entry.set_text(label)
self.payto_amount_entry.set_text(amount)
if signature:
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):
page = gtk.VBox()
page.show()
#self.info = gtk.Label('')
#self.info.set_selectable(True)
#page.pack_start(self.info)
tv = gtk.TextView()
tv.set_editable(False)
tv.set_cursor_visible(False)
@ -739,14 +810,84 @@ class BitcoinGUI:
self.info = tv.get_buffer()
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):
payto_entry, label_entry, amount_entry, fee_entry = data
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):
self.show_message( "invalid bitcoin address")
self.show_message( "invalid bitcoin address:\n"+to_address)
return
try:
@ -761,7 +902,7 @@ class BitcoinGUI:
return
if self.wallet.use_encryption:
password = password_dialog()
password = password_dialog(self.window)
if not password:
return
else:
@ -782,7 +923,7 @@ class BitcoinGUI:
label_entry.set_text("")
amount_entry.set_text("")
fee_entry.set_text("")
self.fee_box.hide()
#self.fee_box.hide()
self.update_sending_tab()
else:
self.show_message( msg )
@ -791,8 +932,20 @@ class BitcoinGUI:
def treeview_button_press(self, treeview, event):
if event.type == gtk.gdk._2BUTTON_PRESS:
c = treeview.get_cursor()[0]
tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8)
self.show_message(tx_details)
if treeview == self.history_treeview:
tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8)
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):
c = treeview.get_cursor()[0]
@ -800,9 +953,21 @@ class BitcoinGUI:
if c and c[0] == 0:
treeview.parent.grab_focus()
treeview.set_cursor((0,))
elif event.keyval == gtk.keysyms.Return and treeview == self.history_treeview:
tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8)
self.show_message(tx_details)
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)
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
def create_history_tab(self):
@ -827,7 +992,7 @@ class BitcoinGUI:
tvcolumn.pack_start(cell, False)
tvcolumn.add_attribute(cell, 'text', 2)
tvcolumn = gtk.TreeViewColumn('Label')
tvcolumn = gtk.TreeViewColumn('Description')
treeview.append_column(tvcolumn)
cell = gtk.CellRendererText()
cell.set_property('foreground', 'grey')
@ -892,7 +1057,10 @@ class BitcoinGUI:
liststore = self.recv_list if is_recv else self.addressbook_list
treeview = gtk.TreeView(model= liststore)
treeview.connect('key-press-event', self.treeview_key_press)
treeview.connect('button-press-event', self.treeview_button_press)
treeview.show()
if not is_recv:
self.contacts_treeview = treeview
tvcolumn = gtk.TreeViewColumn('Address')
treeview.append_column(tvcolumn)
@ -993,7 +1161,7 @@ class BitcoinGUI:
address = liststore.get_value( liststore.get_iter(path), 0)
self.payto_entry.set_text( address )
self.notebook.set_current_page(1)
self.payto_amount_entry.grab_focus()
self.amount_entry.grab_focus()
button.connect("clicked", payto, treeview, liststore)
button.show()
@ -1013,7 +1181,7 @@ class BitcoinGUI:
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))
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
self.status_bar.pop(self.context_id)
self.status_bar.push(self.context_id, text)
@ -1033,6 +1201,11 @@ class BitcoinGUI:
def update_sending_tab(self):
# detect addresses that are not mine in history, add them here...
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:
label = self.wallet.labels.get(address)
n = 0
@ -1062,16 +1235,23 @@ class BitcoinGUI:
if is_default_label: label = tx['default_label']
tooltip = tx_hash + "\n%d confirmations"%conf
tx = self.wallet.tx_history.get(tx_hash)
details = "Transaction Details:\n\n"
details+= "Transaction ID:\n" + tx_hash + "\n\n"
details+= "Status: %d confirmations\n\n"%conf
details+= "Date: %s\n\n"%time_str
details+= "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n"
details+= "Outputs:\n-"+ '\n-'.join(tx['outputs'])
# tx = self.wallet.tx_history.get(tx_hash)
details = "Transaction Details:\n\n" \
+ "Transaction ID:\n" + tx_hash + "\n\n" \
+ "Status: %d confirmations\n\n"%conf \
+ "Date: %s\n\n"%time_str \
+ "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" \
+ "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,
('+' 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 )

3
client/interface.py

@ -19,6 +19,9 @@
import random, socket, ast
class Interface:
def __init__(self):
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

163
client/wallet.py

@ -17,8 +17,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys, base64, os, re, hashlib, socket, getpass, copy, operator, ast, random
from decimal import Decimal
import sys, base64, os, re, hashlib, copy, operator, ast
try:
import ecdsa
@ -151,10 +150,6 @@ def int_to_hex(i, length=1):
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
EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
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.history = {}
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
# not saved
@ -287,7 +297,7 @@ class Wallet:
seed = "%032x"%ecdsa.util.randrange( pow(2,128) )
self.init_mpk(seed)
# encrypt
self.seed = wallet.pw_encode( seed, password )
self.seed = self.pw_encode( seed, password )
def init_mpk(self,seed):
# public key
@ -323,7 +333,7 @@ class Wallet:
def get_sequence(self,n,for_change):
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) """
order = generator_secp256k1.order()
@ -346,10 +356,65 @@ class Wallet:
pk = number_to_string(secexp,order)
return pk
def create_new_address2(self, for_change):
def msg_magic(self, message):
return "\x18Bitcoin Signed Message:\n" + chr( len(message) ) + message
def sign_message(self, address, message, password):
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 """
curve = SECP256k1
n = len(self.change_addresses) if for_change else len(self.addresses)
@ -374,12 +439,12 @@ class Wallet:
is_new = False
while True:
if self.change_addresses == []:
self.create_new_address2(True)
self.create_new_address(True)
is_new = True
continue
a = self.change_addresses[-1]
if self.history.get(a):
self.create_new_address2(True)
self.create_new_address(True)
is_new = True
else:
break
@ -387,13 +452,13 @@ class Wallet:
n = self.gap_limit
while True:
if len(self.addresses) < n:
self.create_new_address2(False)
self.create_new_address(False)
is_new = True
continue
if map( lambda a: self.history.get(a), self.addresses[-n:] ) == n*[[]]:
break
else:
self.create_new_address2(False)
self.create_new_address(False)
is_new = True
@ -427,6 +492,9 @@ class Wallet:
'labels':self.labels,
'contacts':self.addressbook,
'imported_keys':self.imported_keys,
'aliases':self.aliases,
'authorities':self.authorities,
'receipts':self.receipts,
}
f = open(self.path,"w")
f.write( repr(s) )
@ -457,6 +525,9 @@ class Wallet:
self.labels = d.get('labels')
self.addressbook = d.get('contacts')
self.imported_keys = d.get('imported_keys',{})
self.aliases = d.get('aliases',{})
self.authorities = d.get('authorities',{})
self.receipts = d.get('receipts',{})
except:
raise BaseException(upgrade_msg)
@ -473,7 +544,7 @@ class Wallet:
if not self.history.get(addr):
n = n + 1
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
return True, new_address
else:
@ -559,7 +630,7 @@ class Wallet:
s_inputs = []
for i in range(len(inputs)):
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()
pubkey = public_key.to_string()
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):
if not self.is_valid(to_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:
raise BaseException("Not enough funds")
outputs = wallet.choose_tx_outputs( to_address, amount, fee, total )
s_inputs = wallet.sign_inputs( inputs, outputs, password )
outputs = self.choose_tx_outputs( to_address, amount, fee, total )
s_inputs = self.sign_inputs( inputs, outputs, password )
tx = filter( raw_tx( s_inputs, outputs ) )
if to_address not in self.addressbook:
self.addressbook.append(to_address)
if label:
tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex')
wallet.labels[tx_hash] = label
wallet.save()
self.labels[tx_hash] = label
self.save()
return tx
def sendtx(self, tx):
@ -654,5 +725,51 @@ class Wallet:
out = self.interface.send_tx(tx)
if out != tx_hash:
return False, "error: " + out
if self.receipt:
self.receipts[tx_hash] = self.receipt
self.receipt = None
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