move http aliases to separate plugin
authorthomasv <thomasv@gitorious>
Thu, 14 Mar 2013 15:32:05 +0000 (16:32 +0100)
committerthomasv <thomasv@gitorious>
Fri, 15 Mar 2013 14:48:28 +0000 (15:48 +0100)
gui/gui_classic.py
gui/gui_gtk.py
lib/util.py
lib/wallet.py
plugins/aliases.py [new file with mode: 0644]

index 9e8139e..1d80132 100644 (file)
@@ -61,8 +61,6 @@ elif platform.system() == 'Darwin':
 else:
     MONOSPACE_FONT = 'monospace'
 
-ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'    
-
 from electrum import ELECTRUM_VERSION
 import re
 
@@ -426,23 +424,6 @@ class ElectrumWindow(QMainWindow):
     def timer_actions(self):
         self.run_hook('timer_actions')
             
-        if self.payto_e.hasFocus():
-            return
-        r = unicode( self.payto_e.text() )
-        if r != self.previous_payto_e:
-            self.previous_payto_e = r
-            r = r.strip()
-            if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
-                try:
-                    to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
-                except:
-                    return
-                if to_address:
-                    s = r + '  <' + to_address + '>'
-                    self.payto_e.setText(s)
-
-
-
     def update_status(self):
         if self.wallet.interface and self.wallet.interface.is_connected:
             if not self.wallet.up_to_date:
@@ -591,10 +572,11 @@ class ElectrumWindow(QMainWindow):
 
     def address_label_clicked(self, item, column, l, column_addr, column_label):
         if column == column_label and item.isSelected():
+            is_editable = item.data(0, 32).toBool()
+            if not is_editable:
+                return
             addr = unicode( item.text(column_addr) )
             label = unicode( item.text(column_label) )
-            if label in self.wallet.aliases.keys():
-                return
             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
             l.editItem( item, column )
             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
@@ -605,17 +587,14 @@ class ElectrumWindow(QMainWindow):
         if column == column_label:
             addr = unicode( item.text(column_addr) )
             text = unicode( item.text(column_label) )
-            changed = False
+            is_editable = item.data(0, 32).toBool()
+            if not is_editable:
+                return
 
-            if text in self.wallet.aliases.keys():
-                print_error("Error: This is one of your aliases")
-                label = self.wallet.labels.get(addr,'')
-                item.setText(column_label, QString(label))
-            else:
-                changed = self.set_label(addr, text)
-                if changed:
-                    self.update_history_tab()
-                    self.update_completions()
+            changed = self.set_label(addr, text)
+            if changed:
+                self.update_history_tab()
+                self.update_completions()
                 
             self.current_item_changed(item)
 
@@ -778,8 +757,8 @@ class ElectrumWindow(QMainWindow):
         for addr,label in self.wallet.labels.items():
             if addr in self.wallet.addressbook:
                 l.append( label + '  <' + addr + '>')
-        l = l + self.wallet.aliases.keys()
 
+        self.run_hook('update_completions', l)
         self.completions.setStringList(l)
 
 
@@ -794,19 +773,9 @@ class ElectrumWindow(QMainWindow):
         r = unicode( self.payto_e.text() )
         r = r.strip()
 
-        # alias
-        m1 = re.match(ALIAS_REGEXP, r)
         # label or alias, with address in brackets
-        m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
-        
-        if m1:
-            to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
-            if not to_address:
-                return
-        elif m2:
-            to_address = m2.group(2)
-        else:
-            to_address = r
+        m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
+        to_address = m.group(2) if m else r
 
         if not is_valid(to_address):
             QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
@@ -858,10 +827,19 @@ class ElectrumWindow(QMainWindow):
 
 
     def set_url(self, url):
-        payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
+        address, amount, label, message, signature, identity, url = util.parse_url(url)
+
+        if label and self.wallet.labels.get(address) != label:
+            if self.question('Give label "%s" to address %s ?'%(label,address)):
+                if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
+                    self.wallet.addressbook.append(address)
+                self.set_label(address, label)
+
+        self.run_hook('set_url', url, self.show_message, self.question)
+
         self.tabs.setCurrentIndex(1)
-        label = self.wallet.labels.get(payto)
-        m_addr = label + '  <'+ payto+'>' if label else payto
+        label = self.wallet.labels.get(address)
+        m_addr = label + '  <'+ address +'>' if label else address
         self.payto_e.setText(m_addr)
 
         self.message_e.setText(message)
@@ -1029,50 +1007,41 @@ class ElectrumWindow(QMainWindow):
         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
 
 
-    def payto(self, x, is_alias):
-        if not x: return
-        if is_alias:
-            label = x
-            m_addr = label
-        else:
-            addr = x
-            label = self.wallet.labels.get(addr)
-            m_addr = label + '  <' + addr + '>' if label else addr
+    def payto(self, addr):
+        if not addr: return
+        label = self.wallet.labels.get(addr)
+        m_addr = label + '  <' + addr + '>' if label else addr
         self.tabs.setCurrentIndex(1)
         self.payto_e.setText(m_addr)
         self.amount_e.setFocus()
 
-    def delete_contact(self, x, is_alias):
+
+    def delete_contact(self, x):
         if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
-            if not is_alias and x in self.wallet.addressbook:
+            if x in self.wallet.addressbook:
                 self.wallet.addressbook.remove(x)
                 self.set_label(x, None)
-            elif is_alias and x in self.wallet.aliases:
-                self.wallet.aliases.pop(x)
-            self.update_history_tab()
-            self.update_contacts_tab()
-            self.update_completions()
+                self.update_history_tab()
+                self.update_contacts_tab()
+                self.update_completions()
 
-    def create_contact_menu(self, position):
-        # fixme: this function apparently has a side effect.
-        # if it is not called the menu pops up several times
-        #self.contacts_list.selectedIndexes() 
 
+    def create_contact_menu(self, position):
         item = self.contacts_list.itemAt(position)
         if not item: return
         addr = unicode(item.text(0))
         label = unicode(item.text(1))
-        is_alias = label in self.wallet.aliases.keys()
-        x = label if is_alias else addr
+        is_editable = item.data(0,32).toBool()
+        payto_addr = item.data(0,33).toString()
         menu = QMenu()
         menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
-        menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
+        menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
         menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
-        if not is_alias:
+        if is_editable:
             menu.addAction(_("Edit label"), lambda: self.edit_label(False))
-        else:
-            menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
-        menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
+            menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
+
+        self.run_hook('create_contact_menu', menu, item)
         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
 
 
@@ -1157,31 +1126,13 @@ class ElectrumWindow(QMainWindow):
         # we use column 1 because column 0 may be hidden
         l.setCurrentItem(l.topLevelItem(0),1)
 
-    def show_contact_details(self, m):
-        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-signed"
-            msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
-            QMessageBox.information(self, 'Alias', msg, 'OK')
 
     def update_contacts_tab(self):
 
         l = self.contacts_list
         l.clear()
 
-        alias_targets = []
-        for alias, v in self.wallet.aliases.items():
-            s, target = v
-            alias_targets.append(target)
-            item = QTreeWidgetItem( [ target, alias, '-'] )
-            item.setBackgroundColor(0, QColor('lightgray'))
-            l.addTopLevelItem(item)
-            
         for address in self.wallet.addressbook:
-            if address in alias_targets: continue
             label = self.wallet.labels.get(address,'')
             n = 0 
             for tx in self.wallet.transactions.values():
@@ -1189,11 +1140,17 @@ class ElectrumWindow(QMainWindow):
             
             item = QTreeWidgetItem( [ address, label, "%d"%n] )
             item.setFont(0, QFont(MONOSPACE_FONT))
+            # 32 = label can be edited (bool)
+            item.setData(0,32, True)
+            # 33 = payto string
+            item.setData(0,33, address)
             l.addTopLevelItem(item)
 
+        self.run_hook('update_contacts_tab', l)
         l.setCurrentItem(l.topLevelItem(0))
 
 
+
     def create_console_tab(self):
         from qt_console import Console
         self.console = console = Console()
index bbab63d..55b9342 100644 (file)
@@ -868,14 +868,14 @@ class ElectrumWindow:
                 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-signed"
-                    msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
-                    self.show_message(msg)
+                #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-signed"
+                #    msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
+                #    self.show_message(msg)
             
 
     def treeview_key_press(self, treeview, event):
@@ -890,14 +890,14 @@ class ElectrumWindow:
                 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)
+                #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
 
@@ -1145,10 +1145,10 @@ class ElectrumWindow:
     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 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)
@@ -1176,7 +1176,7 @@ class ElectrumWindow:
 
             label, is_default_label = self.wallet.get_label(tx_hash)
             tooltip = tx_hash + "\n%d confirmations"%conf if tx_hash else ''
-            details = self.wallet.get_tx_details(tx_hash)
+            details = self.get_tx_details(tx_hash)
 
             self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label,
                                         format_satoshis(value,True,self.wallet.num_zeros),
@@ -1184,6 +1184,40 @@ class ElectrumWindow:
         if cursor: self.history_treeview.set_cursor( cursor )
 
 
+    def get_tx_details(self, tx_hash):
+        import datetime
+        if not tx_hash: return ''
+        tx = self.wallet.transactions.get(tx_hash)
+        is_mine, v, fee = self.wallet.get_tx_value(tx)
+        conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
+
+        if timestamp:
+            time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
+        else:
+            time_str = 'pending'
+
+        inputs = map(lambda x: x.get('address'), tx.inputs)
+        outputs = map(lambda x: x.get('address'), tx.d['outputs'])
+        tx_details = "Transaction Details" +"\n\n" \
+            + "Transaction ID:\n" + tx_hash + "\n\n" \
+            + "Status: %d confirmations\n"%conf
+        if is_mine:
+            if fee: 
+                tx_details += "Amount sent: %s\n"% format_satoshis(v-fee, False) \
+                              + "Transaction fee: %s\n"% format_satoshis(fee, False)
+            else:
+                tx_details += "Amount sent: %s\n"% format_satoshis(v, False) \
+                              + "Transaction fee: unknown\n"
+        else:
+            tx_details += "Amount received: %s\n"% format_satoshis(v, False) \
+
+        tx_details += "Date: %s\n\n"%time_str \
+            + "Inputs:\n-"+ '\n-'.join(inputs) + "\n\n" \
+            + "Outputs:\n-"+ '\n-'.join(outputs)
+
+        return tx_details
+
+
 
     def newaddress_dialog(self, w):
 
index a76be6b..50d8ec3 100644 (file)
@@ -1,4 +1,4 @@
-import os, sys
+import os, sys, re
 import platform
 import shutil
 from datetime import datetime
@@ -147,3 +147,36 @@ def age(from_date, since_date = None, target_tz=None, include_seconds=False):
         return "about 1 year ago"
     else:
         return "over %d years ago" % (round(distance_in_minutes / 525600))
+
+
+
+
+# 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)
+
+def parse_url(url):
+    o = url[8:].split('?')
+    address = o[0]
+    if len(o)>1:
+        params = o[1].split('&')
+    else:
+        params = []
+
+    amount = label = message = signature = identity = ''
+    for p in params:
+        k,v = p.split('=')
+        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(':')
+            url = url.replace('&%s=%s'%(k,v),'')
+        else: 
+            print k,v
+
+    return address, amount, label, message, signature, identity, url
+
+
+
index 3814cd2..0383e4a 100644 (file)
@@ -33,9 +33,6 @@ import time
 from util import print_msg, print_error, user_dir, format_satoshis
 from bitcoin import *
 
-# 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 encryption
 EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
@@ -82,11 +79,8 @@ class Wallet:
         self.use_encryption        = config.get('use_encryption', False)
         self.seed                  = config.get('seed', '')               # encrypted
         self.labels                = config.get('labels', {})
-        self.aliases               = config.get('aliases', {})            # aliases for addresses
-        self.authorities           = config.get('authorities', {})        # trusted addresses
         self.frozen_addresses      = config.get('frozen_addresses',[])
         self.prioritized_addresses = config.get('prioritized_addresses',[])
-        self.receipts              = config.get('receipts',{})            # signed URIs
         self.addressbook           = config.get('contacts', [])
         self.imported_keys         = config.get('imported_keys',{})
         self.history               = config.get('addr_history',{})        # address -> list(txid, height)
@@ -425,46 +419,6 @@ class Wallet:
         return tx.get_value(addresses, self.prevout_values)
 
 
-    def get_tx_details(self, tx_hash):
-        import datetime
-        if not tx_hash: return ''
-        tx = self.transactions.get(tx_hash)
-        is_mine, v, fee = self.get_tx_value(tx)
-        conf, timestamp = self.verifier.get_confirmations(tx_hash)
-
-        if timestamp:
-            time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
-        else:
-            time_str = 'pending'
-
-        inputs = map(lambda x: x.get('address'), tx.inputs)
-        outputs = map(lambda x: x.get('address'), tx.d['outputs'])
-        tx_details = "Transaction Details" +"\n\n" \
-            + "Transaction ID:\n" + tx_hash + "\n\n" \
-            + "Status: %d confirmations\n"%conf
-        if is_mine:
-            if fee: 
-                tx_details += "Amount sent: %s\n"% format_satoshis(v-fee, False) \
-                              + "Transaction fee: %s\n"% format_satoshis(fee, False)
-            else:
-                tx_details += "Amount sent: %s\n"% format_satoshis(v, False) \
-                              + "Transaction fee: unknown\n"
-        else:
-            tx_details += "Amount received: %s\n"% format_satoshis(v, False) \
-
-        tx_details += "Date: %s\n\n"%time_str \
-            + "Inputs:\n-"+ '\n-'.join(inputs) + "\n\n" \
-            + "Outputs:\n-"+ '\n-'.join(outputs)
-
-        r = self.receipts.get(tx_hash)
-        if r:
-            tx_details += "\n_______________________________________" \
-                + '\n\nSigned URI: ' + r[2] \
-                + "\n\nSigned by: " + r[0] \
-                + '\n\nSignature: ' + r[1]
-
-        return tx_details
-
     
     def update_tx_outputs(self, tx_hash):
         tx = self.transactions.get(tx_hash)
@@ -813,48 +767,6 @@ class Wallet:
         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 = 'https://' + m1.group(2) + '/bitcoin.id/' + m1.group(1) 
-        elif m2:
-            url = 'https://' + 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
-            EC_KEY.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
-            EC_KEY.verify_message(previous, signature, "alias:%s:%s"%(alias,target))
-
-        if not is_valid(target):
-            raise ValueError("Invalid bitcoin address")
-
-        return target, signing_addr, auth_name
 
     def update_password(self, seed, old_password, new_password):
         if new_password == '': new_password = None
@@ -868,96 +780,6 @@ class Wallet:
             self.imported_keys[k] = c
         self.save()
 
-    def get_alias(self, alias, interactive = False, show_message=None, question = None):
-        try:
-            target, signing_address, auth_name = self.read_alias(alias)
-        except BaseException, e:
-            # raise exception if verify fails (verify the chain)
-            if interactive:
-                show_message("Alias error: " + str(e))
-            return
-
-        print target, signing_address, auth_name
-
-        if auth_name is None:
-            a = self.aliases.get(alias)
-            if not a:
-                msg = "Warning: the alias '%s' is self-signed.\nThe signing address is %s.\n\nDo you want to add this alias to your list of contacts?"%(alias,signing_address)
-                if interactive and question( msg ):
-                    self.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 question( msg ):
-                        self.aliases[alias] = (signing_address, target)
-                    else:
-                        target = None
-        else:
-            if signing_address not in self.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 question( msg ):
-                    self.authorities[signing_address] = auth_name
-                else:
-                    target = None
-
-        if target:
-            self.aliases[alias] = (signing_address, target)
-            
-        return target
-
-
-    def parse_url(self, url, show_message, question):
-        o = url[8:].split('?')
-        address = o[0]
-        if len(o)>1:
-            params = o[1].split('&')
-        else:
-            params = []
-
-        amount = label = message = signature = identity = ''
-        for p in params:
-            k,v = p.split('=')
-            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(':')
-                url = url.replace('&%s=%s'%(k,v),'')
-            else: 
-                print k,v
-
-        if label and self.labels.get(address) != label:
-            if question('Give label "%s" to address %s ?'%(label,address)):
-                if address not in self.addressbook and not self.is_mine(address):
-                    self.addressbook.append(address)
-                self.labels[address] = label
-
-        if signature:
-            if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
-                signing_address = self.get_alias(identity, True, show_message, question)
-            elif is_valid(identity):
-                signing_address = identity
-            else:
-                signing_address = None
-            if not signing_address:
-                return
-            try:
-                EC_KEY.verify_message(signing_address, signature, url )
-                self.receipt = (signing_address, signature, url)
-            except:
-                show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.')
-                address = amount = label = identity = message = ''
-
-        if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', address):
-            payto_address = self.get_alias(address, True, show_message, question)
-            if payto_address:
-                address = address + ' <' + payto_address + '>'
-
-        return address, amount, label, message, signature, identity, url
-
 
 
     def freeze(self,addr):
@@ -1008,9 +830,6 @@ class Wallet:
             'labels': self.labels,
             'contacts': self.addressbook,
             'imported_keys': self.imported_keys,
-            'aliases': self.aliases,
-            'authorities': self.authorities,
-            'receipts': self.receipts,
             'num_zeros': self.num_zeros,
             'frozen_addresses': self.frozen_addresses,
             'prioritized_addresses': self.prioritized_addresses,
diff --git a/plugins/aliases.py b/plugins/aliases.py
new file mode 100644 (file)
index 0000000..f8a3a5b
--- /dev/null
@@ -0,0 +1,231 @@
+import re
+import platform
+from decimal import Decimal
+
+from PyQt4.QtGui import *
+from PyQt4.QtCore import *
+import PyQt4.QtCore as QtCore
+import PyQt4.QtGui as QtGui
+
+from electrum_gui.qrcodewidget import QRCodeWidget
+from electrum_gui import bmp, pyqrnative
+from electrum_gui.i18n import _
+
+
+ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'    
+
+
+config = {}
+
+def get_info():
+    return 'Aliases', _('Retrieve aliases using http.')
+
+def init(self):
+    global config
+    config = self.config
+    self.aliases               = config.get('aliases', {})            # aliases for addresses
+    self.authorities           = config.get('authorities', {})        # trusted addresses
+    self.receipts              = config.get('receipts',{})            # signed URIs
+    do_enable(self, is_enabled())
+
+def is_enabled():
+    return config.get('use_aliases') is True
+
+def is_available():
+    return True
+
+
+def toggle(gui):
+    enabled = not is_enabled()
+    config.set_key('use_aliases', enabled, True)
+    do_enable(gui, enabled)
+    return enabled
+
+
+def do_enable(gui, enabled):
+    if enabled:
+        gui.set_hook('timer_actions', timer_actions)
+        gui.set_hook('set_url', set_url_hook)
+        gui.set_hook('update_contacts_tab', update_contacts_tab_hook)
+        gui.set_hook('update_completions', update_completions_hook)
+        gui.set_hook('create_contact_menu', create_contact_menu_hook)
+    else:
+        gui.unset_hook('timer_actions', timer_actions)
+        gui.unset_hook('set_url', set_url_hook)
+        gui.unset_hook('update_contacts_tab', update_contacts_tab_hook)
+        gui.unset_hook('update_completions', update_completions_hook)
+        gui.unset_hook('create_contact_menu', create_contact_menu_hook)
+
+
+def timer_actions(self):
+    if self.payto_e.hasFocus():
+        return
+    r = unicode( self.payto_e.text() )
+    if r != self.previous_payto_e:
+        self.previous_payto_e = r
+        r = r.strip()
+        if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
+            try:
+                to_address = get_alias(self, r, True, self.show_message, self.question)
+            except:
+                return
+            if to_address:
+                s = r + '  <' + to_address + '>'
+                self.payto_e.setText(s)
+
+
+def get_alias(self, alias, interactive = False, show_message=None, question = None):
+    try:
+        target, signing_address, auth_name = read_alias(self, alias)
+    except BaseException, e:
+        # raise exception if verify fails (verify the chain)
+        if interactive:
+            show_message("Alias error: " + str(e))
+        return
+
+    print target, signing_address, auth_name
+
+    if auth_name is None:
+        a = self.aliases.get(alias)
+        if not a:
+            msg = "Warning: the alias '%s' is self-signed.\nThe signing address is %s.\n\nDo you want to add this alias to your list of contacts?"%(alias,signing_address)
+            if interactive and question( msg ):
+                self.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 question( msg ):
+                    self.aliases[alias] = (signing_address, target)
+                else:
+                    target = None
+    else:
+        if signing_address not in self.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 question( msg ):
+                self.authorities[signing_address] = auth_name
+            else:
+                target = None
+
+    if target:
+        self.aliases[alias] = (signing_address, target)
+            
+    return target
+
+
+
+def read_alias(self, alias):
+    import urllib
+
+    m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias)
+    m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias)
+    if m1:
+        url = 'https://' + m1.group(2) + '/bitcoin.id/' + m1.group(1) 
+    elif m2:
+        url = 'https://' + 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
+        EC_KEY.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
+        EC_KEY.verify_message(previous, signature, "alias:%s:%s"%(alias,target))
+
+    if not is_valid(target):
+        raise ValueError("Invalid bitcoin address")
+
+    return target, signing_addr, auth_name
+
+
+def set_url_hook(self, url, show_message, question):
+    payto, amount, label, message, signature, identity, url = util.parse_url(url)
+    if signature:
+        if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
+            signing_address = get_alias(identity, True, show_message, question)
+        elif is_valid(identity):
+            signing_address = identity
+        else:
+            signing_address = None
+        if not signing_address:
+            return
+        try:
+            EC_KEY.verify_message(signing_address, signature, url )
+            self.receipt = (signing_address, signature, url)
+        except:
+            show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.')
+            address = amount = label = identity = message = ''
+
+    if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', address):
+        payto_address = get_alias(address, True, show_message, question)
+        if payto_address:
+            address = address + ' <' + payto_address + '>'
+
+    return address, amount, label, message, signature, identity, url
+
+
+
+def update_contacts_tab_hook(self, l):
+    alias_targets = []
+    for alias, v in self.aliases.items():
+        s, target = v
+        alias_targets.append(target)
+        item = QTreeWidgetItem( [ target, alias, '-'] )
+        item.setBackgroundColor(0, QColor('lightgray'))
+        l.insertTopLevelItem(0,item)
+        item.setData(0,32,False)
+        item.setData(0,33,alias + ' <' + target + '>')
+
+
+
+def update_completions_hook(self, l):
+    l[:] = l + self.aliases.keys()
+
+
+def create_contact_menu_hook(self, menu, item):
+    label = unicode(item.text(1))
+    if label in self.aliases.keys():
+        addr = unicode(item.text(0))
+        label = unicode(item.text(1))
+        menu.addAction(_("View alias details"), lambda: show_contact_details(self, label))
+        menu.addAction(_("Delete alias"), lambda: delete_alias(self, label))
+
+
+def show_contact_details(self, m):
+    a = self.aliases.get(m)
+    if a:
+        if a[0] in self.authorities.keys():
+            s = self.authorities.get(a[0])
+        else:
+            s = "self-signed"
+        msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
+        QMessageBox.information(self, 'Alias', msg, 'OK')
+
+
+def delete_alias(self, x):
+    if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
+        if x in self.aliases:
+            self.aliases.pop(x)
+            self.update_history_tab()
+            self.update_contacts_tab()
+            self.update_completions()