From 93b98e117654866024a8b850e3a4efc0bfd007d3 Mon Sep 17 00:00:00 2001 From: thomasv Date: Thu, 14 Mar 2013 16:32:05 +0100 Subject: [PATCH] move http aliases to separate plugin --- gui/gui_classic.py | 141 +++++++++++--------------------- gui/gui_gtk.py | 76 ++++++++++++----- lib/util.py | 35 ++++++++- lib/wallet.py | 181 ---------------------------------------- plugins/aliases.py | 231 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 369 insertions(+), 295 deletions(-) create mode 100644 plugins/aliases.py diff --git a/gui/gui_classic.py b/gui/gui_classic.py index 9e8139e..1d80132 100644 --- a/gui/gui_classic.py +++ b/gui/gui_classic.py @@ -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() diff --git a/gui/gui_gtk.py b/gui/gui_gtk.py index bbab63d..55b9342 100644 --- a/gui/gui_gtk.py +++ b/gui/gui_gtk.py @@ -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): diff --git a/lib/util.py b/lib/util.py index a76be6b..50d8ec3 100644 --- a/lib/util.py +++ b/lib/util.py @@ -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 + + + diff --git a/lib/wallet.py b/lib/wallet.py index 3814cd2..0383e4a 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -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 index 0000000..f8a3a5b --- /dev/null +++ b/plugins/aliases.py @@ -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() -- 1.7.1