Skip to content

Commit 06030f7

Browse files
committed
contrib: generate-seeds.py generates output in BIP155 format
1 parent f9e86d8 commit 06030f7

File tree

1 file changed

+62
-41
lines changed

1 file changed

+62
-41
lines changed

contrib/seeds/generate-seeds.py

Lines changed: 62 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env python3
2-
# Copyright (c) 2014-2017 Wladimir J. van der Laan
2+
# Copyright (c) 2014-2021 The Bitcoin Core developers
33
# Distributed under the MIT software license, see the accompanying
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
'''
@@ -13,44 +13,47 @@
1313
1414
These files must consist of lines in the format
1515
16-
<ip>
1716
<ip>:<port>
18-
[<ipv6>]
1917
[<ipv6>]:<port>
20-
<onion>.onion
21-
0xDDBBCCAA (IPv4 little-endian old pnSeeds format)
18+
<onion>.onion:<port>
2219
2320
The output will be two data structures with the peers in binary format:
2421
25-
static SeedSpec6 pnSeed6_main[]={
26-
...
27-
}
28-
static SeedSpec6 pnSeed6_test[]={
22+
static const uint8_t chainparams_seed_{main,test}[]={
2923
...
3024
}
3125
3226
These should be pasted into `src/chainparamsseeds.h`.
3327
'''
3428

3529
from base64 import b32decode
36-
from binascii import a2b_hex
30+
from enum import Enum
31+
import struct
3732
import sys
3833
import os
3934
import re
4035

41-
# ipv4 in ipv6 prefix
42-
pchIPv4 = bytearray([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff])
43-
# tor-specific ipv6 prefix
44-
pchOnionCat = bytearray([0xFD,0x87,0xD8,0x7E,0xEB,0x43])
45-
46-
def name_to_ipv6(addr):
47-
if len(addr)>6 and addr.endswith('.onion'):
36+
class BIP155Network(Enum):
37+
IPV4 = 1
38+
IPV6 = 2
39+
TORV2 = 3
40+
TORV3 = 4
41+
I2P = 5
42+
CJDNS = 6
43+
44+
def name_to_bip155(addr):
45+
'''Convert address string to BIP155 (networkID, addr) tuple.'''
46+
if addr.endswith('.onion'):
4847
vchAddr = b32decode(addr[0:-6], True)
49-
if len(vchAddr) != 16-len(pchOnionCat):
48+
if len(vchAddr) == 10:
49+
return (BIP155Network.TORV2, vchAddr)
50+
elif len(vchAddr) == 35:
51+
assert(vchAddr[34] == 3)
52+
return (BIP155Network.TORV3, vchAddr[:32])
53+
else:
5054
raise ValueError('Invalid onion %s' % vchAddr)
51-
return pchOnionCat + vchAddr
5255
elif '.' in addr: # IPv4
53-
return pchIPv4 + bytearray((int(x) for x in addr.split('.')))
56+
return (BIP155Network.IPV4, bytes((int(x) for x in addr.split('.'))))
5457
elif ':' in addr: # IPv6
5558
sub = [[], []] # prefix, suffix
5659
x = 0
@@ -67,13 +70,12 @@ def name_to_ipv6(addr):
6770
sub[x].append(val & 0xff)
6871
nullbytes = 16 - len(sub[0]) - len(sub[1])
6972
assert((x == 0 and nullbytes == 0) or (x == 1 and nullbytes > 0))
70-
return bytearray(sub[0] + ([0] * nullbytes) + sub[1])
71-
elif addr.startswith('0x'): # IPv4-in-little-endian
72-
return pchIPv4 + bytearray(reversed(a2b_hex(addr[2:])))
73+
return (BIP155Network.IPV6, bytes(sub[0] + ([0] * nullbytes) + sub[1]))
7374
else:
7475
raise ValueError('Could not parse address %s' % addr)
7576

76-
def parse_spec(s, defaultport):
77+
def parse_spec(s):
78+
'''Convert endpoint string to BIP155 (networkID, addr, port) tuple.'''
7779
match = re.match(r'\[([0-9a-fA-F:]+)\](?::([0-9]+))?$', s)
7880
if match: # ipv6
7981
host = match.group(1)
@@ -85,32 +87,52 @@ def parse_spec(s, defaultport):
8587
(host,_,port) = s.partition(':')
8688

8789
if not port:
88-
port = defaultport
90+
port = 0
8991
else:
9092
port = int(port)
9193

92-
host = name_to_ipv6(host)
94+
host = name_to_bip155(host)
9395

94-
return (host,port)
96+
return host + (port, )
9597

96-
def process_nodes(g, f, structname, defaultport):
97-
g.write('static SeedSpec6 %s[] = {\n' % structname)
98-
first = True
98+
def ser_compact_size(l):
99+
r = b""
100+
if l < 253:
101+
r = struct.pack("B", l)
102+
elif l < 0x10000:
103+
r = struct.pack("<BH", 253, l)
104+
elif l < 0x100000000:
105+
r = struct.pack("<BI", 254, l)
106+
else:
107+
r = struct.pack("<BQ", 255, l)
108+
return r
109+
110+
def bip155_serialize(spec):
111+
'''
112+
Serialize (networkID, addr, port) tuple to BIP155 binary format.
113+
'''
114+
r = b""
115+
r += struct.pack('B', spec[0].value)
116+
r += ser_compact_size(len(spec[1]))
117+
r += spec[1]
118+
r += struct.pack('>H', spec[2])
119+
return r
120+
121+
def process_nodes(g, f, structname):
122+
g.write('static const uint8_t %s[] = {\n' % structname)
99123
for line in f:
100124
comment = line.find('#')
101125
if comment != -1:
102126
line = line[0:comment]
103127
line = line.strip()
104128
if not line:
105129
continue
106-
if not first:
107-
g.write(',\n')
108-
first = False
109130

110-
(host,port) = parse_spec(line, defaultport)
111-
hoststr = ','.join(('0x%02x' % b) for b in host)
112-
g.write(' {{%s}, %i}' % (hoststr, port))
113-
g.write('\n};\n')
131+
spec = parse_spec(line)
132+
blob = bip155_serialize(spec)
133+
hoststr = ','.join(('0x%02x' % b) for b in blob)
134+
g.write(f' {hoststr},\n')
135+
g.write('};\n')
114136

115137
def main():
116138
if len(sys.argv)<2:
@@ -124,14 +146,13 @@ def main():
124146
g.write(' * List of fixed seed nodes for the bitcoin network\n')
125147
g.write(' * AUTOGENERATED by contrib/seeds/generate-seeds.py\n')
126148
g.write(' *\n')
127-
g.write(' * Each line contains a 16-byte IPv6 address and a port.\n')
128-
g.write(' * IPv4 as well as onion addresses are wrapped inside an IPv6 address accordingly.\n')
149+
g.write(' * Each line contains a BIP155 serialized (networkID, addr, port) tuple.\n')
129150
g.write(' */\n')
130151
with open(os.path.join(indir,'nodes_main.txt'), 'r', encoding="utf8") as f:
131-
process_nodes(g, f, 'pnSeed6_main', 8333)
152+
process_nodes(g, f, 'chainparams_seed_main')
132153
g.write('\n')
133154
with open(os.path.join(indir,'nodes_test.txt'), 'r', encoding="utf8") as f:
134-
process_nodes(g, f, 'pnSeed6_test', 18333)
155+
process_nodes(g, f, 'chainparams_seed_test')
135156
g.write('#endif // BITCOIN_CHAINPARAMSSEEDS_H\n')
136157

137158
if __name__ == '__main__':

0 commit comments

Comments
 (0)