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