aboutsummaryrefslogtreecommitdiff
path: root/contrib/seeds/generate-seeds.py
blob: d95069277df622da03c382e0058bc5adb93c1f15 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#!/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 chainparams.cpp.

This script expects two text files in the directory that is passed as an
argument:

    nodes_main.txt
    nodes_test.txt

These files must consist of lines in the format

    <ip>:<port>
    [<ipv6>]:<port>
    <onion>.onion:<port>

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 struct
import sys
import os
import re

class BIP155Network(Enum):
    IPV4 = 1
    IPV6 = 2
    TORV2 = 3
    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) == 10:
            return (BIP155Network.TORV2, vchAddr)
        elif len(vchAddr) == 35:
            assert(vchAddr[34] == 3)
            return (BIP155Network.TORV3, vchAddr[:32])
        else:
            raise ValueError('Invalid onion %s' % vchAddr)
    elif '.' in addr: # IPv4
        return (BIP155Network.IPV4, bytes((int(x) for x in addr.split('.'))))
    elif ':' in addr: # IPv6
        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))
        return (BIP155Network.IPV6, bytes(sub[0] + ([0] * nullbytes) + sub[1]))
    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)

    return host + (port, )

def ser_compact_size(l):
    r = b""
    if l < 253:
        r = struct.pack("B", l)
    elif l < 0x10000:
        r = struct.pack("<BH", 253, l)
    elif l < 0x100000000:
        r = struct.pack("<BI", 254, l)
    else:
        r = struct.pack("<BQ", 255, l)
    return r

def bip155_serialize(spec):
    '''
    Serialize (networkID, addr, port) tuple to BIP155 binary format.
    '''
    r = b""
    r += struct.pack('B', spec[0].value)
    r += ser_compact_size(len(spec[1]))
    r += spec[1]
    r += struct.pack('>H', spec[2])
    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)
        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 <path_to_nodes_txt>' % 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('#endif // BITCOIN_CHAINPARAMSSEEDS_H\n')

if __name__ == '__main__':
    main()