#!/usr/bin/env python3 # Copyright (c) 2014-2021 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. ''' Script to generate list of seed nodes for kernel/chainparams.cpp. This script expects three text files in the directory that is passed as an argument: nodes_main.txt nodes_test.txt nodes_testnet4.txt These files must consist of lines in the format : []: .onion: .b32.i2p: The output will be two data structures with the peers in binary format: static const uint8_t chainparams_seed_{main,test}[]={ ... } These should be pasted into `src/chainparamsseeds.h`. ''' from base64 import b32decode from enum import Enum import sys import os import re class BIP155Network(Enum): IPV4 = 1 IPV6 = 2 TORV2 = 3 # no longer supported TORV3 = 4 I2P = 5 CJDNS = 6 def name_to_bip155(addr): '''Convert address string to BIP155 (networkID, addr) tuple.''' if addr.endswith('.onion'): vchAddr = b32decode(addr[0:-6], True) if len(vchAddr) == 35: assert vchAddr[34] == 3 return (BIP155Network.TORV3, vchAddr[:32]) elif len(vchAddr) == 10: return (BIP155Network.TORV2, vchAddr) else: raise ValueError('Invalid onion %s' % vchAddr) elif addr.endswith('.b32.i2p'): vchAddr = b32decode(addr[0:-8] + '====', True) if len(vchAddr) == 32: return (BIP155Network.I2P, vchAddr) else: raise ValueError(f'Invalid I2P {vchAddr}') elif '.' in addr: # IPv4 return (BIP155Network.IPV4, bytes((int(x) for x in addr.split('.')))) elif ':' in addr: # IPv6 or CJDNS sub = [[], []] # prefix, suffix x = 0 addr = addr.split(':') for i,comp in enumerate(addr): if comp == '': if i == 0 or i == (len(addr)-1): # skip empty component at beginning or end continue x += 1 # :: skips to suffix assert x < 2 else: # two bytes per component val = int(comp, 16) sub[x].append(val >> 8) sub[x].append(val & 0xff) nullbytes = 16 - len(sub[0]) - len(sub[1]) assert (x == 0 and nullbytes == 0) or (x == 1 and nullbytes > 0) addr_bytes = bytes(sub[0] + ([0] * nullbytes) + sub[1]) if addr_bytes[0] == 0xfc: # Assume that seeds with fc00::/8 addresses belong to CJDNS, # not to the publicly unroutable "Unique Local Unicast" network, see # RFC4193: https://datatracker.ietf.org/doc/html/rfc4193#section-8 return (BIP155Network.CJDNS, addr_bytes) else: return (BIP155Network.IPV6, addr_bytes) else: raise ValueError('Could not parse address %s' % addr) def parse_spec(s): '''Convert endpoint string to BIP155 (networkID, addr, port) tuple.''' match = re.match(r'\[([0-9a-fA-F:]+)\](?::([0-9]+))?$', s) if match: # ipv6 host = match.group(1) port = match.group(2) elif s.count(':') > 1: # ipv6, no port host = s port = '' else: (host,_,port) = s.partition(':') if not port: port = 0 else: port = int(port) host = name_to_bip155(host) if host[0] == BIP155Network.TORV2: return None # TORV2 is no longer supported, so we ignore it else: return host + (port, ) def ser_compact_size(l): r = b"" if l < 253: r = l.to_bytes(1, "little") elif l < 0x10000: r = (253).to_bytes(1, "little") + l.to_bytes(2, "little") elif l < 0x100000000: r = (254).to_bytes(1, "little") + l.to_bytes(4, "little") else: r = (255).to_bytes(1, "little") + l.to_bytes(8, "little") return r def bip155_serialize(spec): ''' Serialize (networkID, addr, port) tuple to BIP155 binary format. ''' r = b"" r += spec[0].value.to_bytes(1, "little") r += ser_compact_size(len(spec[1])) r += spec[1] r += spec[2].to_bytes(2, "big") return r def process_nodes(g, f, structname): g.write('static const uint8_t %s[] = {\n' % structname) for line in f: comment = line.find('#') if comment != -1: line = line[0:comment] line = line.strip() if not line: continue spec = parse_spec(line) if spec is None: # ignore this entry (e.g. no longer supported addresses like TORV2) continue blob = bip155_serialize(spec) hoststr = ','.join(('0x%02x' % b) for b in blob) g.write(f' {hoststr},\n') g.write('};\n') def main(): if len(sys.argv)<2: print(('Usage: %s ' % sys.argv[0]), file=sys.stderr) sys.exit(1) g = sys.stdout indir = sys.argv[1] g.write('#ifndef BITCOIN_CHAINPARAMSSEEDS_H\n') g.write('#define BITCOIN_CHAINPARAMSSEEDS_H\n') g.write('/**\n') g.write(' * List of fixed seed nodes for the bitcoin network\n') g.write(' * AUTOGENERATED by contrib/seeds/generate-seeds.py\n') g.write(' *\n') g.write(' * Each line contains a BIP155 serialized (networkID, addr, port) tuple.\n') g.write(' */\n') with open(os.path.join(indir,'nodes_main.txt'), 'r', encoding="utf8") as f: process_nodes(g, f, 'chainparams_seed_main') g.write('\n') with open(os.path.join(indir,'nodes_test.txt'), 'r', encoding="utf8") as f: process_nodes(g, f, 'chainparams_seed_test') g.write('\n') with open(os.path.join(indir,'nodes_testnet4.txt'), 'r', encoding="utf8") as f: process_nodes(g, f, 'chainparams_seed_testnet4') g.write('#endif // BITCOIN_CHAINPARAMSSEEDS_H\n') if __name__ == '__main__': main()