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