generate-seeds.py raw
1 #!/usr/bin/env python3
2 # Copyright (c) 2014-present The Bitcoin Core developers
3 # Distributed under the MIT software license, see the accompanying
4 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 '''
6 Script to generate list of seed nodes for kernel/chainparams.cpp.
7
8 This script expects three text files in the directory that is passed as an
9 argument:
10
11 nodes_main.txt
12 nodes_signet.txt
13 nodes_test.txt
14 nodes_testnet4.txt
15
16 These files must consist of lines in the format
17
18 <ip>:<port>
19 [<ipv6>]:<port>
20 <onion>.onion:<port>
21 <i2p>.b32.i2p:<port>
22
23 The output will be several data structures with the peers in binary format:
24
25 static const uint8_t chainparams_seed_{main,signet,test,testnet4}[]={
26 ...
27 }
28
29 These should be pasted into `src/chainparamsseeds.h`.
30 '''
31
32 from base64 import b32decode
33 from enum import Enum
34 import sys
35 import os
36 import re
37
38 class BIP155Network(Enum):
39 IPV4 = 1
40 IPV6 = 2
41 TORV2 = 3 # no longer supported
42 TORV3 = 4
43 I2P = 5
44 CJDNS = 6
45
46 def name_to_bip155(addr):
47 '''Convert address string to BIP155 (networkID, addr) tuple.'''
48 if addr.endswith('.onion'):
49 vchAddr = b32decode(addr[0:-6], True)
50 if len(vchAddr) == 35:
51 assert vchAddr[34] == 3
52 return (BIP155Network.TORV3, vchAddr[:32])
53 elif len(vchAddr) == 10:
54 return (BIP155Network.TORV2, vchAddr)
55 else:
56 raise ValueError('Invalid onion %s' % vchAddr)
57 elif addr.endswith('.b32.i2p'):
58 vchAddr = b32decode(addr[0:-8] + '====', True)
59 if len(vchAddr) == 32:
60 return (BIP155Network.I2P, vchAddr)
61 else:
62 raise ValueError(f'Invalid I2P {vchAddr}')
63 elif '.' in addr: # IPv4
64 return (BIP155Network.IPV4, bytes((int(x) for x in addr.split('.'))))
65 elif ':' in addr: # IPv6 or CJDNS
66 sub = [[], []] # prefix, suffix
67 x = 0
68 addr = addr.split(':')
69 for i,comp in enumerate(addr):
70 if comp == '':
71 if i == 0 or i == (len(addr)-1): # skip empty component at beginning or end
72 continue
73 x += 1 # :: skips to suffix
74 assert x < 2
75 else: # two bytes per component
76 val = int(comp, 16)
77 sub[x].append(val >> 8)
78 sub[x].append(val & 0xff)
79 nullbytes = 16 - len(sub[0]) - len(sub[1])
80 assert (x == 0 and nullbytes == 0) or (x == 1 and nullbytes > 0)
81 addr_bytes = bytes(sub[0] + ([0] * nullbytes) + sub[1])
82 if addr_bytes[0] == 0xfc:
83 # Assume that seeds with fc00::/8 addresses belong to CJDNS,
84 # not to the publicly unroutable "Unique Local Unicast" network, see
85 # RFC4193: https://datatracker.ietf.org/doc/html/rfc4193#section-8
86 return (BIP155Network.CJDNS, addr_bytes)
87 else:
88 return (BIP155Network.IPV6, addr_bytes)
89 else:
90 raise ValueError('Could not parse address %s' % addr)
91
92 def parse_spec(s):
93 '''Convert endpoint string to BIP155 (networkID, addr, port) tuple.'''
94 match = re.match(r'\[([0-9a-fA-F:]+)\](?::([0-9]+))?$', s)
95 if match: # ipv6
96 host = match.group(1)
97 port = match.group(2)
98 elif s.count(':') > 1: # ipv6, no port
99 host = s
100 port = ''
101 else:
102 (host,_,port) = s.partition(':')
103
104 if not port:
105 port = 0
106 else:
107 port = int(port)
108
109 host = name_to_bip155(host)
110
111 if host[0] == BIP155Network.TORV2:
112 return None # TORV2 is no longer supported, so we ignore it
113 else:
114 return host + (port, )
115
116 def ser_compact_size(l):
117 r = b""
118 if l < 253:
119 r = l.to_bytes(1, "little")
120 elif l < 0x10000:
121 r = (253).to_bytes(1, "little") + l.to_bytes(2, "little")
122 elif l < 0x100000000:
123 r = (254).to_bytes(1, "little") + l.to_bytes(4, "little")
124 else:
125 r = (255).to_bytes(1, "little") + l.to_bytes(8, "little")
126 return r
127
128 def bip155_serialize(spec):
129 '''
130 Serialize (networkID, addr, port) tuple to BIP155 binary format.
131 '''
132 r = b""
133 r += spec[0].value.to_bytes(1, "little")
134 r += ser_compact_size(len(spec[1]))
135 r += spec[1]
136 r += spec[2].to_bytes(2, "big")
137 return r
138
139 def process_nodes(g, f, structname):
140 g.write('static const uint8_t %s[] = {\n' % structname)
141 for line in f:
142 comment = line.find('#')
143 if comment != -1:
144 line = line[0:comment]
145 line = line.strip()
146 if not line:
147 continue
148
149 spec = parse_spec(line)
150 if spec is None: # ignore this entry (e.g. no longer supported addresses like TORV2)
151 continue
152 blob = bip155_serialize(spec)
153 hoststr = ','.join(('0x%02x' % b) for b in blob)
154 g.write(f' {hoststr},\n')
155 g.write('};\n')
156
157 def main():
158 if len(sys.argv)<2:
159 print(('Usage: %s <path_to_nodes_txt>' % sys.argv[0]), file=sys.stderr)
160 sys.exit(1)
161 g = sys.stdout
162 indir = sys.argv[1]
163 g.write('// Copyright (c) The Bitcoin Core developers\n')
164 g.write('// Distributed under the MIT software license, see the accompanying\n')
165 g.write('// file COPYING or https://opensource.org/license/mit.\n\n')
166 g.write('#ifndef BITCOIN_CHAINPARAMSSEEDS_H\n')
167 g.write('#define BITCOIN_CHAINPARAMSSEEDS_H\n')
168 g.write('/**\n')
169 g.write(' * List of fixed seed nodes for the bitcoin network\n')
170 g.write(' * AUTOGENERATED by contrib/seeds/generate-seeds.py\n')
171 g.write(' *\n')
172 g.write(' * Each line contains a BIP155 serialized (networkID, addr, port) tuple.\n')
173 g.write(' */\n')
174 with open(os.path.join(indir,'nodes_main.txt'), 'r') as f:
175 process_nodes(g, f, 'chainparams_seed_main')
176 g.write('\n')
177 with open(os.path.join(indir,'nodes_signet.txt'), 'r') as f:
178 process_nodes(g, f, 'chainparams_seed_signet')
179 g.write('\n')
180 with open(os.path.join(indir,'nodes_test.txt'), 'r') as f:
181 process_nodes(g, f, 'chainparams_seed_test')
182 g.write('\n')
183 with open(os.path.join(indir,'nodes_testnet4.txt'), 'r') as f:
184 process_nodes(g, f, 'chainparams_seed_testnet4')
185 g.write('#endif // BITCOIN_CHAINPARAMSSEEDS_H\n')
186
187 if __name__ == '__main__':
188 main()
189