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 the relevant test vectors from bip-bech32m.
  • Loading branch information
sipa committed Jan 5, 2021
1 parent a07ee0f commit 05f8733
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 29 deletions.
8 changes: 6 additions & 2 deletions src/key_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class DestinationEncoder : public boost::static_visitor<std::string>
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 @@ -95,9 +95,13 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
std::vector<unsigned char> encdata;
data.clear();
std::tie(encoding, hrp, encdata) = bech32::Decode(str);
if (encoding == bech32::Encoding::BECH32 && encdata.size() > 0 && hrp == params.Bech32HRP()) {
if ((encoding == bech32::Encoding::BECH32 || encoding == bech32::Encoding::BECH32M) &&
encdata.size() > 0 && hrp == params.Bech32HRP()) {
// Bech32 decoding
int version = encdata[0]; // The first 5 bit symbol is the witness version (0-16)
if ((version == 0 && encoding != bech32::Encoding::BECH32) || (version != 0 && encoding != bech32::Encoding::BECH32M)) {
return CNoDestination();
}
// The rest of the symbols are converted witness program bytes.
data.reserve(((encdata.size() - 1) * 5) / 8);
if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, encdata.begin() + 1, encdata.end())) {
Expand Down
45 changes: 45 additions & 0 deletions src/test/data/key_io_invalid.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,51 @@
[
"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv"
],
[
"bc1gmk9yu"
],
[
"tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut"
],
[
"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd"
],
[
"tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf"
],
[
"BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL"
],
[
"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh"
],
[
"tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47"
],
[
"bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4"
],
[
"BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R"
],
[
"bc1pw5dgrnzv"
],
[
"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav"
],
[
"BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P"
],
[
"tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq"
],
[
"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf"
],
[
"tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j"
],
[
"bc1gmk9yu"
]
Expand Down
34 changes: 26 additions & 8 deletions src/test/data/key_io_valid.json
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,25 @@
}
],
[
"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx",
"tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
"0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
{
"isPrivkey": false,
"chain": "test",
"tryCaseFlip": true
}
],
[
"bcrt1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvseswlauz7",
"0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
{
"isPrivkey": false,
"chain": "regtest",
"tryCaseFlip": true
}
],
[
"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y",
"5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6",
{
"isPrivkey": false,
Expand All @@ -495,7 +513,7 @@
}
],
[
"bc1sw50qa3jx3s",
"bc1sw50qgdz25j",
"6002751e",
{
"isPrivkey": false,
Expand All @@ -504,7 +522,7 @@
}
],
[
"bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj",
"bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs",
"5210751e76e8199196d454941c45d1b3a323",
{
"isPrivkey": false,
Expand All @@ -513,20 +531,20 @@
}
],
[
"tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
"0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
"tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c",
"5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
{
"isPrivkey": false,
"chain": "test",
"tryCaseFlip": true
}
],
[
"bcrt1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvseswlauz7",
"0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0",
"512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
{
"isPrivkey": false,
"chain": "regtest",
"chain": "main",
"tryCaseFlip": true
}
]
Expand Down
47 changes: 33 additions & 14 deletions test/functional/test_framework/segwit_addr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@
# Copyright (c) 2017 Pieter Wuille
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Reference implementation for Bech32 and segwit addresses."""
"""Reference implementation for Bech32/Bech32m and segwit addresses."""
import unittest
from enum import Enum

CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
BECH32M_CONST = 0x2bc830a3

class Encoding(Enum):
"""Enumeration type to list the various supported encodings."""
BECH32 = 1
BECH32M = 2


def bech32_polymod(values):
Expand All @@ -27,24 +34,30 @@ def bech32_hrp_expand(hrp):

def bech32_verify_checksum(hrp, data):
"""Verify a checksum given HRP and converted data characters."""
return bech32_polymod(bech32_hrp_expand(hrp) + data) == 1

check = bech32_polymod(bech32_hrp_expand(hrp) + data)
if check == 1:
return Encoding.BECH32
elif check == BECH32M_CONST:
return Encoding.BECH32M
else:
return None

def bech32_create_checksum(hrp, data):
def bech32_create_checksum(hrp, data, spec):
"""Compute the checksum values given HRP and data."""
values = bech32_hrp_expand(hrp) + data
polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ 1
const = BECH32M_CONST if spec == Encoding.BECH32M else 1
polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const
return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]


def bech32_encode(hrp, data):
"""Compute a Bech32 string given HRP and data values."""
combined = data + bech32_create_checksum(hrp, data)
def bech32_encode(hrp, data, spec):
"""Compute a Bech32 or Bech32m string given HRP and data values."""
combined = data + bech32_create_checksum(hrp, data, spec)
return hrp + '1' + ''.join([CHARSET[d] for d in combined])


def bech32_decode(bech):
"""Validate a Bech32 string, and determine HRP and data."""
"""Validate a Bech32/Bech32m string, and determine HRP and data."""
if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or
(bech.lower() != bech and bech.upper() != bech)):
return (None, None)
Expand All @@ -56,9 +69,10 @@ def bech32_decode(bech):
return (None, None)
hrp = bech[:pos]
data = [CHARSET.find(x) for x in bech[pos+1:]]
if not bech32_verify_checksum(hrp, data):
return (None, None)
return (hrp, data[:-6])
spec = bech32_verify_checksum(hrp, data)
if spec is None:
return (None, None, None)
return (hrp, data[:-6], spec)


def convertbits(data, frombits, tobits, pad=True):
Expand Down Expand Up @@ -86,7 +100,7 @@ def convertbits(data, frombits, tobits, pad=True):

def decode_segwit_address(hrp, addr):
"""Decode a segwit address."""
hrpgot, data = bech32_decode(addr)
hrpgot, data, spec = bech32_decode(addr)
if hrpgot != hrp:
return (None, None)
decoded = convertbits(data[1:], 5, 8, False)
Expand All @@ -96,12 +110,15 @@ def decode_segwit_address(hrp, addr):
return (None, None)
if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32:
return (None, None)
if (data[0] == 0 and spec != Encoding.BECH32) or (data[0] != 0 and spec != Encoding.BECH32M):
return (None, None)
return (data[0], decoded)


def encode_segwit_address(hrp, witver, witprog):
"""Encode a segwit address."""
ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5))
spec = Encoding.BECH32 if witver == 0 else Encoding.BECH32M
ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5), spec)
if decode_segwit_address(hrp, ret) == (None, None):
return None
return ret
Expand All @@ -119,3 +136,5 @@ def test_python_bech32(addr):
# P2WSH
test_python_bech32('bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj')
test_python_bech32('bcrt1qft5p2uhsdcdc3l2ua4ap5qqfg4pjaqlp250x7us7a8qqhrxrxfsqseac85')
# P2TR
test_python_bech32('bcrt1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqc8gma6')
10 changes: 5 additions & 5 deletions test/functional/wallet_labels.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,13 @@ def run_test(self):
node.createwallet(wallet_name='watch_only', disable_private_keys=True)
wallet_watch_only = node.get_wallet_rpc('watch_only')
BECH32_VALID = {
'✔️_VER15_PROG40': 'bcrt10qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqn2cjv3',
'✔️_VER16_PROG03': 'bcrt1sqqqqqjq8pdp',
'✔️_VER16_PROB02': 'bcrt1sqqqqqjq8pv',
'✔️_VER15_PROG40': 'bcrt10qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqxkg7fn',
'✔️_VER16_PROG03': 'bcrt1sqqqqq8uhdgr',
'✔️_VER16_PROB02': 'bcrt1sqqqq4wstyw',
}
BECH32_INVALID = {
'❌_VER15_PROG41': 'bcrt10qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzc7xyq',
'❌_VER16_PROB01': 'bcrt1sqqpl9r5c',
'❌_VER15_PROG41': 'bcrt1sqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqajlxj8',
'❌_VER16_PROB01': 'bcrt1sqq5r4036',
}
for l in BECH32_VALID:
ad = BECH32_VALID[l]
Expand Down

0 comments on commit 05f8733

Please sign in to comment.