Skip to content

Commit

Permalink
Use Bech32m encoding for v1+ segwit addresses
Browse files Browse the repository at this point in the history
This also includes updates to the Python test framework implementation,
test vectors, and release notes.
  • Loading branch information
sipa committed Mar 16, 2021
1 parent 25b1c6e commit fe5e495
Show file tree
Hide file tree
Showing 9 changed files with 375 additions and 428 deletions.
70 changes: 42 additions & 28 deletions contrib/testgen/gen_key_io_test_vectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from itertools import islice
from base58 import b58encode_chk, b58decode_chk, b58chars
import random
from segwit_addr import bech32_encode, decode_segwit_address, convertbits, CHARSET
from segwit_addr import bech32_encode, decode_segwit_address, convertbits, CHARSET, Encoding

# key types
PUBKEY_ADDRESS = 0
Expand All @@ -32,6 +32,7 @@
OP_0 = 0x00
OP_1 = 0x51
OP_2 = 0x52
OP_3 = 0x53
OP_16 = 0x60
OP_DUP = 0x76
OP_EQUAL = 0x87
Expand All @@ -44,6 +45,7 @@
script_suffix = (OP_EQUAL,)
p2wpkh_prefix = (OP_0, 20)
p2wsh_prefix = (OP_0, 32)
p2tr_prefix = (OP_1, 32)

metadata_keys = ['isPrivkey', 'chain', 'isCompressed', 'tryCaseFlip']
# templates for valid sequences
Expand All @@ -65,29 +67,39 @@
]
# templates for valid bech32 sequences
bech32_templates = [
# hrp, version, witprog_size, metadata, output_prefix
('bc', 0, 20, (False, 'main', None, True), p2wpkh_prefix),
('bc', 0, 32, (False, 'main', None, True), p2wsh_prefix),
('bc', 1, 2, (False, 'main', None, True), (OP_1, 2)),
('tb', 0, 20, (False, 'test', None, True), p2wpkh_prefix),
('tb', 0, 32, (False, 'test', None, True), p2wsh_prefix),
('tb', 2, 16, (False, 'test', None, True), (OP_2, 16)),
('bcrt', 0, 20, (False, 'regtest', None, True), p2wpkh_prefix),
('bcrt', 0, 32, (False, 'regtest', None, True), p2wsh_prefix),
('bcrt', 16, 40, (False, 'regtest', None, True), (OP_16, 40))
# hrp, version, witprog_size, metadata, encoding, output_prefix
('bc', 0, 20, (False, 'main', None, True), Encoding.BECH32, p2wpkh_prefix),
('bc', 0, 32, (False, 'main', None, True), Encoding.BECH32, p2wsh_prefix),
('bc', 1, 32, (False, 'main', None, True), Encoding.BECH32M, p2tr_prefix),
('bc', 2, 2, (False, 'main', None, True), Encoding.BECH32M, (OP_2, 2)),
('tb', 0, 20, (False, 'test', None, True), Encoding.BECH32, p2wpkh_prefix),
('tb', 0, 32, (False, 'test', None, True), Encoding.BECH32, p2wsh_prefix),
('tb', 1, 32, (False, 'test', None, True), Encoding.BECH32M, p2tr_prefix),
('tb', 3, 16, (False, 'test', None, True), Encoding.BECH32M, (OP_3, 16)),
('bcrt', 0, 20, (False, 'regtest', None, True), Encoding.BECH32, p2wpkh_prefix),
('bcrt', 0, 32, (False, 'regtest', None, True), Encoding.BECH32, p2wsh_prefix),
('bcrt', 1, 32, (False, 'regtest', None, True), Encoding.BECH32M, p2tr_prefix),
('bcrt', 16, 40, (False, 'regtest', None, True), Encoding.BECH32M, (OP_16, 40))
]
# templates for invalid bech32 sequences
bech32_ng_templates = [
# hrp, version, witprog_size, invalid_bech32, invalid_checksum, invalid_char
('tc', 0, 20, False, False, False),
('tb', 17, 32, False, False, False),
('bcrt', 3, 1, False, False, False),
('bc', 15, 41, False, False, False),
('tb', 0, 16, False, False, False),
('bcrt', 0, 32, True, False, False),
('bc', 0, 16, True, False, False),
('tb', 0, 32, False, True, False),
('bcrt', 0, 20, False, False, True)
# hrp, version, witprog_size, encoding, invalid_bech32, invalid_checksum, invalid_char
('tc', 0, 20, Encoding.BECH32, False, False, False),
('bt', 1, 32, Encoding.BECH32M, False, False, False),
('tb', 17, 32, Encoding.BECH32M, False, False, False),
('bcrt', 3, 1, Encoding.BECH32M, False, False, False),
('bc', 15, 41, Encoding.BECH32M, False, False, False),
('tb', 0, 16, Encoding.BECH32, False, False, False),
('bcrt', 0, 32, Encoding.BECH32, True, False, False),
('bc', 0, 16, Encoding.BECH32, True, False, False),
('tb', 0, 32, Encoding.BECH32, False, True, False),
('bcrt', 0, 20, Encoding.BECH32, False, False, True),
('bc', 0, 20, Encoding.BECH32M, False, False, False),
('tb', 0, 32, Encoding.BECH32M, False, False, False),
('bcrt', 0, 20, Encoding.BECH32M, False, False, False),
('bc', 1, 32, Encoding.BECH32, False, False, False),
('tb', 2, 16, Encoding.BECH32, False, False, False),
('bcrt', 16, 20, Encoding.BECH32, False, False, False),
]

def is_valid(v):
Expand Down Expand Up @@ -127,8 +139,9 @@ def gen_valid_bech32_vector(template):
hrp = template[0]
witver = template[1]
witprog = bytearray(os.urandom(template[2]))
dst_prefix = bytearray(template[4])
rv = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5))
encoding = template[4]
dst_prefix = bytearray(template[5])
rv = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5), encoding)
return rv, dst_prefix + witprog

def gen_valid_vectors():
Expand Down Expand Up @@ -186,22 +199,23 @@ def gen_invalid_bech32_vector(template):
hrp = template[0]
witver = template[1]
witprog = bytearray(os.urandom(template[2]))
encoding = template[3]

if no_data:
rv = bech32_encode(hrp, [])
rv = bech32_encode(hrp, [], encoding)
else:
data = [witver] + convertbits(witprog, 8, 5)
if template[3] and not no_data:
if template[4] and not no_data:
if template[2] % 5 in {2, 4}:
data[-1] |= 1
else:
data.append(0)
rv = bech32_encode(hrp, data)
rv = bech32_encode(hrp, data, encoding)

if template[4]:
if template[5]:
i = len(rv) - random.randrange(1, 7)
rv = rv[:i] + random.choice(CHARSET.replace(rv[i], '')) + rv[i + 1:]
if template[5]:
if template[6]:
i = len(hrp) + 1 + random.randrange(0, len(rv) - len(hrp) - 4)
rv = rv[:i] + rv[i:i + 4].upper() + rv[i + 4:]

Expand Down
3 changes: 2 additions & 1 deletion doc/bips.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
BIPs that are implemented by Bitcoin Core (up-to-date up to **v0.21.0**):
BIPs that are implemented by Bitcoin Core (up-to-date up to **v22.0**):

* [`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))
* [`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)).
Expand Down Expand Up @@ -50,3 +50,4 @@ BIPs that are implemented by Bitcoin Core (up-to-date up to **v0.21.0**):
* [`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)).
* [`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)).
* [`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.
* [`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)).
13 changes: 13 additions & 0 deletions doc/release-notes-20861.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Updated RPCs
------------

- Due to [BIP 350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki)
being implemented, behavior for all RPCs that accept addresses is changed when
a native witness version 1 (or higher) is passed. These now require a Bech32m
encoding instead of a Bech32 one, and Bech32m encoding will be used for such
addresses in RPC output as well. No version 1 addresses should be created
for mainnet until consensus rules are adopted that give them meaning
(e.g. through [BIP 341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki)).
Once that happens, Bech32m is expected to be used for them, so this shouldn't
affect any production systems, but may be observed on other networks where such
addresses already have meaning (like signet).
12 changes: 10 additions & 2 deletions src/key_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class DestinationEncoder
std::vector<unsigned char> data = {(unsigned char)id.version};
data.reserve(1 + (id.length * 8 + 4) / 5);
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.program, id.program + id.length);
return bech32::Encode(bech32::Encoding::BECH32, m_params.Bech32HRP(), data);
return bech32::Encode(bech32::Encoding::BECH32M, m_params.Bech32HRP(), data);
}

std::string operator()(const CNoDestination& no) const { return {}; }
Expand Down Expand Up @@ -96,14 +96,22 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
}
data.clear();
const auto dec = bech32::Decode(str);
if (dec.encoding == bech32::Encoding::BECH32 && dec.data.size() > 0) {
if ((dec.encoding == bech32::Encoding::BECH32 || dec.encoding == bech32::Encoding::BECH32M) && dec.data.size() > 0) {
// Bech32 decoding
error_str = "";
if (dec.hrp != params.Bech32HRP()) {
error_str = "Invalid prefix for Bech32 address";
return CNoDestination();
}
int version = dec.data[0]; // The first 5 bit symbol is the witness version (0-16)
if (version == 0 && dec.encoding != bech32::Encoding::BECH32) {
error_str = "Version 0 witness address must use Bech32 checksum";
return CNoDestination();
}
if (version != 0 && dec.encoding != bech32::Encoding::BECH32M) {
error_str = "Version 1+ witness address must use Bech32m checksum";
return CNoDestination();
}
// The rest of the symbols are converted witness program bytes.
data.reserve(((dec.data.size() - 1) * 5) / 8);
if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, dec.data.begin() + 1, dec.data.end())) {
Expand Down
Loading

0 comments on commit fe5e495

Please sign in to comment.