Merge ElementsProject/elements#1025: Introduce blech32m format for v1+ witness programs

c72c949d29 blech32: copy ubsan suppression for bech32 to blech32 (Andrew Poelstra)
d13fb4994b blech32: add test vectors for blech32 and blech32m (Andrew Poelstra)
15a826ea27 blech32: add functional tests for blech32m (Andrew Poelstra)
c01e09e8c3 blech32: add blech32m format and use it to decode witness v1+ addresses (Andrew Poelstra)
18fcec8714 naming nits (Fabian Jahr)
8515f40fed Add signet support to gen_key_io_test_vectors.py (Pieter Wuille)
b3df66f82e Use Bech32m encoding for v1+ segwit addresses (Pieter Wuille)
42f43a1bcb Add Bech32m test vectors (Pieter Wuille)
b1d1d94e01 Implement Bech32m encoding/decoding (Pieter Wuille)
c607835bad Better error messages for invalid addresses (Bezdrighin)

Pull request description:

  Includes backports of https://github.com/bitcoin/bitcoin/pull/20832 (1 commit) and https://github.com/bitcoin/bitcoin/pull/20861 (5 commits)

ACKs for top commit:
  gwillen:
    utACK c72c949.

Tree-SHA512: af96a6ef31b1cab72b0350197dcb34761e9ffb2ec43685084408b5fafcda0adee1945045003194600372652f7cca65d721bda3ed9b6be7e9543d3199e2cbe145
This commit is contained in:
Andrew Poelstra 2021-08-30 18:59:03 +00:00
commit 64194498cf
No known key found for this signature in database
GPG key ID: C588D63CE41B97C1
24 changed files with 1327 additions and 738 deletions

View file

@ -4,5 +4,5 @@ Utilities to generate test vectors for the data-driven Bitcoin tests.
Usage:
PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py valid 50 > ../../src/test/data/key_io_valid.json
PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py invalid 50 > ../../src/test/data/key_io_invalid.json
PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py valid 70 > ../../src/test/data/key_io_valid.json
PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py invalid 70 > ../../src/test/data/key_io_invalid.json

View file

@ -3,11 +3,11 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
'''
Generate valid and invalid base58 address and private key test vectors.
Generate valid and invalid base58/bech32(m) address and private key test vectors.
Usage:
PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py valid 50 > ../../src/test/data/key_io_valid.json
PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py invalid 50 > ../../src/test/data/key_io_invalid.json
PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py valid 70 > ../../src/test/data/key_io_valid.json
PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py invalid 70 > ../../src/test/data/key_io_invalid.json
'''
# 2012 Wladimir J. van der Laan
# Released under MIT License
@ -15,7 +15,7 @@ import os
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
@ -32,6 +32,7 @@ PRIVKEY_REGTEST = 239
OP_0 = 0x00
OP_1 = 0x51
OP_2 = 0x52
OP_3 = 0x53
OP_16 = 0x60
OP_DUP = 0x76
OP_EQUAL = 0x87
@ -44,6 +45,7 @@ script_prefix = (OP_HASH160, 20)
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
@ -54,40 +56,58 @@ templates = [
((SCRIPT_ADDRESS,), 20, (), (False, 'main', None, None), script_prefix, script_suffix),
((PUBKEY_ADDRESS_TEST,), 20, (), (False, 'test', None, None), pubkey_prefix, pubkey_suffix),
((SCRIPT_ADDRESS_TEST,), 20, (), (False, 'test', None, None), script_prefix, script_suffix),
((PUBKEY_ADDRESS_TEST,), 20, (), (False, 'signet', None, None), pubkey_prefix, pubkey_suffix),
((SCRIPT_ADDRESS_TEST,), 20, (), (False, 'signet', None, None), script_prefix, script_suffix),
((PUBKEY_ADDRESS_REGTEST,), 20, (), (False, 'regtest', None, None), pubkey_prefix, pubkey_suffix),
((SCRIPT_ADDRESS_REGTEST,), 20, (), (False, 'regtest', None, None), script_prefix, script_suffix),
((PRIVKEY,), 32, (), (True, 'main', False, None), (), ()),
((PRIVKEY,), 32, (1,), (True, 'main', True, None), (), ()),
((PRIVKEY_TEST,), 32, (), (True, 'test', False, None), (), ()),
((PRIVKEY_TEST,), 32, (1,), (True, 'test', True, None), (), ()),
((PRIVKEY_TEST,), 32, (), (True, 'signet', False, None), (), ()),
((PRIVKEY_TEST,), 32, (1,), (True, 'signet', True, None), (), ()),
((PRIVKEY_REGTEST,), 32, (), (True, 'regtest', False, None), (), ()),
((PRIVKEY_REGTEST,), 32, (1,), (True, 'regtest', True, None), (), ())
]
# 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)),
('tb', 0, 20, (False, 'signet', None, True), Encoding.BECH32, p2wpkh_prefix),
('tb', 0, 32, (False, 'signet', None, True), Encoding.BECH32, p2wsh_prefix),
('tb', 1, 32, (False, 'signet', None, True), Encoding.BECH32M, p2tr_prefix),
('tb', 3, 32, (False, 'signet', None, True), Encoding.BECH32M, (OP_3, 32)),
('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):
@ -127,8 +147,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():
@ -186,22 +207,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:]

View file

@ -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)).
@ -46,3 +46,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)).

View file

@ -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).

View file

@ -1,4 +1,4 @@
// Copyright (c) 2017 Pieter Wuille
// Copyright (c) 2017, 2021 Pieter Wuille
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@ -12,10 +12,10 @@ namespace bech32
typedef std::vector<uint8_t> data;
/** The Bech32 character set for encoding. */
/** The Bech32 and Bech32m character set for encoding. */
const char* CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
/** The Bech32 character set for decoding. */
/** The Bech32 and Bech32m character set for decoding. */
const int8_t CHARSET_REV[128] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
@ -27,6 +27,12 @@ const int8_t CHARSET_REV[128] = {
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1
};
/* Determine the final constant to use for the specified encoding. */
uint32_t EncodingConstant(Encoding encoding) {
assert(encoding == Encoding::BECH32 || encoding == Encoding::BECH32M);
return encoding == Encoding::BECH32 ? 1 : 0x2bc830a3;
}
/** This function will compute what 6 5-bit values to XOR into the last 6 input values, in order to
* make the checksum 0. These 6 values are packed together in a single 30-bit integer. The higher
* bits correspond to earlier values. */
@ -111,21 +117,24 @@ data ExpandHRP(const std::string& hrp)
}
/** Verify a checksum. */
bool VerifyChecksum(const std::string& hrp, const data& values)
Encoding VerifyChecksum(const std::string& hrp, const data& values)
{
// PolyMod computes what value to xor into the final values to make the checksum 0. However,
// if we required that the checksum was 0, it would be the case that appending a 0 to a valid
// list of values would result in a new valid list. For that reason, Bech32 requires the
// resulting checksum to be 1 instead.
return PolyMod(Cat(ExpandHRP(hrp), values)) == 1;
// resulting checksum to be 1 instead. In Bech32m, this constant was amended.
const uint32_t check = PolyMod(Cat(ExpandHRP(hrp), values));
if (check == EncodingConstant(Encoding::BECH32)) return Encoding::BECH32;
if (check == EncodingConstant(Encoding::BECH32M)) return Encoding::BECH32M;
return Encoding::INVALID;
}
/** Create a checksum. */
data CreateChecksum(const std::string& hrp, const data& values)
data CreateChecksum(Encoding encoding, const std::string& hrp, const data& values)
{
data enc = Cat(ExpandHRP(hrp), values);
enc.resize(enc.size() + 6); // Append 6 zeroes
uint32_t mod = PolyMod(enc) ^ 1; // Determine what to XOR into those 6 zeroes.
uint32_t mod = PolyMod(enc) ^ EncodingConstant(encoding); // Determine what to XOR into those 6 zeroes.
data ret(6);
for (size_t i = 0; i < 6; ++i) {
// Convert the 5-bit groups in mod to checksum values.
@ -134,13 +143,13 @@ data CreateChecksum(const std::string& hrp, const data& values)
return ret;
}
/** Encode a Bech32 string. */
std::string Encode(const std::string& hrp, const data& values) {
// First ensure that the HRP is all lowercase. BIP-173 requires an encoder
// to return a lowercase Bech32 string, but if given an uppercase HRP, the
/** Encode a Bech32 or Bech32m string. */
std::string Encode(Encoding encoding, const std::string& hrp, const data& values) {
// First ensure that the HRP is all lowercase. BIP-173 and BIP350 require an encoder
// to return a lowercase Bech32/Bech32m string, but if given an uppercase HRP, the
// result will always be invalid.
for (const char& c : hrp) assert(c < 'A' || c > 'Z');
data checksum = CreateChecksum(hrp, values);
data checksum = CreateChecksum(encoding, hrp, values);
data combined = Cat(values, checksum);
std::string ret = hrp + '1';
ret.reserve(ret.size() + combined.size());
@ -150,8 +159,8 @@ std::string Encode(const std::string& hrp, const data& values) {
return ret;
}
/** Decode a Bech32 string. */
std::pair<std::string, data> Decode(const std::string& str) {
/** Decode a Bech32 or Bech32m string. */
DecodeResult Decode(const std::string& str) {
bool lower = false, upper = false;
for (size_t i = 0; i < str.size(); ++i) {
unsigned char c = str[i];
@ -178,10 +187,9 @@ std::pair<std::string, data> Decode(const std::string& str) {
for (size_t i = 0; i < pos; ++i) {
hrp += LowerCase(str[i]);
}
if (!VerifyChecksum(hrp, values)) {
return {};
}
return {hrp, data(values.begin(), values.end() - 6)};
Encoding result = VerifyChecksum(hrp, values);
if (result == Encoding::INVALID) return {};
return {result, std::move(hrp), data(values.begin(), values.end() - 6)};
}
} // namespace bech32

View file

@ -1,13 +1,14 @@
// Copyright (c) 2017 Pieter Wuille
// Copyright (c) 2017, 2021 Pieter Wuille
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// Bech32 is a string encoding format used in newer address types.
// The output consists of a human-readable part (alphanumeric), a
// separator character (1), and a base32 data section, the last
// 6 characters of which are a checksum.
// Bech32 and Bech32m are string encoding formats used in newer
// address types. The outputs consist of a human-readable part
// (alphanumeric), a separator character (1), and a base32 data
// section, the last 6 characters of which are a checksum. The
// module is namespaced under bech32 for historical reasons.
//
// For more information, see BIP 173.
// For more information, see BIP 173 and BIP 350.
#ifndef BITCOIN_BECH32_H
#define BITCOIN_BECH32_H
@ -19,11 +20,29 @@
namespace bech32
{
/** Encode a Bech32 string. If hrp contains uppercase characters, this will cause an assertion error. */
std::string Encode(const std::string& hrp, const std::vector<uint8_t>& values);
enum class Encoding {
INVALID, //!< Failed decoding
/** Decode a Bech32 string. Returns (hrp, data). Empty hrp means failure. */
std::pair<std::string, std::vector<uint8_t>> Decode(const std::string& str);
BECH32, //!< Bech32 encoding as defined in BIP173
BECH32M, //!< Bech32m encoding as defined in BIP350
};
/** Encode a Bech32 or Bech32m string. If hrp contains uppercase characters, this will cause an
* assertion error. Encoding must be one of BECH32 or BECH32M. */
std::string Encode(Encoding encoding, const std::string& hrp, const std::vector<uint8_t>& values);
struct DecodeResult
{
Encoding encoding; //!< What encoding was detected in the result; Encoding::INVALID if failed.
std::string hrp; //!< The human readable part
std::vector<uint8_t> data; //!< The payload (excluding checksum)
DecodeResult() : encoding(Encoding::INVALID) {}
DecodeResult(Encoding enc, std::string&& h, std::vector<uint8_t>&& d) : encoding(enc), hrp(std::move(h)), data(std::move(d)) {}
};
/** Decode a Bech32 or Bech32m string. */
DecodeResult Decode(const std::string& str);
/// Exported for testing.
uint32_t PolyMod(const std::vector<uint8_t>& v);

View file

@ -19,7 +19,7 @@ static void Bech32Encode(benchmark::Bench& bench)
tmp.reserve(1 + 32 * 8 / 5);
ConvertBits<8, 5, true>([&](unsigned char c) { tmp.push_back(c); }, v.begin(), v.end());
bench.batch(v.size()).unit("byte").run([&] {
bech32::Encode("bc", tmp);
bech32::Encode(bech32::Encoding::BECH32, "bc", tmp);
});
}

View file

@ -1,9 +1,10 @@
// Copyright (c) 2017 Pieter Wuille
// Copyright (c) 2017, 2021 Pieter Wuille
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <blech32.h>
#include <assert.h>
/*
* IMPORTANT NOTE: Comments below may largely pertain for bech32, not blech32.
* Some of these magic constants have changes.
@ -16,10 +17,10 @@ namespace blech32
typedef std::vector<uint8_t> data;
/** The Blech32 character set for encoding. */
/** The Blech32 and Blech32m character set for encoding. */
const char* CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
/** The Blech32 character set for decoding. */
/** The Blech32 and Blech32m character set for decoding. */
const int8_t CHARSET_REV[128] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
@ -31,6 +32,23 @@ const int8_t CHARSET_REV[128] = {
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1
};
// ELEMENTS: this encoding constant is a random 60-byte number generated
// in sage. For bech32m, Pieter did a fair bit of grinding to choose
// an optimal constant. For blech32m we did no such grinding ... but
// having said this, our only hard design constraint is that we need to
// avoid 1, which has special algebraic structure that leads to extension
// attacks.
//
// Further, because blech32m is a longer code than bech32m, doing an
// analogous grinding project would be intractable, even if we were
// willing to put the time and effort in.
/* Determine the final constant to use for the specified encoding. */
uint64_t EncodingConstant(Encoding encoding) {
assert(encoding == Encoding::BLECH32 || encoding == Encoding::BLECH32M);
return encoding == Encoding::BLECH32 ? 1 : 0x455972a3350f7a1;
}
/** Concatenate two byte arrays. */
data Cat(data x, const data& y)
{
@ -65,7 +83,7 @@ uint64_t PolyMod(const data& v)
// During the course of the loop below, `c` contains the bitpacked coefficients of the
// polynomial constructed from just the values of v that were processed so far, mod g(x). In
// the above example, `c` initially corresponds to 1 mod (x), and after processing 2 inputs of
// the above example, `c` initially corresponds to 1 mod g(x), and after processing 2 inputs of
// v, it corresponds to x^2 + v0*x + v1 mod g(x). As 1 mod g(x) = 1, that is the starting value
// for `c`.
uint64_t c = 1;
@ -122,21 +140,24 @@ data ExpandHRP(const std::string& hrp)
}
/** Verify a checksum. */
bool VerifyChecksum(const std::string& hrp, const data& values)
Encoding VerifyChecksum(const std::string& hrp, const data& values)
{
// PolyMod computes what value to xor into the final values to make the checksum 0. However,
// if we required that the checksum was 0, it would be the case that appending a 0 to a valid
// list of values would result in a new valid list. For that reason, Blech32 requires the
// resulting checksum to be 1 instead.
return PolyMod(Cat(ExpandHRP(hrp), values)) == 1;
// resulting checksum to be 1 instead. In Blech32m, this constant was amended.
const uint64_t check = PolyMod(Cat(ExpandHRP(hrp), values));
if (check == EncodingConstant(Encoding::BLECH32)) return Encoding::BLECH32;
if (check == EncodingConstant(Encoding::BLECH32M)) return Encoding::BLECH32M;
return Encoding::INVALID;
}
/** Create a checksum. */
data CreateChecksum(const std::string& hrp, const data& values)
data CreateChecksum(Encoding encoding, const std::string& hrp, const data& values)
{
data enc = Cat(ExpandHRP(hrp), values);
enc.resize(enc.size() + 12); // ELEMENTS: Append 6->12 zeroes
uint64_t mod = PolyMod(enc) ^ 1; // Determine what to XOR into those 6 zeroes.
uint64_t mod = PolyMod(enc) ^ EncodingConstant(encoding); // Determine what to XOR into those 6 zeroes.
data ret(12); // ELEMENTS: 6->12
for (size_t i = 0; i < 12; ++i) { // ELEMENTS: 6->12
// Convert the 5-bit groups in mod to checksum values.
@ -145,9 +166,13 @@ data CreateChecksum(const std::string& hrp, const data& values)
return ret;
}
/** Encode a Blech32 string. */
std::string Encode(const std::string& hrp, const data& values) {
data checksum = CreateChecksum(hrp, values);
/** Encode a Blech32 or Blech32m string. */
std::string Encode(Encoding encoding, const std::string& hrp, const data& values) {
// First ensure that the HRP is all lowercase. BIP-173 and BIP350 require an encoder
// to return a lowercase Blech32/Blech32m string, but if given an uppercase HRP, the
// result will always be invalid.
for (const char& c : hrp) assert(c < 'A' || c > 'Z');
data checksum = CreateChecksum(encoding, hrp, values);
data combined = Cat(values, checksum);
std::string ret = hrp + '1';
ret.reserve(ret.size() + combined.size());
@ -157,8 +182,8 @@ std::string Encode(const std::string& hrp, const data& values) {
return ret;
}
/** Decode a Blech32 string. */
std::pair<std::string, data> Decode(const std::string& str) {
/** Decode a Blech32 or Blech32m string. */
DecodeResult Decode(const std::string& str) {
bool lower = false, upper = false;
for (size_t i = 0; i < str.size(); ++i) {
unsigned char c = str[i];
@ -185,10 +210,9 @@ std::pair<std::string, data> Decode(const std::string& str) {
for (size_t i = 0; i < pos; ++i) {
hrp += LowerCase(str[i]);
}
if (!VerifyChecksum(hrp, values)) {
return {};
}
return {hrp, data(values.begin(), values.end() - 12)};
Encoding result = VerifyChecksum(hrp, values);
if (result == Encoding::INVALID) return {};
return {result, std::move(hrp), data(values.begin(), values.end() - 12)};
}
} // namespace blech32

View file

@ -2,12 +2,13 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// Bech32 is a string encoding format used in newer address types.
// The output consists of a human-readable part (alphanumeric), a
// separator character (1), and a base32 data section, the last
// 6 characters of which are a checksum.
// Bech32 and Bech32m are string encoding formats used in newer
// address types. The outputs consist of a human-readable part
// (alphanumeric), a separator character (1), and a base32 data
// section, the last 6 characters of which are a checksum. The
// module is namespaced under bech32 for historical reasons.
//
// For more information, see BIP 173.
// For more information, see BIP 173 and BIP 350.
#ifndef BITCOIN_BLECH32_H
#define BITCOIN_BLECH32_H
@ -19,16 +20,34 @@
namespace blech32
{
/** Encode a Bech32 string. Returns the empty string in case of failure. */
std::string Encode(const std::string& hrp, const std::vector<uint8_t>& values);
enum class Encoding {
INVALID, //!< Failed decoding
/** Decode a Bech32 string. Returns (hrp, data). Empty hrp means failure. */
std::pair<std::string, std::vector<uint8_t>> Decode(const std::string& str);
BLECH32, //!< Original blech32
BLECH32M, //!< Blech32m tweaked a la bech32
};
/** Encode a Blech32 or Blech32m string. If hrp contains uppercase characters, this will cause an
* assertion error. Encoding must be one of BLECH32 or BLECH32M. */
std::string Encode(Encoding encoding, const std::string& hrp, const std::vector<uint8_t>& values);
struct DecodeResult
{
Encoding encoding; //!< What encoding was detected in the result; Encoding::INVALID if failed.
std::string hrp; //!< The human readable part
std::vector<uint8_t> data; //!< The payload (excluding checksum)
DecodeResult() : encoding(Encoding::INVALID) {}
DecodeResult(Encoding enc, std::string&& h, std::vector<uint8_t>&& d) : encoding(enc), hrp(std::move(h)), data(std::move(d)) {}
};
/** Decode a Blech32 or Blech32m string */
DecodeResult Decode(const std::string& str);
/// Exported for testing.
uint64_t PolyMod(const std::vector<uint8_t>& v);
/// Exported for testing.
std::vector<uint8_t> CreateChecksum(const std::string& hrp, const std::vector<uint8_t>& values);
std::vector<uint8_t> CreateChecksum(Encoding encoding, const std::string& hrp, const std::vector<uint8_t>& values);
} // namespace blech32

View file

@ -17,6 +17,11 @@
#include <string.h>
#include <algorithm>
/// Maximum witness length for Bech32 addresses.
static constexpr std::size_t BECH32_WITNESS_PROG_MAX_LEN = 40;
/// Maximum witness length for Blech32 addresses. Note this does not include the extra 33-byte blinding key.
static constexpr std::size_t BLECH32_WITNESS_PROG_MAX_LEN = 40;
namespace
{
class DestinationEncoder : public boost::static_visitor<std::string>
@ -74,12 +79,12 @@ public:
bytes.insert(bytes.end(), id.begin(), id.end());
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, bytes.begin(), bytes.end());
const std::string& hrp = for_parent ? m_params.ParentBlech32HRP() : m_params.Blech32HRP();
return blech32::Encode(hrp, data);
return blech32::Encode(blech32::Encoding::BLECH32, hrp, data);
}
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.begin(), id.end());
const std::string& hrp = for_parent ? m_params.ParentBech32HRP() : m_params.Bech32HRP();
return bech32::Encode(hrp, data);
return bech32::Encode(bech32::Encoding::BECH32, hrp, data);
}
std::string operator()(const WitnessV0ScriptHash& id) const
@ -91,12 +96,12 @@ public:
bytes.insert(bytes.end(), id.begin(), id.end());
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, bytes.begin(), bytes.end());
const std::string& hrp = for_parent ? m_params.ParentBlech32HRP() : m_params.Blech32HRP();
return blech32::Encode(hrp, data);
return blech32::Encode(blech32::Encoding::BLECH32, hrp, data);
}
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.begin(), id.end());
const std::string& hrp = for_parent ? m_params.ParentBech32HRP() : m_params.Bech32HRP();
return bech32::Encode(hrp, data);
return bech32::Encode(bech32::Encoding::BECH32, hrp, data);
}
std::string operator()(const WitnessUnknown& id) const
@ -111,23 +116,24 @@ public:
bytes.insert(bytes.end(), id.program, id.program + id.length);
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, bytes.begin(), bytes.end());
const std::string& hrp = for_parent ? m_params.ParentBlech32HRP() : m_params.Blech32HRP();
return blech32::Encode(hrp, data);
return blech32::Encode(blech32::Encoding::BLECH32M, hrp, data);
}
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.program, id.program + id.length);
const std::string& hrp = for_parent ? m_params.ParentBech32HRP() : m_params.Bech32HRP();
return bech32::Encode(hrp, data);
return bech32::Encode(bech32::Encoding::BECH32M, hrp, data);
}
std::string operator()(const CNoDestination& no) const { return {}; }
std::string operator()(const NullData& null) const { return {}; }
};
CTxDestination DecodeDestination(const std::string& str, const CChainParams& params, const bool for_parent)
CTxDestination DecodeDestination(const std::string& str, const CChainParams& params, const bool for_parent, std::string& error_str)
{
std::vector<unsigned char> data;
size_t pk_size = CPubKey::COMPRESSED_SIZE;
uint160 hash;
error_str = "";
if (DecodeBase58Check(str, data, 55)) {
// base58-encoded Bitcoin addresses.
// Public-key-hash-addresses have version 0 (or 111 testnet).
@ -167,16 +173,35 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
std::copy(payload_start + pk_size, data.end(), hash.begin());
return ScriptHash(CScriptID(hash), pubkey);
}
// Set potential error message.
// This message may be changed if the address can also be interpreted as a Bech32 address.
error_str = "Invalid prefix for Base58-encoded address";
}
data.clear();
auto bech = bech32::Decode(str);
const auto dec = bech32::Decode(str);
const std::string& hrp = for_parent ? params.ParentBech32HRP() : params.Bech32HRP();
if (bech.second.size() > 0 && bech.first == hrp) {
if ((dec.encoding == bech32::Encoding::BECH32 || dec.encoding == bech32::Encoding::BECH32M) && dec.data.size() > 0) {
// Bech32 decoding
int version = bech.second[0]; // The first 5 bit symbol is the witness version (0-16)
error_str = "";
if (dec.hrp != hrp) {
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(((bech.second.size() - 1) * 5) / 8);
if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, bech.second.begin() + 1, bech.second.end())) {
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())) {
if (version == 0) {
{
WitnessV0KeyHash keyid;
@ -192,11 +217,21 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
return scriptid;
}
}
error_str = "Invalid Bech32 v0 address data size";
return CNoDestination();
}
if (version > 16 || data.size() < 2 || data.size() > 40) {
if (version > 16) {
error_str = "Invalid Bech32 address witness version";
return CNoDestination();
}
if (data.size() < 2 || data.size() > BECH32_WITNESS_PROG_MAX_LEN) {
error_str = "Invalid Bech32 address data size";
return CNoDestination();
}
WitnessUnknown unk;
unk.version = version;
std::copy(data.begin(), data.end(), unk.program);
@ -208,14 +243,29 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
data.clear();
auto blech = blech32::Decode(str);
const std::string& bl_hrp = for_parent ? params.ParentBlech32HRP() : params.Blech32HRP();
if (blech.second.size() > 0 && blech.first == bl_hrp) {
if ((blech.encoding == blech32::Encoding::BLECH32 || blech.encoding == blech32::Encoding::BLECH32M) && blech.data.size() > 0) {
// Blech32 decoding
int version = blech.second[0]; // The first 5 bit symbol is the witness version (0-16)
error_str = "";
data.reserve(((blech.second.size() - 1) * 5) / 8);
if (blech.hrp != bl_hrp) {
error_str = "Invalid prefix for Blech32 address";
return CNoDestination();
}
int version = blech.data[0]; // The first 5 bit symbol is the witness version (0-16)
if (version == 0 && blech.encoding != blech32::Encoding::BLECH32) {
error_str = "Version 0 witness address must use Blech32 checksum";
return CNoDestination();
}
if (version != 0 && blech.encoding != blech32::Encoding::BLECH32M) {
error_str = "Version 1+ witness address must use Blech32m checksum";
return CNoDestination();
}
data.reserve(((blech.data.size() - 1) * 5) / 8);
// The rest of the symbols are converted blinding pubkey and witness program bytes.
if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, blech.second.begin() + 1, blech.second.end())) {
if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, blech.data.begin() + 1, blech.data.end())) {
// Must be long enough for blinding key and other data taken below
if (data.size() < 34) {
return CNoDestination();
@ -240,11 +290,21 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
return scriptid;
}
}
error_str = "Invalid Blech32 v0 address data size";
return CNoDestination();
}
if (version > 16 || data.size() < 2 || data.size() > 40) {
if (version > 16) {
error_str = "Invalid Blech32 address witness version";
return CNoDestination();
}
if (data.size() < 2 || data.size() > BLECH32_WITNESS_PROG_MAX_LEN) {
error_str = "Invalid Blech32 address data size";
return CNoDestination();
}
WitnessUnknown unk;
unk.version = version;
std::copy(data.begin(), data.end(), unk.program);
@ -254,6 +314,9 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
}
}
// Set error message if address can't be interpreted as Base58 or Bech32 or Blech32.
if (error_str.empty()) error_str = "Invalid address format";
return CNoDestination();
}
} // namespace
@ -341,32 +404,41 @@ std::string EncodeDestination(const CTxDestination& dest)
return boost::apply_visitor(DestinationEncoder(Params(), false), dest);
}
CTxDestination DecodeDestination(const std::string& str, std::string& error_msg)
{
return DecodeDestination(str, Params(), false, error_msg);
}
CTxDestination DecodeDestination(const std::string& str)
{
return DecodeDestination(str, Params(), false);
std::string error_msg;
return DecodeDestination(str, Params(), false, error_msg);
}
bool IsValidDestinationString(const std::string& str, const CChainParams& params)
{
return IsValidDestination(DecodeDestination(str, params, false));
std::string error_msg;
return IsValidDestination(DecodeDestination(str, params, false, error_msg));
}
bool IsValidDestinationString(const std::string& str)
{
return IsValidDestination(DecodeDestination(str, Params(), false));
std::string error_msg;
return IsValidDestination(DecodeDestination(str, Params(), false, error_msg));
}
//
// ELEMENTS
std::string EncodeParentDestination(const CTxDestination& dest)
{
return boost::apply_visitor(DestinationEncoder(Params(), true), dest);
}
CTxDestination DecodeParentDestination(const std::string& str)
CTxDestination DecodeParentDestination(const std::string& str, std::string& error_msg)
{
return DecodeDestination(str, Params(), true);
return DecodeDestination(str, Params(), true, error_msg);
}
// ELEMENTS

View file

@ -23,11 +23,12 @@ std::string EncodeExtPubKey(const CExtPubKey& extpubkey);
std::string EncodeDestination(const CTxDestination& dest);
CTxDestination DecodeDestination(const std::string& str);
CTxDestination DecodeDestination(const std::string& str, std::string& error_msg);
bool IsValidDestinationString(const std::string& str);
bool IsValidDestinationString(const std::string& str, const CChainParams& params);
// ELEMENTS
std::string EncodeParentDestination(const CTxDestination& dest);
CTxDestination DecodeParentDestination(const std::string& str);
CTxDestination DecodeParentDestination(const std::string& str, std::string& error_msg);
#endif // BITCOIN_KEY_IO_H

View file

@ -43,9 +43,9 @@ static RPCHelpMan validateaddress()
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::BOOL, "isvalid", "If the address is valid or not. If not, this is the only property returned."},
{RPCResult::Type::BOOL, "isvalid_parent", "If the address is valid or not for parent chain. Valid or not, no additional details will be appended unlike `isvalid`"},
{RPCResult::Type::STR, "address", "The address validated"},
{RPCResult::Type::BOOL, "isvalid", "If the address is valid or not"},
{RPCResult::Type::BOOL, "isvalid_parent", "If the address is valid or not for parent chain"},
{RPCResult::Type::STR, "address", "The bitcoin address validated"},
{RPCResult::Type::STR_HEX, "scriptPubKey", "The hex-encoded scriptPubKey generated by the address"},
{RPCResult::Type::BOOL, "isscript", "If the key is a script"},
{RPCResult::Type::BOOL, "iswitness", "If the address is a witness address"},
@ -58,6 +58,8 @@ static RPCHelpMan validateaddress()
{RPCResult::Type::STR, "address", ""},
{RPCResult::Type::STR_HEX, "scriptPubKey", ""},
}},
{RPCResult::Type::STR, "error", /* optional */ true, "Error message, if any"},
{RPCResult::Type::STR, "error_parent", /* optional */ true, "Error message, if any"},
}
},
RPCExamples{
@ -66,15 +68,19 @@ static RPCHelpMan validateaddress()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
CTxDestination dest = DecodeDestination(request.params[0].get_str());
CTxDestination parent_dest = DecodeParentDestination(request.params[0].get_str());
std::string error_msg;
CTxDestination dest = DecodeDestination(request.params[0].get_str(), error_msg);
std::string error_msg_parent;
CTxDestination parent_dest = DecodeParentDestination(request.params[0].get_str(), error_msg_parent);
bool isValid = IsValidDestination(dest);
bool is_valid_parent = IsValidDestination(parent_dest);
CHECK_NONFATAL(isValid == error_msg.empty());
CHECK_NONFATAL(is_valid_parent == error_msg_parent.empty());
UniValue ret(UniValue::VOBJ);
ret.pushKV("isvalid", isValid);
ret.pushKV("isvalid_parent", is_valid_parent);
if (isValid)
{
if (isValid) {
std::string currentAddress = EncodeDestination(dest);
ret.pushKV("address", currentAddress);
@ -100,6 +106,11 @@ static RPCHelpMan validateaddress()
parent_info.pushKVs(blind_detail);
ret.pushKV("parent_address_info", parent_info);
}
if (!isValid && !is_valid_parent) {
ret.pushKV("error", error_msg);
ret.pushKV("error_parent", error_msg_parent);
}
return ret;
},
};

View file

@ -10,7 +10,7 @@
BOOST_FIXTURE_TEST_SUITE(bech32_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(bip173_testvectors_valid)
BOOST_AUTO_TEST_CASE(bech32_testvectors_valid)
{
static const std::string CASES[] = {
"A12UEL5L",
@ -22,15 +22,35 @@ BOOST_AUTO_TEST_CASE(bip173_testvectors_valid)
"?1ezyfcl",
};
for (const std::string& str : CASES) {
auto ret = bech32::Decode(str);
BOOST_CHECK(!ret.first.empty());
std::string recode = bech32::Encode(ret.first, ret.second);
const auto dec = bech32::Decode(str);
BOOST_CHECK(dec.encoding == bech32::Encoding::BECH32);
std::string recode = bech32::Encode(bech32::Encoding::BECH32, dec.hrp, dec.data);
BOOST_CHECK(!recode.empty());
BOOST_CHECK(CaseInsensitiveEqual(str, recode));
}
}
BOOST_AUTO_TEST_CASE(bip173_testvectors_invalid)
BOOST_AUTO_TEST_CASE(bech32m_testvectors_valid)
{
static const std::string CASES[] = {
"A1LQFN3A",
"a1lqfn3a",
"an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6",
"abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx",
"11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8",
"split1checkupstagehandshakeupstreamerranterredcaperredlc445v",
"?1v759aa"
};
for (const std::string& str : CASES) {
const auto dec = bech32::Decode(str);
BOOST_CHECK(dec.encoding == bech32::Encoding::BECH32M);
std::string recode = bech32::Encode(bech32::Encoding::BECH32M, dec.hrp, dec.data);
BOOST_CHECK(!recode.empty());
BOOST_CHECK(CaseInsensitiveEqual(str, recode));
}
}
BOOST_AUTO_TEST_CASE(bech32_testvectors_invalid)
{
static const std::string CASES[] = {
" 1nwldj5",
@ -49,8 +69,32 @@ BOOST_AUTO_TEST_CASE(bip173_testvectors_invalid)
"A12uEL5L",
};
for (const std::string& str : CASES) {
auto ret = bech32::Decode(str);
BOOST_CHECK(ret.first.empty());
const auto dec = bech32::Decode(str);
BOOST_CHECK(dec.encoding == bech32::Encoding::INVALID);
}
}
BOOST_AUTO_TEST_CASE(bech32m_testvectors_invalid)
{
static const std::string CASES[] = {
" 1xj0phk",
"\x7f""1g6xzxy",
"\x80""1vctc34",
"an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4",
"qyrz8wqd2c9m",
"1qyrz8wqd2c9m",
"y1b0jsk6g",
"lt1igcx5c0",
"in1muywd",
"mm1crxm3i",
"au1s5cgom",
"M1VUXWEZ",
"16plkw9",
"1p2gdwpf"
};
for (const std::string& str : CASES) {
const auto dec = bech32::Decode(str);
BOOST_CHECK(dec.encoding == bech32::Encoding::INVALID);
}
}

View file

@ -6,11 +6,100 @@
#include <random.h>
#include <util/strencodings.h>
#include <test/util/setup_common.h>
#include <test/util/str.h>
#include <boost/test/unit_test.hpp>
BOOST_FIXTURE_TEST_SUITE(blech32_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(blech32_testvectors_valid)
{
static const std::string CASES[] = {
"A133NZFWEYK7UT",
"a133nzfweyk7ut",
"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio195jhgldwsn5j",
"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lgmcn7l7t7xve",
"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xldwlutcw2l",
"split1checkupstagehandshakeupstreamerranterredcaperredegneyqml9esp",
"?19dv34t3p4s35",
};
for (const std::string& str : CASES) {
const auto dec = blech32::Decode(str);
BOOST_CHECK(dec.encoding == blech32::Encoding::BLECH32);
std::string recode = blech32::Encode(blech32::Encoding::BLECH32, dec.hrp, dec.data);
BOOST_CHECK(!recode.empty());
BOOST_CHECK(CaseInsensitiveEqual(str, recode));
}
}
BOOST_AUTO_TEST_CASE(blech32m_testvectors_valid)
{
static const std::string CASES[] = {
"A1EYL4VXQ3HRPT",
"a1eyl4vxq3hrpt",
"an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11yatn6l85muud",
"abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqg8m5pqg67zq3",
"11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll09wxh8ajdvxv",
"split1checkupstagehandshakeupstreamerranterredcaperred3alwpgz2yydp",
"?1dcqxsrg55dv5"
};
for (const std::string& str : CASES) {
const auto dec = blech32::Decode(str);
BOOST_CHECK(dec.encoding == blech32::Encoding::BLECH32M);
std::string recode = blech32::Encode(blech32::Encoding::BLECH32M, dec.hrp, dec.data);
BOOST_CHECK(!recode.empty());
BOOST_CHECK(CaseInsensitiveEqual(str, recode));
}
}
BOOST_AUTO_TEST_CASE(blech32m_testvectors_invalid)
{
static const std::string CASES[] = {
" 1xj0phk",
"\x7f""1g6xzxy",
"\x80""1vctc34",
"an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4",
"qyrz8wqd2c9m",
"1qyrz8wqd2c9m",
"y1b0jsk6g",
"lt1igcx5c0",
"in1muywd",
"mm1crxm3i",
"au1s5cgom",
"M1VUXWEZ",
"16plkw9",
"1p2gdwpf"
};
for (const std::string& str : CASES) {
const auto dec = blech32::Decode(str);
BOOST_CHECK(dec.encoding == blech32::Encoding::INVALID);
}
}
BOOST_AUTO_TEST_CASE(blech32_testvectors_invalid)
{
static const std::string CASES[] = {
" 1nwldj5",
"\x7f""1axkwrx",
"\x80""1eym55h",
"an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx",
"pzry9x0s0muk",
"1pzry9x0s0muk",
"x1b4n0q5v",
"li1dgmt3",
"de1lg7wt\xff",
"A1G7SGD8",
"10a06t8",
"1qzzfhee",
"a12UEL5L",
"A12uEL5L",
};
for (const std::string& str : CASES) {
const auto dec = blech32::Decode(str);
BOOST_CHECK(dec.encoding == blech32::Encoding::INVALID);
}
}
BOOST_AUTO_TEST_CASE(blech32_polymod_sanity)
{
std::vector<unsigned char> data(40);
@ -36,7 +125,7 @@ BOOST_AUTO_TEST_CASE(blech32_checksum)
std::vector<unsigned char> vector{7,2,3,4,5,6,7,8,9,234,123,213,16};
std::vector<unsigned char> b32;
ConvertBits<8, 5, true>([&](unsigned char c) { b32.push_back(c); }, vector.begin(), vector.end());
std::vector<unsigned char> cs = blech32::CreateChecksum("lq", b32);
std::vector<unsigned char> cs = blech32::CreateChecksum(blech32::Encoding::BLECH32, "lq", b32);
std::vector<unsigned char> expected_cs{22,13,13,5,4,4,23,7,28,21,30,12};
for (size_t i = 0; i < expected_cs.size(); i++) {

View file

@ -6,177 +6,207 @@
"x"
],
[
"37qgekLpCCHrQuSjvX3fs496FWTGsHFHizjJAs6NPcR47aefnnCWECAhHV6E3g4YN7u7Yuwod5Y"
"2v7k5Bb8Lr1MMgTgW6HAf5YHXi6BzpPjHpQ4srD4RSwHYpzXKiXmLAgiLhkXvp3JF5v7nq45EWr"
],
[
"dzb7VV1Ui55BARxv7ATxAtCUeJsANKovDGWFVgpTbhq9gvPqP3yv"
"RAZzCGtMbiUgMiiyrZySrSpdfnQReFXA3r"
],
[
"MuNu7ZAEDFiHthiunm7dPjwKqrVNCM3mAz6rP9zFveQu14YA8CxExSJTHcVP9DErn6u84E6Ej7S"
"NYamy7tcPQTzoU5iyQojD3sqhiz7zxkvn8"
],
[
"rPpQpYknyNQ5AEHuY6H8ijJJrYc2nDKKk9jjmKEXsWzyAQcFGpDLU2Zvsmoi8JLR7hAwoy3RQWf"
"geaFG555Ex5nyRf7JjW6Pj2GwZA8KYxtJJLbr1eZhVW75STbYBZeRszy3wg4pkKdF4ez9J4wQiz"
],
[
"4Uc3FmN6NQ6zLBK5QQBXRBUREaaHwCZYsGCueHauuDmJpZKn6jkEskMB2Zi2CNgtb5r6epWEFfUJq"
"2Cxmid3c2XQ2zvQ8SA1ha2TKqvqbJS9XFmXRsCneBS3Po7Qqb65z5zNdsoF9AfieXFcpoVPmkmfa"
],
[
"7aQgR5DFQ25vyXmqZAWmnVCjL3PkBcdVkBUpjrjMTcghHx3E8wb"
"gaJ7UVge2njVg9tFTetJrtHgruMm7aQDiSAxfHrVEgzK8N2ooagDVmDkdph434xzc4K96Gjyxcs"
],
[
"17QpPprjeg69fW1DV8DcYYCKvWjYhXvWkov6MJ1iTTvMFj6weAqW7wybZeH57WTNxXVCRH4veVs"
"5JN5BEVQPZ3tAiatz1RGXkrJuE3EC6bervMaPb38wTNgEuZCeqp"
],
[
"KxuACDviz8Xvpn1xAh9MfopySZNuyajYMZWz16Dv2mHHryznWUp3"
"3TnFbyUtBRS5rE1KTW81qLVspjJNaB3uu6uuvLjxhZo2DB6PCGh"
],
[
"7nK3GSmqdXJQtdohvGfJ7KsSmn3TmGqExug49583bDAL91pVSGq5xS9SHoAYL3Wv3ijKTit65th"
"7UgSZGaMaTc4d2mdEgcGBFiMeS6eMsithGUqvBsKTQdGzD7XQDbMEYo3gojdbXEPbUdFF3CQoK72f"
],
[
"cTivdBmq7bay3RFGEBBuNfMh2P1pDCgRYN2Wbxmgwr4ki3jNUL2va"
"9261wfqQqruNDnBDhbbb4tN9oKA1KpRFHeoYeufyJApVGixyAG4V"
],
[
"gjMV4vjNjyMrna4fsAr8bWxAbwtmMUBXJS3zL4NJt5qjozpbQLmAfK1uA3CquSqsZQMpoD1g2nk"
"cS824CTUh18scFmYuqt6BgxuRhdR4dEEnCHs3fzBbcyQgbfasHbw"
],
[
"emXm1naBMoVzPjbk7xpeTVMFy4oDEe25UmoyGgKEB1gGWsK8kRGs"
"tc1q0ywf7wkz6t580n3yemd3ucfw8jxn93tpc6wskt"
],
[
"7VThQnNRj1o3Zyvc7XHPRrjDf8j2oivPTeDXnRPYWeYGE4pXeRJDZgf28ppti5hsHWXS2GSobdqyo"
"bt1pxeeuh96wpm5c6u3kavts2qgwlv6y8um7u7ga6ltlwrhrv7w9vers8lgt3k"
],
[
"1G9u6oCVCPh2o8m3t55ACiYvG1y5BHewUkDSdiQarDcYXXhFHYdzMdYfUAhfxn5vNZBwpgUNpso"
"tb130lvl2lyugsk2tf3zhwcjjv39dmwt2tt7ytqaexy8edwcuwks6p5scll5kz"
],
[
"31QQ7ZMLkScDiB4VyZjuptr7AEc9j1SjstF7pRoLhHTGkW4Q2y9XELobQmhhWxeRvqcukGd1XCq"
"bcrt1rhsveeudk"
],
[
"DHqKSnpxa8ZdQyH8keAhvLTrfkyBMQxqngcQA5N8LQ9KVt25kmGN"
"bc10rmfwl8nxdweeyc4sf89t0tn9fv9w6qpyzsnl2r4k48vjqh03qas9asdje0rlr0phru0wqw0p"
],
[
"2LUHcJPbwLCy9GLH1qXmfmAwvadWw4bp4PCpDfduLqV17s6iDcy1imUwhQJhAoNoN1XNmweiJP4i"
"tb1qjqnfsuatr54e957xzg9sqk7yqcry9lns"
],
[
"7USRzBXAnmck8fX9HmW7RAb4qt92VFX6soCnts9s74wxm4gguVhtG5of8fZGbNPJA83irHVY6bCos"
"bcrt1q8p08mv8echkf3es027u4cdswxlylm3th76ls8v6y4zy4vwsavngpr4e4td"
],
[
"1DGezo7BfVebZxAbNT3XGujdeHyNNBF3vnficYoTSp4PfK2QaML9bHzAMxke3wdKdHYWmsMTJVu"
"BC1QNC2H66VLWTWTW52DP0FYUSNU3QQG5VT4V"
],
[
"2D12DqDZKwCxxkzs1ZATJWvgJGhQ4cFi3WrizQ5zLAyhN5HxuAJ1yMYaJp8GuYsTLLxTAz6otCfb"
"tb1qgk665m2auw09rc7pqyf7aulcuhmatz9xqtr5mxew7zuysacaascqs9v0vn"
],
[
"8AFJzuTujXjw1Z6M3fWhQ1ujDW7zsV4ePeVjVo7D1egERqSW9nZ"
"bcrt17CAPP7"
],
[
"163Q17qLbTCue8YY3AvjpUhotuaodLm2uqMhpYirsKjVqnxJRWTEoywMVY3NbBAHuhAJ2cF9GAZ"
"bc1qxmf2d6aerjzam3rur0zufqxqnyqfts5u302s7x"
],
[
"2MnmgiRH4eGLyLc9eAqStzk7dFgBjFtUCtu"
"tb1qn8x5dnzpexq7nnvrvnhwr9c3wkakpcyu9wwsjzq9pstkwg0t6qhs4l3rv6"
],
[
"461QQ2sYWxU7H2PV4oBwJGNch8XVTYYbZxU"
"BCRT1Q397G2RNVYRL5LK07CE8NCKHVKP8Z4SC9U0MVH9"
],
[
"2UCtv53VttmQYkVU4VMtXB31REvQg4ABzs41AEKZ8UcB7DAfVzdkV9JDErwGwyj5AUHLkmgZeobs"
"bc1pgxwyajq0gdn389f69uwn2fw9q0z5c9s063j5dgkdd23ajaud4hpsercr9h"
],
[
"cSNjAsnhgtiFMi6MtfvgscMB2Cbhn2v1FUYfviJ1CdjfidvmeW6mn"
"tb1z6mnmp5k542l6yk4ul0mp4rq3yvz44lfm"
],
[
"gmsow2Y6EWAFDFE1CE4Hd3Tpu2BvfmBfG1SXsuRARbnt1WjkZnFh1qGTiptWWbjsq2Q6qvpgJVj"
"bcrt17capp7"
],
[
"nksUKSkzS76v8EsSgozXGMoQFiCoCHzCVajFKAXqzK5on9ZJYVHMD5CKwgmX3S3c7M1U3xabUny"
"2D2bqvKseKHdoKjCNvjVULUgmxHu9hjKGwDbPRjTRH59tsHNLeyKwq3vyVBbo9LByY9wiapqjwFY"
],
[
"L3favK1UzFGgdzYBF2oBT5tbayCo4vtVBLJhg2iYuMeePxWG8SQc"
"2SSjAim4wZpeQRe5zTj1qqS6Li9ttJDaZ3ze"
],
[
"7VxLxGGtYT6N99GdEfi6xz56xdQ8nP2dG1CavuXx7Rf2PrvNMTBNevjkfgs9JmkcGm6EXpj8ipyPZ"
"mi9H6MjLwXxy9kxe1x4ToxyLRBsmcZxgVi"
],
[
"2mbZwFXF6cxShaCo2czTRB62WTx9LxhTtpP"
"VciXoxEitcn88jy197J9n9cpJ1pZahzU3SyWUiHqLgcfjttLEEJz"
],
[
"dB7cwYdcPSgiyAwKWL3JwCVwSk6epU2txw"
"KppmwADGoExPT9Eq5hjRWpWFDbzJyfzHFgsfxBiDHNpVBgWPRNuy"
],
[
"HPhFUhUAh8ZQQisH8QQWafAxtQYju3SFTX"
"TN7EQXMxKffzvHo54yHHu9R4ks9f5gWBW3MMVf5k72zAqrgVK9ys"
],
[
"4ctAH6AkHzq5ioiM1m9T3E2hiYEev5mTsB"
"92dbrMEYzP5dD5UhQ6maNkCQ4GLG42BM4Gc6XKZzSSMSfosfkkcB"
],
[
"Hn1uFi4dNexWrqARpjMqgT6cX1UsNPuV3cHdGg9ExyXw8HTKadbktRDtdeVmY3M1BxJStiL4vjJ"
"J7VQxPxyzuWEkRstQWpCz2AgysEz1APgnWCEQrFvkN3umAnCrhQF"
],
[
"Sq3fDbvutABmnAHHExJDgPLQn44KnNC7UsXuT7KZecpaYDMU9Txs"
"tc1qymllj6c96v5qj2504y27ldtner6eh8ldx38t83"
],
[
"6TqWyrqdgUEYDQU1aChMuFMMEimHX44qHFzCUgGfqxGgZNMUVWJ"
"bt1flep4g"
],
[
"giqJo7oWqFxNKWyrgcBxAVHXnjJ1t6cGoEffce5Y1y7u649Noj5wJ4mmiUAKEVVrYAGg2KPB3Y4"
"tb13c553hwygcgj48qwmr9f8q0hgdcfklyaye5sxzcpcjnmxv4z506xs90tchn"
],
[
"cNzHY5e8vcmM3QVJUcjCyiKMYfeYvyueq5qCMV3kqcySoLyGLYUK"
"bcrt1tyddyu"
],
[
"37uTe568EYc9WLoHEd9jXEvUiWbq5LFLscNyqvAzLU5vBArUJA6eydkLmnMwJDjkL5kXc2VK7ig"
"bc10qssq2mknjqf0glwe2f3587wc4jysvs3f8s6chysae6hcl6fxzdm4wxyyscrl5k9f5qmnf05a"
],
[
"EsYbG4tWWWY45G31nox838qNdzksbPySWc"
"tb1q425lmgvxdgtyl2m6xuu2pc354y4fvgg8"
],
[
"nbuzhfwMoNzA3PaFnyLcRxE9bTJPDkjZ6Rf6Y6o2ckXZfzZzXBT"
"bcrt1q9wp8e5d2u3u4g0pll0cy7smeeuqezdun9xl439n3p2gg4fvgfvk3hu52hj"
],
[
"cQN9PoxZeCWK1x56xnz6QYAsvR11XAce3Ehp3gMUdfSQ53Y2mPzx"
"bc1qrz5acazpue8vl4zsaxn8fxtmeuqmyjkq3"
],
[
"1Gm3N3rkef6iMbx4voBzaxtXcmmiMTqZPhcuAepRzYUJQW4qRpEnHvMojzof42hjFRf8PE2jPde"
"tb1qkeuglpgmnex9tv3fr7htzfrh3rwrk23r52rx9halxzmv9fr85lwq0fwhmp"
],
[
"2TAq2tuN6x6m233bpT7yqdYQPELdTDJn1eU"
"bcrt1qd0t2wrhl7s57z99rsyaekpq0dyjcQRSSmz80r4"
],
[
"ntEtnnGhqPii4joABvBtSEJG6BxjT2tUZqE8PcVYgk3RHpgxgHDCQxNbLJf7ardf1dDk2oCQ7Cf"
"BC1QXLFDUCGX90T3E53PQCNKJ2PK25MSF3VLPMVY6T"
],
[
"Ky1YjoZNgQ196HJV3HpdkecfhRBmRZdMJk89Hi5KGfpfPwS2bUbfd"
"tb1qmycg4zszgnk34vaurx3cu8wpvteg9h40yq6cp52gt26gjel03t3su3x3xu"
],
[
"2A1q1YsMZowabbvta7kTy2Fd6qN4r5ZCeG3qLpvZBMzCixMUdkN2Y4dHB1wPsZAeVXUGD83MfRED"
"bcrt1q9hy58r4fnuxqzdqndpmq9pptc9nt2dw3rczf5e"
],
[
"tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty"
"BC1PA7682NAY6JQSLUWAJYTC0ERWTMW7A4RPWLNTUS32LCXWLHVKKKTQ2UL8CG"
],
[
"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5"
"tb1z850dpxnwz2fzae5h2myatj4yvu6rq5xq"
],
[
"BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2"
"bcrt1sp525pzjsmpqvcrawjreww36e9keg876skjvpwt"
],
[
"bc1rw5uspcuh"
"xcAvW5jurCpzSpLxBKEhCewCgwwuGhqJnC"
],
[
"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90"
"2Cvv8yp9kXbQt8EKh6Yma95yJ1uwYF9YKXuVhGJyu3dHGVsb2AVpTC62TFACZZ3KDNrALxR2CVNs"
],
[
"BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P"
"niUuL46hCuEVvkAzZKHvD746qbmLmzip9Pv3F6UZV14JxzEXBnTkVxCT4URapChJG6qAEgsZs6G"
],
[
"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7"
"2UHHgGfiipzvB8Eumnmvq6SowvrMJimjT3NwwG1839XEiUfwtpSdkUrseNsQuagXv21ce7aZu6yo"
],
[
"bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du"
"8u9djKu4u6o3bsgeR4BKNnLK3akpo64FYzDAmA9239wKeshgF97"
],
[
"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv"
"TC1QPAARXSLVMXHVRR0474LZXQYZWLGFZYPSFVL9E4"
],
[
"bc1gmk9yu"
"bt1pakek0n2267t9yaksxaczgr2syhv9y3xkx0wnsdwchfa6xkmjtvuqg3kgyr"
],
[
"tb13h83rtwq62udrhwpn87uely7cyxcjrj0azz6a4r3n9s87x5uj98ys6ufp83"
],
[
"bcrt1rk5vw5qf2"
],
[
"bc10d3rmtg62h747en5j6fju5g5qyvsransrkty6ghh96pu647wumctejlsngh9pf26cysrys2x2"
],
[
"tb1qajuy2cdwqgmrzc7la85al5cwcq374tsp"
],
[
"bcrt1q3udxvj6x20chqh723mn064mzz65yr56ef00xk8czvu3jnx04ydapzk02s5"
],
[
"bc1qule2szwzyaq4qy0s3aa4mauucyqt6fewe"
],
[
"tb1ql0qny5vg9gh5tyzke6dw36px5ulkrp24x53x0pl2t5lpwrtejw3s2seej2"
],
[
"bcrt17CAPP7"
],
[
"bc1qtvm6davyf725wfedc2d5mrgfewqgcrce8gjrpl"
],
[
"tb1q5acjgtqrrw3an0dzavxxxzlex8k7aukjzjk9v2u4rmfdqxjphcyq7ge97e"
]
]

File diff suppressed because it is too large Load diff

View file

@ -16,28 +16,28 @@
void test_one_input(const std::vector<uint8_t>& buffer)
{
const std::string random_string(buffer.begin(), buffer.end());
const std::pair<std::string, std::vector<uint8_t>> r1 = bech32::Decode(random_string);
if (r1.first.empty()) {
assert(r1.second.empty());
const auto r1 = bech32::Decode(random_string);
if (r1.hrp.empty()) {
assert(r1.encoding == bech32::Encoding::INVALID);
assert(r1.data.empty());
} else {
const std::string& hrp = r1.first;
const std::vector<uint8_t>& data = r1.second;
const std::string reencoded = bech32::Encode(hrp, data);
assert(r1.encoding != bech32::Encoding::INVALID);
const std::string reencoded = bech32::Encode(r1.encoding, r1.hrp, r1.data);
assert(CaseInsensitiveEqual(random_string, reencoded));
}
std::vector<unsigned char> input;
ConvertBits<8, 5, true>([&](unsigned char c) { input.push_back(c); }, buffer.begin(), buffer.end());
const std::string encoded = bech32::Encode("bc", input);
assert(!encoded.empty());
const std::pair<std::string, std::vector<uint8_t>> r2 = bech32::Decode(encoded);
if (r2.first.empty()) {
assert(r2.second.empty());
} else {
const std::string& hrp = r2.first;
const std::vector<uint8_t>& data = r2.second;
assert(hrp == "bc");
assert(data == input);
if (input.size() + 3 + 6 <= 90) {
// If it's possible to encode input in Bech32(m) without exceeding the 90-character limit:
for (auto encoding : {bech32::Encoding::BECH32, bech32::Encoding::BECH32M}) {
const std::string encoded = bech32::Encode(encoding, "bc", input);
assert(!encoded.empty());
const auto r2 = bech32::Decode(encoded);
assert(r2.encoding == encoding);
assert(r2.hrp == "bc");
assert(r2.data == input);
}
}
}

View file

@ -4222,13 +4222,19 @@ RPCHelpMan getaddressinfo()
LOCK(pwallet->cs_wallet);
UniValue ret(UniValue::VOBJ);
CTxDestination dest = DecodeDestination(request.params[0].get_str());
std::string error_msg;
CTxDestination dest = DecodeDestination(request.params[0].get_str(), error_msg);
// Make sure the destination is valid
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
// Set generic error message in case 'DecodeDestination' didn't set it
if (error_msg.empty()) error_msg = "Invalid address";
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error_msg);
}
UniValue ret(UniValue::VOBJ);
std::string currentAddress = EncodeDestination(dest);
ret.pushKV("address", currentAddress);
@ -5492,9 +5498,10 @@ static RPCHelpMan sendtomainchain_base()
EnsureWalletIsUnlocked(pwallet);
CTxDestination parent_address = DecodeParentDestination(request.params[0].get_str());
std::string error_str;
CTxDestination parent_address = DecodeParentDestination(request.params[0].get_str(), error_str);
if (!IsValidDestination(parent_address))
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid Bitcoin address: %s", error_str));
CAmount nAmount = AmountFromValue(request.params[1]);
if (nAmount <= 0)

View file

@ -0,0 +1,130 @@
#!/usr/bin/env python3
# Copyright (c) 2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test error messages for 'getaddressinfo' and 'validateaddress' RPC commands."""
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
BECH32_VALID = 'ert1qtmp74ayg7p24uslctssvjm06q5phz4yr7gdkdv'
BECH32_INVALID_BECH32 = 'ert1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqugsf3u'
BECH32_INVALID_BECH32M = 'ert1qw508d6qejxtdg4y5r3zarvary0c5xw7kfqwaud'
BECH32_INVALID_VERSION = 'ert130xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq4q68pj'
BECH32_INVALID_SIZE = 'ert1s0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav25pltc58'
BECH32_INVALID_V0_SIZE = 'ert1qw508d6qejxtdg4y5r3zarvary0c5xw7kqq2287l0'
BECH32_INVALID_PREFIX = 'bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx'
BASE58_VALID = '2dcjQH4DQC3pMcSQkMkSQyPPEr7rZ6Ga4GR'
BASE58_INVALID_PREFIX = '17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem'
INVALID_ADDRESS = 'asfah14i8fajz0123f'
## ELEMENTS
BLECH32_VALID = 'el1qq0umk3pez693jrrlxz9ndlkuwne93gdu9g83mhhzuyf46e3mdzfpva0w48gqgzgrklncnm0k5zeyw8my2ypfsmxh4xcjh2rse'
BLECH32_INVALID_BLECH32 = 'el1pq0umk3pez693jrrlxz9ndlkuwne93gdu9g83mhhzuyf46e3mdzfpva0w48gqgzgrklncnm0k5zeyw8my2ypfsxguu9nrdg2pc'
BLECH32_INVALID_BLECH32M = 'el1qq0umk3pez693jrrlxz9ndlkuwne93gdu9g83mhhzuyf46e3mdzfpva0w48gqgzgrklncnm0k5zeyw8my2ypfsnnmzrstzt7de'
BLECH32_INVALID_VERSION = 'ert130xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq4q68pj'
BLECH32_INVALID_SIZE = 'el1pq0umk3pez693jrrlxz9ndlkuwne93gdu9g83mhhzuyf46e3mdzfpva0w48gqgzgrklncnm0k5zeyw8my2ypfsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpe9jfn0gypaj'
BLECH32_INVALID_V0_SIZE = 'ert1qw508d6qejxtdg4y5r3zarvary0c5xw7kqq2287l0'
BLECH32_INVALID_PREFIX = 'lq1qq0umk3pez693jrrlxz9ndlkuwne93gdu9g83mhhzuyf46e3mdzfpva0w48gqgzgrklncnm0k5zeyw8my2ypfscmm3q74jvv3r'
class InvalidAddressErrorMessageTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def test_validateaddress(self):
node = self.nodes[0]
# Bech32
info = node.validateaddress(BECH32_INVALID_SIZE)
assert not info['isvalid']
assert_equal(info['error'], 'Invalid Bech32 address data size')
info = node.validateaddress(BECH32_INVALID_PREFIX)
assert not info['isvalid']
assert_equal(info['error'], 'Invalid prefix for Bech32 address')
info = node.validateaddress(BECH32_INVALID_BECH32)
assert not info['isvalid']
assert_equal(info['error'], 'Version 1+ witness address must use Bech32m checksum')
info = node.validateaddress(BECH32_INVALID_BECH32M)
assert not info['isvalid']
assert_equal(info['error'], 'Version 0 witness address must use Bech32 checksum')
info = node.validateaddress(BECH32_INVALID_V0_SIZE)
assert not info['isvalid']
assert_equal(info['error'], 'Invalid Bech32 v0 address data size')
info = node.validateaddress(BECH32_VALID)
assert info['isvalid']
assert 'error' not in info
# Base58
info = node.validateaddress(BASE58_INVALID_PREFIX)
assert not info['isvalid']
assert_equal(info['error'], 'Invalid prefix for Base58-encoded address')
info = node.validateaddress(BASE58_VALID)
assert info['isvalid']
assert 'error' not in info
# Invalid address format
info = node.validateaddress(INVALID_ADDRESS)
assert not info['isvalid']
assert_equal(info['error'], 'Invalid address format')
## ELEMENTS
info = node.validateaddress(BLECH32_INVALID_SIZE)
assert not info['isvalid']
assert_equal(info['error'], 'Invalid Blech32 address data size')
info = node.validateaddress(BLECH32_INVALID_PREFIX)
assert not info['isvalid']
assert_equal(info['error'], 'Invalid prefix for Blech32 address')
info = node.validateaddress(BLECH32_INVALID_BLECH32)
assert not info['isvalid']
assert_equal(info['error'], 'Version 1+ witness address must use Blech32m checksum')
info = node.validateaddress(BLECH32_INVALID_BLECH32M)
assert not info['isvalid']
assert_equal(info['error'], 'Version 0 witness address must use Blech32 checksum')
info = node.validateaddress(BLECH32_VALID)
assert info['isvalid']
assert 'error' not in info
def test_getaddressinfo(self):
node = self.nodes[0]
assert_raises_rpc_error(-5, "Invalid Bech32 address data size", node.getaddressinfo, BECH32_INVALID_SIZE)
assert_raises_rpc_error(-5, "Invalid prefix for Bech32 address", node.getaddressinfo, BECH32_INVALID_PREFIX)
assert_raises_rpc_error(-5, "Invalid prefix for Base58-encoded address", node.getaddressinfo, BASE58_INVALID_PREFIX)
assert_raises_rpc_error(-5, "Invalid address format", node.getaddressinfo, INVALID_ADDRESS)
## ELEMENTS
assert_raises_rpc_error(-5, "Invalid Blech32 address data size", node.getaddressinfo, BLECH32_INVALID_SIZE)
assert_raises_rpc_error(-5, "Invalid prefix for Blech32 address", node.getaddressinfo, BLECH32_INVALID_PREFIX)
def run_test(self):
self.test_validateaddress()
self.test_getaddressinfo()
if __name__ == '__main__':
InvalidAddressErrorMessageTest().main()

View file

@ -2,10 +2,18 @@
# 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"
BECH32_CONST = 1
BECH32M_CONST = 0x2bc830a3
class Encoding(Enum):
"""Enumeration type to list the various supported encodings."""
BECH32 = 1
BECH32M = 2
def bech32_polymod(values):
@ -27,38 +35,45 @@ 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 == BECH32_CONST:
return Encoding.BECH32
elif check == BECH32M_CONST:
return Encoding.BECH32M
else:
return None
def bech32_create_checksum(hrp, data):
def bech32_create_checksum(encoding, hrp, data):
"""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 encoding == Encoding.BECH32M else BECH32_CONST
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(encoding, hrp, data):
"""Compute a Bech32 or Bech32m string given HRP and data values."""
combined = data + bech32_create_checksum(encoding, hrp, data)
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)
return (None, None, None)
bech = bech.lower()
pos = bech.rfind('1')
if pos < 1 or pos + 7 > len(bech) or len(bech) > 90:
return (None, None)
return (None, None, None)
if not all(x in CHARSET for x in bech[pos+1:]):
return (None, None)
return (None, 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])
encoding = bech32_verify_checksum(hrp, data)
if encoding is None:
return (None, None, None)
return (encoding, hrp, data[:-6])
def convertbits(data, frombits, tobits, pad=True):
@ -86,7 +101,7 @@ def convertbits(data, frombits, tobits, pad=True):
def decode_segwit_address(hrp, addr):
"""Decode a segwit address."""
hrpgot, data = bech32_decode(addr)
encoding, hrpgot, data = bech32_decode(addr)
if hrpgot != hrp:
return (None, None)
decoded = convertbits(data[1:], 5, 8, False)
@ -96,12 +111,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 encoding != Encoding.BECH32) or (data[0] != 0 and encoding != 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))
encoding = Encoding.BECH32 if witver == 0 else Encoding.BECH32M
ret = bech32_encode(encoding, hrp, [witver] + convertbits(witprog, 8, 5))
if decode_segwit_address(hrp, ret) == (None, None):
return None
return ret
@ -119,3 +137,5 @@ class TestFrameworkScript(unittest.TestCase):
# P2WSH
test_python_bech32('bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj')
test_python_bech32('bcrt1qft5p2uhsdcdc3l2ua4ap5qqfg4pjaqlp250x7us7a8qqhrxrxfsqseac85')
# P2TR
test_python_bech32('bcrt1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqc8gma6')

View file

@ -159,6 +159,7 @@ BASE_SCRIPTS = [
'wallet_keypool_topup.py',
'wallet_keypool_topup.py --descriptors',
'interface_zmq.py',
'rpc_invalid_address_message.py',
'interface_bitcoin_cli.py',
'mempool_resurrect.py',
'wallet_txn_doublespend.py --mineblock',

View file

@ -567,7 +567,7 @@ class WalletTest(BitcoinTestFramework):
assert_equal(total_txs, len(self.nodes[0].listtransactions("*", 99999)))
# Test getaddressinfo on external address. Note that these addresses are taken from disablewallet.py
assert_raises_rpc_error(-5, "Invalid address", self.nodes[0].getaddressinfo, "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy")
assert_raises_rpc_error(-5, "Invalid prefix for Base58-encoded address", self.nodes[0].getaddressinfo, "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy")
address_info = self.nodes[0].getaddressinfo("CTEuA2rzWUDTGt6jFwmAtGtWWqtRgP7NwY4qbGGyht8QpgLvpQmbRHtzeF1yTV1rmJ9GiHhJoqnrbUYT")
assert_equal(address_info['address'], "CTEuA2rzWUDTGt6jFwmAtGtWWqtRgP7NwY4qbGGyht8QpgLvpQmbRHtzeF1yTV1rmJ9GiHhJoqnrbUYT")
assert_equal(address_info["scriptPubKey"], "76a914dc389a2145ec3cd4fe37bd6a11653456168cfa2c88ac")

View file

@ -141,13 +141,13 @@ class WalletLabelsTest(BitcoinTestFramework):
node.createwallet(wallet_name='watch_only', disable_private_keys=True)
wallet_watch_only = node.get_wallet_rpc('watch_only')
BECH32_VALID = {
'_VER15_PROG40': 'ert10qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqygadtm',
'_VER16_PROG03': 'ert1sqqqqq78f8sy',
'_VER16_PROB02': 'ert1sqqqq8l77hs',
'_VER15_PROG40': 'ert10qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq35dpwe',
'_VER16_PROG03': 'ert1sqqqqqtmet4x',
'_VER16_PROB02': 'ert1sqqqqjrwjjj',
}
BECH32_INVALID = {
'❌_VER15_PROG41': 'ert10qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqkk69my',
'❌_VER16_PROB01': 'ert1sqqpx5djq',
'❌_VER15_PROG41': 'ert1sqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfum9dr',
'❌_VER16_PROB01': 'ert1sqq56yphz',
}
for l in BECH32_VALID:
ad = BECH32_VALID[l]

View file

@ -79,6 +79,7 @@ implicit-unsigned-integer-truncation:leveldb/*
implicit-integer-sign-change:crc32c/*
implicit-signed-integer-truncation:script/interpreter.cpp
implicit-integer-sign-change:blech32.cpp
implicit-integer-sign-change:primitives/block.h
implicit-integer-sign-change:primitives/confidential.cpp
implicit-integer-sign-change:primitives/confidential.h