Skip to content

Commit 8558cea

Browse files
committed
Add regtest support
1 parent 84ef028 commit 8558cea

File tree

6 files changed

+73
-24
lines changed

6 files changed

+73
-24
lines changed

buidl/bech32.py

+22-15
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@
1111

1212
BECH32M_CONSTANT = 0x2BC830A3
1313

14+
PREFIX = {
15+
'mainnet': 'bc',
16+
'testnet': 'tb',
17+
'regtest': 'bcrt',
18+
'signet': 'tb',
19+
}
20+
NET_FOR_PREFIX = {v: k for k, v in PREFIX.items() if k != 'signet'}
21+
1422

1523
def uses_only_bech32_chars(string):
1624
return bool(BECH32_CHARS_RE.match(string.lower()))
@@ -163,10 +171,9 @@ def encode_bech32(nums):
163171

164172
def encode_bech32_checksum(s, network="mainnet"):
165173
"""Convert a segwit ScriptPubKey to a bech32 address"""
166-
if network == "mainnet":
167-
prefix = "bc"
168-
else:
169-
prefix = "tb"
174+
prefix = PREFIX.get(network)
175+
if not prefix:
176+
raise ValueError(f"unrecognized network: {network}")
170177
version = s[0]
171178
if version > 0:
172179
version -= 0x50
@@ -181,21 +188,21 @@ def encode_bech32_checksum(s, network="mainnet"):
181188

182189
def decode_bech32(s):
183190
"""Returns network, segwit version and the hash from the bech32 address"""
184-
hrp, raw_data = s.split("1")
185-
if hrp == "bc":
186-
network = "mainnet"
187-
elif hrp == "tb":
188-
network = "testnet"
191+
regtest_prefix = PREFIX['regtest']
192+
if s.startswith(regtest_prefix):
193+
hrp, raw_data = regtest_prefix, s[5:]
189194
else:
195+
hrp, raw_data = s.split("1")
196+
197+
network = NET_FOR_PREFIX.get(hrp)
198+
if not network:
190199
raise ValueError(f"unknown human readable part: {hrp}")
200+
191201
data = [BECH32_ALPHABET.index(c) for c in raw_data]
192202
version = data[0]
193-
if version == 0:
194-
if not bech32_verify_checksum(hrp, data):
195-
raise ValueError(f"bad address: {s}")
196-
else:
197-
if not bech32m_verify_checksum(hrp, data):
198-
raise ValueError(f"bad address: {s}")
203+
verify_fnc = bech32_verify_checksum if version == 0 else bech32m_verify_checksum
204+
if not verify_fnc(hrp, data):
205+
raise ValueError(f"bad address: {s}")
199206
number = 0
200207
for digit in data[1:-6]:
201208
number = (number << 5) + digit

buidl/block.py

+3
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,16 @@ def get_outpoints(self):
175175
GENESIS_BLOCK_MAINNET_HEX = "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c"
176176
GENESIS_BLOCK_TESTNET_HEX = "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff001d1aa4ae18"
177177
GENESIS_BLOCK_SIGNET_HEX = "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a008f4d5fae77031e8ad22203"
178+
GENESIS_BLOCK_REGTEST_HEX = "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff7f2002000000"
178179
GENESIS_BLOCK_HEADERS = {
179180
"mainnet": Block.parse_header(hex=GENESIS_BLOCK_MAINNET_HEX),
180181
"testnet": Block.parse_header(hex=GENESIS_BLOCK_TESTNET_HEX),
181182
"signet": Block.parse_header(hex=GENESIS_BLOCK_SIGNET_HEX),
183+
"regtest": Block.parse_header(hex=GENESIS_BLOCK_REGTEST_HEX),
182184
}
183185
GENESIS_BLOCK_HASH = {
184186
"mainnet": GENESIS_BLOCK_HEADERS["mainnet"].hash(),
185187
"testnet": GENESIS_BLOCK_HEADERS["testnet"].hash(),
186188
"signet": GENESIS_BLOCK_HEADERS["signet"].hash(),
189+
"regtest": GENESIS_BLOCK_HEADERS["regtest"].hash(),
187190
}

buidl/hd.py

+21-6
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@
2727
"mainnet": bytes.fromhex("0488ade4"),
2828
"testnet": bytes.fromhex("04358394"),
2929
"signet": bytes.fromhex("04358394"),
30+
"regtest": bytes.fromhex("04358394"),
3031
}
3132

3233
XPUB = {
3334
"mainnet": bytes.fromhex("0488b21e"),
3435
"testnet": bytes.fromhex("043587cf"),
3536
"signet": bytes.fromhex("043587cf"),
37+
"regtest": bytes.fromhex("043587cf"),
3638
}
3739

3840
# P2PKH or P2SH, P2WPKH in P2SH, P2WPKH, Multi-signature P2WSH in P2SH, Multi-signature P2WSH
@@ -57,6 +59,7 @@
5759
"mainnet": "m/48h/0h/0h/2h",
5860
"testnet": "m/48h/1h/0h/2h",
5961
"signet": "m/48h/1h/0h/2h",
62+
"regtest": "m/48h/1h/0h/2h",
6063
}
6164

6265

@@ -266,14 +269,20 @@ def parse(cls, s):
266269
return cls.raw_parse(stream)
267270

268271
@classmethod
269-
def raw_parse(cls, s):
270-
"""Returns a HDPrivateKey from a stream"""
272+
def raw_parse(cls, s, network=None):
273+
"""
274+
Returns a HDPrivateKey from a stream.
275+
276+
`network` should be specified if not mainnet, since SLIP-0132 version
277+
numbers are don't differentiate between non-mainnet networks.
278+
"""
271279
# first 4 bytes are the priv_version
272280
priv_version = s.read(4)
273281
# check that the priv_version is one of the TESTNET or MAINNET
274282
# private keys, if not raise a ValueError
275283
if priv_version in ALL_TESTNET_XPRVS:
276-
network = "testnet"
284+
if network is None:
285+
network = "testnet"
277286
elif priv_version in ALL_MAINNET_XPRVS:
278287
network = "mainnet"
279288
else:
@@ -680,14 +689,20 @@ def parse(cls, s):
680689
return cls.raw_parse(stream)
681690

682691
@classmethod
683-
def raw_parse(cls, s):
684-
"""Returns a HDPublicKey from a stream"""
692+
def raw_parse(cls, s, network=None):
693+
"""
694+
Returns a HDPublicKey from a stream.
695+
696+
`network` should be specified if not mainnet, since SLIP-0132 version
697+
numbers are don't differentiate between non-mainnet networks.
698+
"""
685699
# first 4 bytes are the pub_version
686700
pub_version = s.read(4)
687701
# check that the pub_version is one of the TESTNET or MAINNET
688702
# public keys, if not raise a ValueError
689703
if pub_version in ALL_TESTNET_XPUBS:
690-
network = "testnet"
704+
if network is None:
705+
network = "testnet"
691706
elif pub_version in ALL_MAINNET_XPUBS:
692707
network = "mainnet"
693708
else:

buidl/network.py

+2
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@
2626
"mainnet": b"\xf9\xbe\xb4\xd9",
2727
"testnet": b"\x0b\x11\x09\x07",
2828
"signet": b"\x0a\x03\xcf\x40",
29+
"regtest": b"\xfa\xbf\xb5\xda",
2930
}
3031
PORT = {
3132
"mainnet": 8333,
3233
"testnet": 18333,
3334
"signet": 38333,
35+
"regtest": 18444,
3436
}
3537

3638

buidl/pecc.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,14 @@ def tweaked(self, tweak):
617617

618618
@classmethod
619619
def parse(cls, wif):
620-
"""Converts WIF to a PrivateKey object"""
620+
"""
621+
Converts WIF to a PrivateKey object.
622+
623+
Note that this doesn't differentiate between non-mainnet networks. Since
624+
this class doesn't generate anything downstream of the particular network
625+
(e.g. addresses), it shouldn't be a problem, however the network inferred
626+
here cannot be relied upon if parsing a non-mainnet key.
627+
"""
621628
raw = raw_decode_base58(wif)
622629
if len(raw) == 34:
623630
compressed = True

buidl/test/test_bech32.py

+17-2
Original file line numberDiff line numberDiff line change
@@ -15,45 +15,54 @@ def test_bech32(self):
1515
"mainnet": "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3",
1616
"testnet": "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
1717
"signet": "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
18+
"regtest": "bcrt1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qzf4jry",
1819
},
1920
{
2021
"hex_script": "00200000000000000000000000000000000000000000000000000000000000000000",
2122
"mainnet": "bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthqst8",
2223
"testnet": "tb1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqulkl3g",
2324
"signet": "tb1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqulkl3g",
25+
"regtest": "bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj",
2426
},
2527
{
2628
"hex_script": "00140000000000000000000000000000000000000000",
2729
"mainnet": "bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq9e75rs",
2830
"testnet": "tb1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq0l98cr",
2931
"signet": "tb1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq0l98cr",
32+
"regtest": "bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqdku202",
3033
},
3134
{
3235
"hex_script": "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
3336
"mainnet": "bc1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvses5wp4dt",
3437
"testnet": "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
3538
"signet": "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
39+
"regtest": "bcrt1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvseswlauz7",
3640
},
3741
{
3842
"hex_script": "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6",
3943
"mainnet": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y",
4044
"testnet": "tb1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kxwkgjv",
4145
"signet": "tb1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kxwkgjv",
46+
"regtest": "bcrt1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k0ylj56",
4247
},
4348
{
4449
"hex_script": "5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
4550
"mainnet": "bc1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvses7epu4h",
4651
"testnet": "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c",
4752
"signet": "tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c",
53+
"regtest": "bcrt1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesyga46z",
4854
},
4955
{
5056
"hex_script": "512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
5157
"mainnet": "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0",
5258
"testnet": "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47zagq",
5359
"signet": "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47zagq",
60+
"regtest": "bcrt1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqc8gma6",
5461
},
5562
]
5663
for test in tests:
64+
# Special-case signet here because it will decode as a testnet-inferred
65+
# address.
5766
raw = bytes.fromhex(test["hex_script"])
5867
want = test["signet"]
5968
version = BECH32_ALPHABET.index(want[3:4])
@@ -63,9 +72,15 @@ def test_bech32(self):
6372
self.assertEqual(got_network, "testnet")
6473
self.assertEqual(got_version, version)
6574
self.assertEqual(got_raw, raw[2:])
66-
for network in ("mainnet", "testnet"):
75+
76+
for network in ("mainnet", "testnet", "regtest"):
77+
ver_index = 3
78+
if network == "regtest":
79+
# Account for the two extra letters ("rt") in the regtest prefix.
80+
ver_index += 2
81+
6782
want = test[network]
68-
version = BECH32_ALPHABET.index(want[3:4])
83+
version = BECH32_ALPHABET.index(want[ver_index])
6984
result = encode_bech32_checksum(raw, network=network)
7085
self.assertEqual(result, want)
7186
got_network, got_version, got_raw = decode_bech32(result)

0 commit comments

Comments
 (0)