electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

contacts.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