tMerge branch 'master' of gitorious.org:electrum/electrum - electrum - Electrum Bitcoin wallet
 (HTM) git clone https://git.parazyd.org/electrum
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
       ---
 (DIR) commit eb060854a156d72b5af8f1c029f805a3e476fcb4
 (DIR) parent b0b4dfaa186dc1b8621ff947051360447320c092
 (HTM) Author: ThomasV <thomasv@gitorious>
       Date:   Tue,  7 Feb 2012 19:48:15 +0300
       
       Merge branch 'master' of gitorious.org:electrum/electrum
       
       Diffstat:
         M client/electrum                     |      79 +++++++++++++++++++++----------
         M client/gui.py                       |     324 ++++++++++++++++++++++++-------
         M client/interface.py                 |       3 +++
         A client/msqr.py                      |      94 +++++++++++++++++++++++++++++++
         M client/version.py                   |       2 +-
         M client/wallet.py                    |     163 ++++++++++++++++++++++++++-----
       
       6 files changed, 545 insertions(+), 120 deletions(-)
       ---
 (DIR) diff --git a/client/electrum b/client/electrum
       t@@ -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))
        
       t@@ -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()
       t@@ -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)
       t@@ -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:
       t@@ -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
       t@@ -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):
       t@@ -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)
       t@@ -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:
       t@@ -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:
       t@@ -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
       +        
       +
 (DIR) diff --git a/client/gui.py b/client/gui.py
       t@@ -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')
       t@@ -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()
       t@@ -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.
       t@@ -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()
        
        
        
       t@@ -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:')
       t@@ -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)
       t@@ -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()
       t@@ -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:
       t@@ -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)
        
        
       t@@ -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 ):
       t@@ -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 ) )
       t@@ -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)
       t@@ -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:
       t@@ -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:
       t@@ -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 )
       t@@ -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]
       t@@ -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):
       t@@ -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')
       t@@ -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)
       t@@ -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()
       t@@ -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) 
       t@@ -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 
       t@@ -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 )
        
        
 (DIR) diff --git a/client/interface.py b/client/interface.py
       t@@ -19,6 +19,9 @@
        
        import random, socket, ast
        
       +        
       +
       +
        class Interface:
            def __init__(self):
                self.servers = ['ecdsa.org','electrum.novit.ro']  # list of default servers
 (DIR) diff --git a/client/msqr.py b/client/msqr.py
       t@@ -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
 (DIR) diff --git a/client/version.py b/client/version.py
       t@@ -1,2 +1,2 @@
       -ELECTRUM_VERSION = "0.37"
       +ELECTRUM_VERSION = "0.38"
        SEED_VERSION = 4  # bump this everytime the seed generation is modified
 (DIR) diff --git a/client/wallet.py b/client/wallet.py
       t@@ -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  
       t@@ -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))
       t@@ -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
        
        
        
       t@@ -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
       t@@ -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
       t@@ -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()
                
       t@@ -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)
       t@@ -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
       t@@ -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
        
        
       t@@ -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) )
       t@@ -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)
        
       t@@ -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:
       t@@ -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 ) )
       t@@ -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):
       t@@ -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