Skip to content

Commit fe5e495

Browse files
committed
Use Bech32m encoding for v1+ segwit addresses
This also includes updates to the Python test framework implementation, test vectors, and release notes.
1 parent 25b1c6e commit fe5e495

File tree

9 files changed

+375
-428
lines changed

9 files changed

+375
-428
lines changed

contrib/testgen/gen_key_io_test_vectors.py

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from itertools import islice
1616
from base58 import b58encode_chk, b58decode_chk, b58chars
1717
import random
18-
from segwit_addr import bech32_encode, decode_segwit_address, convertbits, CHARSET
18+
from segwit_addr import bech32_encode, decode_segwit_address, convertbits, CHARSET, Encoding
1919

2020
# key types
2121
PUBKEY_ADDRESS = 0
@@ -32,6 +32,7 @@
3232
OP_0 = 0x00
3333
OP_1 = 0x51
3434
OP_2 = 0x52
35+
OP_3 = 0x53
3536
OP_16 = 0x60
3637
OP_DUP = 0x76
3738
OP_EQUAL = 0x87
@@ -44,6 +45,7 @@
4445
script_suffix = (OP_EQUAL,)
4546
p2wpkh_prefix = (OP_0, 20)
4647
p2wsh_prefix = (OP_0, 32)
48+
p2tr_prefix = (OP_1, 32)
4749

4850
metadata_keys = ['isPrivkey', 'chain', 'isCompressed', 'tryCaseFlip']
4951
# templates for valid sequences
@@ -65,29 +67,39 @@
6567
]
6668
# templates for valid bech32 sequences
6769
bech32_templates = [
68-
# hrp, version, witprog_size, metadata, output_prefix
69-
('bc', 0, 20, (False, 'main', None, True), p2wpkh_prefix),
70-
('bc', 0, 32, (False, 'main', None, True), p2wsh_prefix),
71-
('bc', 1, 2, (False, 'main', None, True), (OP_1, 2)),
72-
('tb', 0, 20, (False, 'test', None, True), p2wpkh_prefix),
73-
('tb', 0, 32, (False, 'test', None, True), p2wsh_prefix),
74-
('tb', 2, 16, (False, 'test', None, True), (OP_2, 16)),
75-
('bcrt', 0, 20, (False, 'regtest', None, True), p2wpkh_prefix),
76-
('bcrt', 0, 32, (False, 'regtest', None, True), p2wsh_prefix),
77-
('bcrt', 16, 40, (False, 'regtest', None, True), (OP_16, 40))
70+
# hrp, version, witprog_size, metadata, encoding, output_prefix
71+
('bc', 0, 20, (False, 'main', None, True), Encoding.BECH32, p2wpkh_prefix),
72+
('bc', 0, 32, (False, 'main', None, True), Encoding.BECH32, p2wsh_prefix),
73+
('bc', 1, 32, (False, 'main', None, True), Encoding.BECH32M, p2tr_prefix),
74+
('bc', 2, 2, (False, 'main', None, True), Encoding.BECH32M, (OP_2, 2)),
75+
('tb', 0, 20, (False, 'test', None, True), Encoding.BECH32, p2wpkh_prefix),
76+
('tb', 0, 32, (False, 'test', None, True), Encoding.BECH32, p2wsh_prefix),
77+
('tb', 1, 32, (False, 'test', None, True), Encoding.BECH32M, p2tr_prefix),
78+
('tb', 3, 16, (False, 'test', None, True), Encoding.BECH32M, (OP_3, 16)),
79+
('bcrt', 0, 20, (False, 'regtest', None, True), Encoding.BECH32, p2wpkh_prefix),
80+
('bcrt', 0, 32, (False, 'regtest', None, True), Encoding.BECH32, p2wsh_prefix),
81+
('bcrt', 1, 32, (False, 'regtest', None, True), Encoding.BECH32M, p2tr_prefix),
82+
('bcrt', 16, 40, (False, 'regtest', None, True), Encoding.BECH32M, (OP_16, 40))
7883
]
7984
# templates for invalid bech32 sequences
8085
bech32_ng_templates = [
81-
# hrp, version, witprog_size, invalid_bech32, invalid_checksum, invalid_char
82-
('tc', 0, 20, False, False, False),
83-
('tb', 17, 32, False, False, False),
84-
('bcrt', 3, 1, False, False, False),
85-
('bc', 15, 41, False, False, False),
86-
('tb', 0, 16, False, False, False),
87-
('bcrt', 0, 32, True, False, False),
88-
('bc', 0, 16, True, False, False),
89-
('tb', 0, 32, False, True, False),
90-
('bcrt', 0, 20, False, False, True)
86+
# hrp, version, witprog_size, encoding, invalid_bech32, invalid_checksum, invalid_char
87+
('tc', 0, 20, Encoding.BECH32, False, False, False),
88+
('bt', 1, 32, Encoding.BECH32M, False, False, False),
89+
('tb', 17, 32, Encoding.BECH32M, False, False, False),
90+
('bcrt', 3, 1, Encoding.BECH32M, False, False, False),
91+
('bc', 15, 41, Encoding.BECH32M, False, False, False),
92+
('tb', 0, 16, Encoding.BECH32, False, False, False),
93+
('bcrt', 0, 32, Encoding.BECH32, True, False, False),
94+
('bc', 0, 16, Encoding.BECH32, True, False, False),
95+
('tb', 0, 32, Encoding.BECH32, False, True, False),
96+
('bcrt', 0, 20, Encoding.BECH32, False, False, True),
97+
('bc', 0, 20, Encoding.BECH32M, False, False, False),
98+
('tb', 0, 32, Encoding.BECH32M, False, False, False),
99+
('bcrt', 0, 20, Encoding.BECH32M, False, False, False),
100+
('bc', 1, 32, Encoding.BECH32, False, False, False),
101+
('tb', 2, 16, Encoding.BECH32, False, False, False),
102+
('bcrt', 16, 20, Encoding.BECH32, False, False, False),
91103
]
92104

93105
def is_valid(v):
@@ -127,8 +139,9 @@ def gen_valid_bech32_vector(template):
127139
hrp = template[0]
128140
witver = template[1]
129141
witprog = bytearray(os.urandom(template[2]))
130-
dst_prefix = bytearray(template[4])
131-
rv = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5))
142+
encoding = template[4]
143+
dst_prefix = bytearray(template[5])
144+
rv = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5), encoding)
132145
return rv, dst_prefix + witprog
133146

134147
def gen_valid_vectors():
@@ -186,22 +199,23 @@ def gen_invalid_bech32_vector(template):
186199
hrp = template[0]
187200
witver = template[1]
188201
witprog = bytearray(os.urandom(template[2]))
202+
encoding = template[3]
189203

190204
if no_data:
191-
rv = bech32_encode(hrp, [])
205+
rv = bech32_encode(hrp, [], encoding)
192206
else:
193207
data = [witver] + convertbits(witprog, 8, 5)
194-
if template[3] and not no_data:
208+
if template[4] and not no_data:
195209
if template[2] % 5 in {2, 4}:
196210
data[-1] |= 1
197211
else:
198212
data.append(0)
199-
rv = bech32_encode(hrp, data)
213+
rv = bech32_encode(hrp, data, encoding)
200214

201-
if template[4]:
215+
if template[5]:
202216
i = len(rv) - random.randrange(1, 7)
203217
rv = rv[:i] + random.choice(CHARSET.replace(rv[i], '')) + rv[i + 1:]
204-
if template[5]:
218+
if template[6]:
205219
i = len(hrp) + 1 + random.randrange(0, len(rv) - len(hrp) - 4)
206220
rv = rv[:i] + rv[i:i + 4].upper() + rv[i + 4:]
207221

doc/bips.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
BIPs that are implemented by Bitcoin Core (up-to-date up to **v0.21.0**):
1+
BIPs that are implemented by Bitcoin Core (up-to-date up to **v22.0**):
22

33
* [`BIP 9`](https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki): The changes allowing multiple soft-forks to be deployed in parallel have been implemented since **v0.12.1** ([PR #7575](https://github.com/bitcoin/bitcoin/pull/7575))
44
* [`BIP 11`](https://github.com/bitcoin/bips/blob/master/bip-0011.mediawiki): Multisig outputs are standard since **v0.6.0** ([PR #669](https://github.com/bitcoin/bitcoin/pull/669)).
@@ -50,3 +50,4 @@ BIPs that are implemented by Bitcoin Core (up-to-date up to **v0.21.0**):
5050
* [`BIP 325`](https://github.com/bitcoin/bips/blob/master/bip-0325.mediawiki): Signet test network is supported as of **v0.21.0** ([PR 18267](https://github.com/bitcoin/bitcoin/pull/18267)).
5151
* [`BIP 339`](https://github.com/bitcoin/bips/blob/master/bip-0339.mediawiki): Relay of transactions by wtxid is supported as of **v0.21.0** ([PR 18044](https://github.com/bitcoin/bitcoin/pull/18044)).
5252
* [`BIP 340`](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) [`341`](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki) [`342`](https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki): Validation rules for Taproot (including Schnorr signatures and Tapscript leaves) are implemented as of **v0.21.0** ([PR 19953](https://github.com/bitcoin/bitcoin/pull/19953)), without mainnet activation.
53+
* [`BIP 350`](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki): Addresses for native v1+ segregated Witness outputs use Bech32m instead of Bech32 as of **v22.0** ([PR 20861](https://github.com/bitcoin/bitcoin/pull/20861)).

doc/release-notes-20861.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Updated RPCs
2+
------------
3+
4+
- Due to [BIP 350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki)
5+
being implemented, behavior for all RPCs that accept addresses is changed when
6+
a native witness version 1 (or higher) is passed. These now require a Bech32m
7+
encoding instead of a Bech32 one, and Bech32m encoding will be used for such
8+
addresses in RPC output as well. No version 1 addresses should be created
9+
for mainnet until consensus rules are adopted that give them meaning
10+
(e.g. through [BIP 341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki)).
11+
Once that happens, Bech32m is expected to be used for them, so this shouldn't
12+
affect any production systems, but may be observed on other networks where such
13+
addresses already have meaning (like signet).

src/key_io.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class DestinationEncoder
6262
std::vector<unsigned char> data = {(unsigned char)id.version};
6363
data.reserve(1 + (id.length * 8 + 4) / 5);
6464
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.program, id.program + id.length);
65-
return bech32::Encode(bech32::Encoding::BECH32, m_params.Bech32HRP(), data);
65+
return bech32::Encode(bech32::Encoding::BECH32M, m_params.Bech32HRP(), data);
6666
}
6767

6868
std::string operator()(const CNoDestination& no) const { return {}; }
@@ -96,14 +96,22 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
9696
}
9797
data.clear();
9898
const auto dec = bech32::Decode(str);
99-
if (dec.encoding == bech32::Encoding::BECH32 && dec.data.size() > 0) {
99+
if ((dec.encoding == bech32::Encoding::BECH32 || dec.encoding == bech32::Encoding::BECH32M) && dec.data.size() > 0) {
100100
// Bech32 decoding
101101
error_str = "";
102102
if (dec.hrp != params.Bech32HRP()) {
103103
error_str = "Invalid prefix for Bech32 address";
104104
return CNoDestination();
105105
}
106106
int version = dec.data[0]; // The first 5 bit symbol is the witness version (0-16)
107+
if (version == 0 && dec.encoding != bech32::Encoding::BECH32) {
108+
error_str = "Version 0 witness address must use Bech32 checksum";
109+
return CNoDestination();
110+
}
111+
if (version != 0 && dec.encoding != bech32::Encoding::BECH32M) {
112+
error_str = "Version 1+ witness address must use Bech32m checksum";
113+
return CNoDestination();
114+
}
107115
// The rest of the symbols are converted witness program bytes.
108116
data.reserve(((dec.data.size() - 1) * 5) / 8);
109117
if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, dec.data.begin() + 1, dec.data.end())) {

0 commit comments

Comments
 (0)