/ test / functional / test_framework / descriptors.py
descriptors.py
 1  #!/usr/bin/env python3
 2  # Copyright (c) 2019 Pieter Wuille
 3  # Distributed under the MIT software license, see the accompanying
 4  # file COPYING or http://www.opensource.org/licenses/mit-license.php.
 5  """Utility functions related to output descriptors"""
 6  
 7  import re
 8  
 9  INPUT_CHARSET = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ "
10  CHECKSUM_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
11  GENERATOR = [0xf5dee51989, 0xa9fdca3312, 0x1bab10e32d, 0x3706b1677a, 0x644d626ffd]
12  
13  def descsum_polymod(symbols):
14      """Internal function that computes the descriptor checksum."""
15      chk = 1
16      for value in symbols:
17          top = chk >> 35
18          chk = (chk & 0x7ffffffff) << 5 ^ value
19          for i in range(5):
20              chk ^= GENERATOR[i] if ((top >> i) & 1) else 0
21      return chk
22  
23  def descsum_expand(s):
24      """Internal function that does the character to symbol expansion"""
25      groups = []
26      symbols = []
27      for c in s:
28          if c not in INPUT_CHARSET:
29              return None
30          v = INPUT_CHARSET.find(c)
31          symbols.append(v & 31)
32          groups.append(v >> 5)
33          if len(groups) == 3:
34              symbols.append(groups[0] * 9 + groups[1] * 3 + groups[2])
35              groups = []
36      if len(groups) == 1:
37          symbols.append(groups[0])
38      elif len(groups) == 2:
39          symbols.append(groups[0] * 3 + groups[1])
40      return symbols
41  
42  def descsum_create(s):
43      """Add a checksum to a descriptor without"""
44      symbols = descsum_expand(s) + [0, 0, 0, 0, 0, 0, 0, 0]
45      checksum = descsum_polymod(symbols) ^ 1
46      return s + '#' + ''.join(CHECKSUM_CHARSET[(checksum >> (5 * (7 - i))) & 31] for i in range(8))
47  
48  def descsum_check(s, require=True):
49      """Verify that the checksum is correct in a descriptor"""
50      if '#' not in s:
51          return not require
52      if s[-9] != '#':
53          return False
54      if not all(x in CHECKSUM_CHARSET for x in s[-8:]):
55          return False
56      symbols = descsum_expand(s[:-9]) + [CHECKSUM_CHARSET.find(x) for x in s[-8:]]
57      return descsum_polymod(symbols) == 1
58  
59  def drop_origins(s):
60      '''Drop the key origins from a descriptor'''
61      desc = re.sub(r'\[.+?\]', '', s)
62      if '#' in s:
63          desc = desc[:desc.index('#')]
64      return descsum_create(desc)