derive plugins from BasePlugin class
[electrum-nvc.git] / plugins / aliases.py
1 import re
2 import platform
3 from decimal import Decimal
4
5 from PyQt4.QtGui import *
6 from PyQt4.QtCore import *
7 import PyQt4.QtCore as QtCore
8 import PyQt4.QtGui as QtGui
9
10 from electrum_gui.qrcodewidget import QRCodeWidget
11 from electrum_gui import bmp, pyqrnative
12 from electrum_gui.i18n import _
13
14
15 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'    
16
17
18
19 from electrum_gui import BasePlugin
20 class Plugin(BasePlugin):
21
22     def __init__(self, gui):
23         BasePlugin.__init__(self, gui, 'aliases', 'Aliases', _('Retrieve aliases using http.'))
24         self.aliases      = self.config.get('aliases', {})            # aliases for addresses
25         self.authorities  = self.config.get('authorities', {})        # trusted addresses
26         self.receipts     = self.config.get('receipts',{})            # signed URIs
27
28
29     def timer_actions(self):
30         if self.gui.payto_e.hasFocus():
31             return
32         r = unicode( self.gui.payto_e.text() )
33         if r != self.gui.previous_payto_e:
34             self.gui.previous_payto_e = r
35             r = r.strip()
36             if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
37                 try:
38                     to_address = self.get_alias(r, True, self.gui.show_message, self.gui.question)
39                 except:
40                     return
41                 if to_address:
42                     s = r + '  <' + to_address + '>'
43                     self.gui.payto_e.setText(s)
44
45
46     def get_alias(self, alias, interactive = False, show_message=None, question = None):
47         try:
48             target, signing_address, auth_name = read_alias(self, alias)
49         except BaseException, e:
50             # raise exception if verify fails (verify the chain)
51             if interactive:
52                 show_message("Alias error: " + str(e))
53             return
54
55         print target, signing_address, auth_name
56
57         if auth_name is None:
58             a = self.aliases.get(alias)
59             if not a:
60                 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)
61                 if interactive and question( msg ):
62                     self.aliases[alias] = (signing_address, target)
63                 else:
64                     target = None
65             else:
66                 if signing_address != a[0]:
67                     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
68                     if interactive and question( msg ):
69                         self.aliases[alias] = (signing_address, target)
70                     else:
71                         target = None
72         else:
73             if signing_address not in self.authorities.keys():
74                 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)
75                 if interactive and question( msg ):
76                     self.authorities[signing_address] = auth_name
77                 else:
78                     target = None
79
80         if target:
81             self.aliases[alias] = (signing_address, target)
82             
83         return target
84
85
86
87     def read_alias(self, alias):
88         import urllib
89
90         m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias)
91         m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias)
92         if m1:
93             url = 'https://' + m1.group(2) + '/bitcoin.id/' + m1.group(1) 
94         elif m2:
95             url = 'https://' + alias + '/bitcoin.id'
96         else:
97             return ''
98         try:
99             lines = urllib.urlopen(url).readlines()
100         except:
101             return ''
102
103         # line 0
104         line = lines[0].strip().split(':')
105         if len(line) == 1:
106             auth_name = None
107             target = signing_addr = line[0]
108         else:
109             target, auth_name, signing_addr, signature = line
110             msg = "alias:%s:%s:%s"%(alias,target,auth_name)
111             print msg, signature
112             EC_KEY.verify_message(signing_addr, signature, msg)
113         
114         # other lines are signed updates
115         for line in lines[1:]:
116             line = line.strip()
117             if not line: continue
118             line = line.split(':')
119             previous = target
120             print repr(line)
121             target, signature = line
122             EC_KEY.verify_message(previous, signature, "alias:%s:%s"%(alias,target))
123
124         if not is_valid(target):
125             raise ValueError("Invalid bitcoin address")
126
127         return target, signing_addr, auth_name
128
129
130     def set_url(self, url, show_message, question):
131         payto, amount, label, message, signature, identity, url = util.parse_url(url)
132         if signature:
133             if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
134                 signing_address = get_alias(identity, True, show_message, question)
135             elif is_valid(identity):
136                 signing_address = identity
137             else:
138                 signing_address = None
139             if not signing_address:
140                 return
141             try:
142                 EC_KEY.verify_message(signing_address, signature, url )
143                 self.receipt = (signing_address, signature, url)
144             except:
145                 show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.')
146                 address = amount = label = identity = message = ''
147
148         if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', address):
149             payto_address = get_alias(address, True, show_message, question)
150             if payto_address:
151                 address = address + ' <' + payto_address + '>'
152
153         return address, amount, label, message, signature, identity, url
154
155
156
157     def update_contacts_tab(self, l):
158         alias_targets = []
159         for alias, v in self.aliases.items():
160             s, target = v
161             alias_targets.append(target)
162             item = QTreeWidgetItem( [ target, alias, '-'] )
163             item.setBackgroundColor(0, QColor('lightgray'))
164             item.setData(0,32,False)
165             item.setData(0,33,alias + ' <' + target + '>')
166             l.insertTopLevelItem(0,item)
167
168
169     def update_completions(self, l):
170         l[:] = l + self.aliases.keys()
171
172
173     def create_contact_menu(self, menu, item):
174         label = unicode(item.text(1))
175         if label in self.aliases.keys():
176             addr = unicode(item.text(0))
177             label = unicode(item.text(1))
178             menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
179             menu.addAction(_("Delete alias"), lambda: delete_alias(self, label))
180
181
182     def show_contact_details(self, m):
183         a = self.aliases.get(m)
184         if a:
185             if a[0] in self.authorities.keys():
186                 s = self.authorities.get(a[0])
187             else:
188                 s = "self-signed"
189             msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
190             QMessageBox.information(self.gui, 'Alias', msg, 'OK')
191
192
193     def delete_alias(self, x):
194         if self.gui.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
195             if x in self.aliases:
196                 self.aliases.pop(x)
197                 self.update_history_tab()
198                 self.update_contacts_tab()
199                 self.update_completions()