tcontacts.py - electrum - Electrum Bitcoin wallet
HTML git clone https://git.parazyd.org/electrum
DIR Log
DIR Files
DIR Refs
DIR Submodules
---
tcontacts.py (4439B)
---
1 # Electrum - Lightweight Bitcoin Client
2 # Copyright (c) 2015 Thomas Voegtlin
3 #
4 # Permission is hereby granted, free of charge, to any person
5 # obtaining a copy of this software and associated documentation files
6 # (the "Software"), to deal in the Software without restriction,
7 # including without limitation the rights to use, copy, modify, merge,
8 # publish, distribute, sublicense, and/or sell copies of the Software,
9 # and to permit persons to whom the Software is furnished to do so,
10 # subject to the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be
13 # included in all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19 # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20 # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 # SOFTWARE.
23 import re
24
25 import dns
26 from dns.exception import DNSException
27
28 from . import bitcoin
29 from . import dnssec
30 from .util import read_json_file, write_json_file, to_string
31 from .logging import Logger
32
33
34 class Contacts(dict, Logger):
35
36 def __init__(self, db):
37 Logger.__init__(self)
38 self.db = db
39 d = self.db.get('contacts', {})
40 try:
41 self.update(d)
42 except:
43 return
44 # backward compatibility
45 for k, v in self.items():
46 _type, n = v
47 if _type == 'address' and bitcoin.is_address(n):
48 self.pop(k)
49 self[n] = ('address', k)
50
51 def save(self):
52 self.db.put('contacts', dict(self))
53
54 def import_file(self, path):
55 data = read_json_file(path)
56 data = self._validate(data)
57 self.update(data)
58 self.save()
59
60 def export_file(self, path):
61 write_json_file(path, self)
62
63 def __setitem__(self, key, value):
64 dict.__setitem__(self, key, value)
65 self.save()
66
67 def pop(self, key):
68 if key in self.keys():
69 res = dict.pop(self, key)
70 self.save()
71 return res
72
73 def resolve(self, k):
74 if bitcoin.is_address(k):
75 return {
76 'address': k,
77 'type': 'address'
78 }
79 if k in self.keys():
80 _type, addr = self[k]
81 if _type == 'address':
82 return {
83 'address': addr,
84 'type': 'contact'
85 }
86 out = self.resolve_openalias(k)
87 if out:
88 address, name, validated = out
89 return {
90 'address': address,
91 'name': name,
92 'type': 'openalias',
93 'validated': validated
94 }
95 raise Exception("Invalid Bitcoin address or alias", k)
96
97 def resolve_openalias(self, url):
98 # support email-style addresses, per the OA standard
99 url = url.replace('@', '.')
100 try:
101 records, validated = dnssec.query(url, dns.rdatatype.TXT)
102 except DNSException as e:
103 self.logger.info(f'Error resolving openalias: {repr(e)}')
104 return None
105 prefix = 'btc'
106 for record in records:
107 string = to_string(record.strings[0], 'utf8')
108 if string.startswith('oa1:' + prefix):
109 address = self.find_regex(string, r'recipient_address=([A-Za-z0-9]+)')
110 name = self.find_regex(string, r'recipient_name=([^;]+)')
111 if not name:
112 name = address
113 if not address:
114 continue
115 return address, name, validated
116
117 def find_regex(self, haystack, needle):
118 regex = re.compile(needle)
119 try:
120 return regex.search(haystack).groups()[0]
121 except AttributeError:
122 return None
123
124 def _validate(self, data):
125 for k, v in list(data.items()):
126 if k == 'contacts':
127 return self._validate(v)
128 if not bitcoin.is_address(k):
129 data.pop(k)
130 else:
131 _type, _ = v
132 if _type != 'address':
133 data.pop(k)
134 return data
135