diff --git a/.github/workflows/doxygen-gh-pages.yml b/.github/workflows/doxygen-gh-pages.yml new file mode 100644 index 0000000..6a2600a --- /dev/null +++ b/.github/workflows/doxygen-gh-pages.yml @@ -0,0 +1,31 @@ +name: 'Deploy Doxygen docs to GitHub Pages' + +on: + push: + branches: + - master + +permissions: + contents: write + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v3 + + - name: Install Doxygen + run: sudo apt-get install doxygen graphviz -y + + - name: Generate documentation + run: doxygen + + - name: Create .nojekyll + run: touch html/.nojekyll + + - name: Deploy to GitHub Pages + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: html + single-commit: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0aafbbb --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +*~ +*.log +*.o +/doxygen-doc/ +/bech32 +/libbech32.pc +/test + +# http://www.gnu.org/software/automake + +*.trs +Makefile +Makefile.in +.deps/ +.dirstamp + +# http://www.gnu.org/software/autoconf + +/aclocal.m4 +/autom4te.cache/ +/build-aux/ +/config.cache +/config.h +/config.h.in +/config.log +/config.status +/configure +/stamp-h1 + +# https://www.gnu.org/software/libtool/ + +*.la +*.lo +.libs/ +/libtool diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..e0806af --- /dev/null +++ b/Doxyfile @@ -0,0 +1,23 @@ +PROJECT_NAME = libbech32 +PROJECT_BRIEF = "Library for Bech32/Bech32m encoding and decoding" +OUTPUT_DIRECTORY = $(DOCDIR) +OPTIMIZE_OUTPUT_FOR_C = YES +INLINE_SIMPLE_STRUCTS = YES +EXTRACT_STATIC = YES +SHOW_INCLUDE_FILES = NO +SORT_BRIEF_DOCS = YES +WARN_NO_PARAMDOC = YES +FILE_PATTERNS = *.h \ + *.dox +VERBATIM_HEADERS = NO +GENERATE_HTML = $(GENERATE_HTML) +GENERATE_HTMLHELP = $(GENERATE_HTMLHELP) +GENERATE_CHI = $(GENERATE_CHI) +GENERATE_LATEX = $(GENERATE_LATEX) +GENERATE_RTF = $(GENERATE_RTF) +GENERATE_MAN = $(GENERATE_MAN) +GENERATE_XML = $(GENERATE_XML) +MAN_LINKS = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES +PREDEFINED = __attribute__(x)= diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cb33446 --- /dev/null +++ b/LICENSE @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + 22 rue de Plaisance, 75014 Paris, France + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..0a21821 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,70 @@ +ACLOCAL_AMFLAGS = -I build-aux/m4 + +AM_CPPFLAGS = +if NDEBUG +AM_CPPFLAGS += -DNDEBUG +endif +COMMON_CFLAGS := -Wall -Wextra -Wcast-qual -Wconversion -Wdisabled-optimization -Wdouble-promotion -Wno-implicit-fallthrough -Wmissing-declarations -Wno-missing-field-initializers -Wpacked -Wno-parentheses -Wredundant-decls -Wno-sign-conversion $(addprefix -Wsuggest-attribute=,pure const noreturn malloc) -Wno-vla +AM_CFLAGS = $(COMMON_CFLAGS) $(addprefix -Werror=,implicit-function-declaration incompatible-pointer-types int-conversion) +AM_CXXFLAGS = $(COMMON_CFLAGS) -Wnoexcept -Wold-style-cast -Wsign-promo -Wsuggest-override -Wno-terminate -Wzero-as-null-pointer-constant + +include_HEADERS = bech32.h + +pkgconfig_DATA = libbech32.pc +EXTRA_DIST = $(pkgconfig_DATA) + +dist_man_MANS = bech32.1 + +lib_LTLIBRARIES = libbech32.la +libbech32_la_SOURCES = libbech32.c +if BUILD_CXX +libbech32_la_SOURCES += libbech32_c++.cpp +libbech32_la_LINK = $(CXXLINK) $(libbech32_la_CXXFLAGS) $(libbech32_la_LDFLAGS) +else +libbech32_la_LINK = $(LINK) $(libbech32_la_CFLAGS) $(libbech32_la_LDFLAGS) +endif +# How to update version-info: +# - oldprog+newlib and newprog+oldlib are both okay => +0:+1:+0 +# - oldprog+newlib is okay, but newprog+oldlib won't work => +1:=0:+1 +# - oldprog+newlib won't work => +1:=0:=0 +libbech32_la_LDFLAGS = -no-undefined -version-info 0:0:0 + +bin_PROGRAMS = bech32 +bech32_SOURCES = bech32.c +bech32_LDADD = libbech32.la + +if BUILD_TESTS + +check_PROGRAMS = test +test_SOURCES = test.cpp +test_CPPFLAGS = $(filter-out -DNDEBUG,$(AM_CPPFLAGS)) +test_LDFLAGS = -no-install +test_LDADD = libbech32.la + +TESTS = $(check_PROGRAMS) +noinst_PROGRAMS = $(check_PROGRAMS) + +else + +check-local: + @! echo "You didn't enable the tests! Run './configure --enable-tests'." >&2 + +endif # BUILD_TESTS + +@DX_RULES@ +MOSTLYCLEANFILES = $(DX_CLEANFILES) + +if BUILD_MANPAGES +man3_MANS = $(addprefix doxygen-doc/man/man3/,$(addsuffix .3,bech32.h \ + $(shell $(SED) -Ene 's/^((\w|\*)+\s+)+(\w+)\(.*$$/\3/p#)' $(srcdir)/bech32.h))) +$(man3_MANS) : doxygen-doc +$(DX_DOCDIR)/$(PACKAGE).tag : $(include_HEADERS) +endif + +install-exec-hook: + cd $(DESTDIR)$(bindir) && { test -e bech32m$(EXEEXT) || $(LN_S) -n bech32$(EXEEXT) bech32m$(EXEEXT) ; } + cd $(DESTDIR)$(man1dir) && { test -e bech32m.1 || $(LN_S) -n bech32.1 bech32m.1 ; } + +uninstall-hook: + rm -f $(DESTDIR)$(bindir)/bech32m$(EXEEXT) + rm -f $(DESTDIR)$(man1dir)/bech32m.1 diff --git a/README.md b/README.md new file mode 100644 index 0000000..33fb0a7 --- /dev/null +++ b/README.md @@ -0,0 +1,347 @@ +# libbech32 +**Library for Bech32/Bech32m encoding and decoding** + +Libbech32 is a C library for encoding and decoding SegWit addresses and arbitrary bit strings in Bech32/Bech32m format. It offers three interfaces: a **low-level API** for working with arbitrary bit strings, a **high-level API** for working with SegWit Bitcoin addresses specifically, and a **command-line utility** for Bech32/Bech32m encoding/decoding from `stdin` to `stdout`. C++ wrappers are optionally provided for both APIs. + +This document gives a general overview. Please refer to the auto-generated [Doxygen documentation](https://whitslack.github.io/libbech32/bech32_8h.html) for specific details. + +## Low-level API + +The low-level API allows encoding/decoding arbitrary bit strings to/from Bech32/Bech32m format. Bits are supplied to the encoder, or extracted from the decoder, through function calls that pass a buffer and a bit length. In the case that the bit length is not evenly divisible by 8, the bits of the last byte in the buffer are aligned to the least significant bit. Successive calls pack/unpack bit strings in the encoding without introducing any padding between segments. + +### Encoding + +To encode a bit string in Bech32/Bech32m, allocate a `struct bech32_encoder_state` and initialize it by passing to `bech32_encode_begin()` a pointer to it along with a pointer to the output buffer that is to receive the encoding and a pointer to the human-readable prefix to use in the encoding: + +```c +enum bech32_error error; +struct bech32_encoder_state state; +char output[BECH32_MAX_SIZE]; +static const char hrp[] = "bc"; + +if ((error = bech32_encode_begin(&state, output, sizeof output, hrp, strlen(hrp))) < 0) { + abort(); // TODO handle error +} +``` + +Next, supply any number of bit strings to the encoder by repeatedly calling `bech32_encode_data()`, passing a pointer to the bits to encode and the number of bits to encode: + +```c +const uint8_t version = 16; +if ((error = bech32_encode_data(&state, &version, 5)) < 0) { + abort(); // TODO handle error +} + +static const unsigned char program[] = { 0x75, 0x1e }; +if ((error = bech32_encode_data(&state, program, sizeof program * CHAR_BIT)) < 0) { + abort(); // TODO handle error +} +``` + +Finally, call `bech32_encode_finish()` to flush the encoder and append the checksum to the encoding, passing the constant to use for the checksum calculation, either 1 for Bech32 or `BECH32M_CONST` for Bech32m: + +```c +if ((error = bech32_encode_finish(&state, BECH32M_CONST)) < 0) { + abort(); // TODO handle error +} +assert(strcmp(output, "bc1sw50qgdz25j") == 0); +``` + +### Decoding + +To decode a bit string from Bech32/Bech32m, allocate a `struct bech32_decoder_state` and initialize it by passing to `bech32_decode_begin()` a pointer to it along with a pointer to the input buffer containing the encoding to be decoded: + +```c +ssize_t n; +struct bech32_decoder_state state; +static const char input[] = "bc1sw50qgdz25j"; + +if ((n = bech32_decode_begin(&state, input, strlen(input))) < 0) { + abort(); // TODO handle error +} +assert(n == 2); // returns size of human-readable prefix at input +``` + +Next, extract any number of bit strings from the decoder by repeatedly calling `bech32_decode_data()`, passing a pointer to an output buffer to receive the decoded bits and the number of bits to decode: + +```c +enum bech32_error error; + +uint8_t version; +if ((error = bech32_decode_data(&state, &version, 5)) < 0) { + abort(); // TODO handle error +} +assert(version == 16); + +unsigned char program[2]; +if ((error = bech32_decode_data(&state, program, sizeof program * CHAR_BIT)) < 0) { + abort(); // TODO handle error +} +static const unsigned char expected[] = { 0x75, 0x1e }; +assert(memcmp(program, expected, sizeof program) == 0); +``` + +Finally, call `bech32_decode_finish()` to check any padding bits and verify the checksum, passing the constant to use for the checksum verification, either 1 for Bech32 or `BECH32M_CONST` for Bech32m: + +```c +if ((n = bech32_decode_finish(&state, BECH32M_CONST)) < 0) { + abort(); // TODO handle error +} +assert((5 - n) % 5 == (5 + sizeof program * CHAR_BIT) % 5); // returns number of padding bits +``` + +### C++ example + +```cpp +#include + +#include +#include +#include +#include + +static void example_encode() { + bech32::Encoder enc("bc"); + + const uint8_t version = 16; + enc.write(&version, 5); + + static constexpr std::array program { 0x75, 0x1e }; + enc.write(program.data(), program.size() * CHAR_BIT); + + std::string encoding = enc.finish(BECH32M_CONST); + assert(encoding == "bc1sw50qgdz25j"); +} + +static void example_decode() { + bech32::Decoder dec("bc1sw50qgdz25j"); + assert(dec.prefix() == "bc"); + + uint8_t version; + dec.read(&version, 5); + assert(version == 16); + + std::array program; + dec.read(program.data(), program.size() * CHAR_BIT); + assert((program == std::array { 0x75, 0x1e })); + + size_t n = dec.finish(BECH32M_CONST); + assert((5 - n) % 5 == (5 + program.size() * CHAR_BIT) % 5); +} + +int main() { + example_encode(); + example_decode(); + return 0; +} +``` + +## High-level API + +The high-level API allows encoding/decoding a SegWit address with a single function call. + +### Encoding + +To encode a SegWit address, call `segwit_address_encode()`, passing a pointer to an output buffer to receive the null-terminated address, the size of the output buffer, a pointer to a buffer containing the witness program, the size of the witness program (in bytes), a pointer to the human-readable prefix, the size of the human-readable prefix, and the witness version: + +```c +char address[BECH32_MAX_SIZE + 1]; +static const unsigned char program[] = { + 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, + 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6 +}; +static const char hrp[] = "bc"; +const unsigned version = 0; +ssize_t n; +if ((n = segwit_address_encode( + address, sizeof address, + program, sizeof program, + hrp, strlen(hrp), version)) < 0) +{ + abort(); // TODO handle error +} +assert(strcmp(address, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4") == 0); +``` + +### Decoding + +To decode a SegWit address, call `segwit_address_decode()`, passing a pointer to an output buffer to receive the decoded witness program, the size of the output buffer, a pointer to the address to decode, the size of the address, a pointer to a variable to receive the size of the human-readable prefix, and a pointer to a variable to receive the witness version: + +```c +unsigned char program[WITNESS_PROGRAM_MAX_SIZE]; +static const char address[] = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"; +size_t n_hrp; +unsigned version; +ssize_t n; +if ((n = segwit_address_decode( + program, sizeof program, + address, strlen(address), + &n_hrp, &version)) < 0) +{ + abort(); // TODO handle error +} +assert(n_hrp == 2); +assert(version == 0); +static const unsigned char expected[] = { + 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, + 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6 +}; +assert(n == sizeof expected && memcmp(program, expected, n) == 0); +``` + +### C++ example + +```cpp +#include + +#include +#include +#include +#include +#include +#include +#include + +static void example_encode() { + static constexpr std::array program { + 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, + 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6 + }; + std::string address = bech32::encode_segwit_address(program.data(), program.size(), "bc", 0); + assert(address == "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"); +} + +static void example_decode() { + static constexpr std::string_view address = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"; + static constexpr std::array expected { + 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, + 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6 + }; + auto [program, hrp, version] = bech32::decode_segwit_address(address); + assert(std::ranges::equal(program, as_bytes(std::span(expected)))); + assert(hrp == "bc"); + assert(version == 0); +} + +int main() { + example_encode(); + example_decode(); + return 0; +} +``` + +## Command-line utility + +The library comes with a command-line utility for encoding/decoding Bech32/Bech32m. It supports only data payloads a whole number of bytes in size, optionally prefixed by a 5-bit version field such as in SegWit addresses. + +**Usage:** `bech32` \[`-h`] \[`-m`] *hrp* { \[*version*] | `-d` \[`-v`|*version*] } + +Reads data from `stdin` and writes its Bech32 encoding to `stdout`. +If *version* is given, its least significant 5 bits are encoded as a SegWit version field. + +
+
-d,--decode
+
Decode a Bech32 encoding from stdin and write the decoded data to stdout. +If version is given, assert that it matches the version field in the data.
+ +
-h,--hex
+
Use hexadecimal for data input/output. +If this option is not specified, the data are read/written in raw binary.
+ +
-m,--bech32m
+
Use Bech32m instead of Bech32. +Implied if the command is invoked as bech32m.
+ +
-v,--exit-version
+
Extract a 5-bit SegWit version field and return it as the exit status.
+
+ +### Examples + +Encode a 2-byte, version-16 witness program, given in hexadecimal: +```bash +$ echo 751e | bech32m -h bc 16 +bc1sw50qgdz25j +``` + +Decode a Bech32m encoding to hexadecimal and return its witness version as the exit status: +```bash +$ echo bc1sw50qgdz25j | bech32m -dhv bc ; echo $? +751e +16 +``` + +Encode a P2WPKH SegWit address, given its public key hash in hexadecimal: +```bash +$ echo 751e76e8199196d454941c45d1b3a323f1433bd6 | bech32 -h bc 0 +bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 +``` + +Decode the public key hash from a P2WPKH SegWit address, +and assert that its human-readable prefix is `bc` and its witness version is 0: +```bash +$ echo bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 | bech32 -dh bc 0 +751e76e8199196d454941c45d1b3a323f1433bd6 +``` + +The *hrp* given on the command line is asserted when decoding: +```bash +$ echo tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx | bech32 -dhv bc +bech32: human-readable prefix was "tb", not "bc" +``` + +The *version* given on the command line is asserted when decoding: +```bash +$ echo bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh | bech32m -dh bc 1 +bech32m: version was 0, not 1 +``` + +Encode an empty string in Bech32 format with no version field: +```bash +$ bech32 bc +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static void print_usage() { + fprintf(stderr, "usage: %s [-h] [-m] { [] | -d [-v|] }\n\n" + "Reads data from stdin and writes its Bech32 encoding to stdout. If is\n" + "given, its least significant 5 bits are encoded as a SegWit version field.\n\n" + "-d,--decode\n" + " Decode a Bech32 encoding from stdin and write the data to stdout. If\n" + " is given, assert that it matches the version field in the data.\n" + "-h,--hex\n" + " Use hexadecimal for data input/output.\n" + "-m,--bech32m\n" + " Use Bech32m instead of Bech32. Implied if invoked as 'bech32m'.\n" + "-v,--exit-version\n" + " Extract a 5-bit SegWit version field and return it as the exit status.\n", + program_invocation_short_name); +} + +static int gethex() { + static const int8_t DECODE['f' + 1 - '0'] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15 + }; + int hi, lo; + if ((hi = getchar()) < 0) + return hi; + if ((hi -= '0') >= 0 && hi <= 'f' - '0' && (hi = DECODE[hi]) >= 0) { + if ((lo = getchar()) >= 0 && (lo -= '0') >= 0 && lo <= 'f' - '0' && (lo = DECODE[lo]) >= 0) + return hi << 4 | lo; + } + else if (hi == '\n' - '0') + return EOF; + errx(EX_DATAERR, "invalid hex on stdin"); +} + +static ssize_t fwrite_hex(const unsigned char in[], size_t n_in, FILE *out) { + static const char ENCODE[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + for (size_t i = 0; i < n_in; ++i) { + char x[2] = { ENCODE[in[i] >> 4], ENCODE[in[i] & 0xF] }; + if (fwrite(x, 1, 2, out) < 2) + return -1; + } + return putc('\n', out) < 0 ? -1 : (ssize_t) n_in; +} + +static const char *errmsg(enum bech32_error error) { + switch (error) { + case BECH32_TOO_SHORT: + return "input is too short"; + case BECH32_TOO_LONG: + return "input is too long"; + case BECH32_NO_SEPARATOR: + return "no separator found"; + case BECH32_MIXED_CASE: + return "input uses mixed case"; + case BECH32_ILLEGAL_CHAR: + return "illegal character"; + case BECH32_PADDING_ERROR: + return "padding error"; + case BECH32_CHECKSUM_FAILURE: + return "checksum verification failed"; + case BECH32_HRP_TOO_SHORT: + return "human-readable prefix is empty"; + case BECH32_HRP_TOO_LONG: + return "human-readable prefix is too long"; + case BECH32_HRP_ILLEGAL_CHAR: + return "invalid human-readable prefix"; + case BECH32_BUFFER_INADEQUATE: + case SEGWIT_VERSION_ILLEGAL: + case SEGWIT_PROGRAM_TOO_SHORT: + case SEGWIT_PROGRAM_TOO_LONG: + case SEGWIT_PROGRAM_ILLEGAL_SIZE: + break; + } + __builtin_unreachable(); +} + +int main(int argc, char *argv[]) { + static const struct option longopts[] = { + { .name = "decode", .has_arg = no_argument, .val = 'd' }, + { .name = "hex", .has_arg = no_argument, .val = 'h' }, + { .name = "bech32m", .has_arg = no_argument, .val = 'm' }, + { .name = "exit-version", .has_arg = no_argument, .val = 'v' }, + { .name = "help", .has_arg = no_argument, .val = 1 }, + { .name = "version", .has_arg = no_argument, .val = 2 }, + { } + }; + bool decode = false, hex = false, exit_version = false; + uint32_t constant = strcmp(program_invocation_short_name, "bech32m") ? 1 : BECH32M_CONST; + for (int opt; (opt = getopt_long(argc, argv, "dhmv", longopts, NULL)) >= 0;) { + switch (opt) { + case 1: + print_usage(); + return EX_OK; + case 2: + printf("bech32 %s\n", VERSION); + return EX_OK; + case 'd': + decode = true; + break; + case 'h': + hex = true; + break; + case 'm': + constant = BECH32M_CONST; + break; + case 'v': + exit_version = true; + break; + default: + print_usage(); + return EX_USAGE; + } + } + if ((decode ? argc - optind > 1 + !exit_version : argc - optind > 2 || exit_version) || optind >= argc) + return print_usage(), EX_USAGE; + const char *const hrp = argv[optind++]; + size_t n_hrp = strlen(hrp); + if (n_hrp < BECH32_HRP_MIN_SIZE) + errx(EX_USAGE, errmsg(BECH32_HRP_TOO_SHORT)); + if (n_hrp > BECH32_HRP_MAX_SIZE) + errx(EX_USAGE, errmsg(BECH32_HRP_TOO_LONG)); + int8_t version = optind < argc ? (int8_t) atoi(argv[optind++]) : -1; + + unsigned char in[BECH32_MAX_SIZE]; + size_t n_in = 0, nmax_in = decode ? BECH32_MAX_SIZE : + (BECH32_MAX_SIZE - n_hrp - 1/*separator*/ - (version >= 0) - 6/*checksum*/) * 5 / CHAR_BIT; + if (decode || hex) { + for (int c;;) { + if (decode ? (c = getchar()) < 0 || c == '\n' : (c = gethex()) < 0) { + if (ferror(stdin)) + err(EX_IOERR, "error reading from stdin"); + break; + } + if (n_in == nmax_in) + errx(EX_DATAERR, errmsg(BECH32_TOO_LONG)); + in[n_in++] = (unsigned char) c; + } + } + else { + n_in = fread(in, 1, nmax_in, stdin); + if (ferror(stdin)) + err(EX_IOERR, "error reading from stdin"); + if (!feof(stdin) && getchar() >= 0) + errx(EX_DATAERR, errmsg(BECH32_TOO_LONG)); + } + + unsigned char out[BECH32_MAX_SIZE + 1/*'\n'*/]; + size_t n_out = 0; + if (decode) { + if (n_in < BECH32_MIN_SIZE) + errx(EX_DATAERR, errmsg(BECH32_TOO_SHORT)); + ssize_t ret; + struct bech32_decoder_state state; + if ((ret = bech32_decode_begin(&state, (const char *) in, n_in)) < 0) + errx(EX_DATAERR, errmsg((enum bech32_error) ret)); + if ((size_t) ret != n_hrp || strncasecmp((const char *) in, hrp, ret)) + errx(EX_DATAERR, "human-readable prefix was \"%.*s\", not \"%s\"", (int) ret, in, hrp); + if (version >= 0 || exit_version) { + if (bech32_decode_bits_remaining(&state) < 5) + errx(EX_DATAERR, errmsg(BECH32_TOO_SHORT)); + int8_t expected_version = version; + if ((ret = bech32_decode_data(&state, (unsigned char *) &version, 5)) < 0) + errx(EX_DATAERR, errmsg((enum bech32_error) ret)); + if (expected_version >= 0 && version != expected_version) + errx(EX_DATAERR, "version was %d, not %d", version, expected_version); + } + n_out = bech32_decode_bits_remaining(&state) / CHAR_BIT; + assert(n_out <= sizeof out); + if ((ret = bech32_decode_data(&state, out, n_out * CHAR_BIT)) < 0 || + (ret = bech32_decode_finish(&state, constant)) < 0) + errx(EX_DATAERR, errmsg((enum bech32_error) ret)); + } + else { + n_out = n_hrp + 1/*separator*/ + (version >= 0) + (n_in * CHAR_BIT + 4) / 5 + 6/*checksum*/; + assert(n_out <= sizeof out); + ssize_t ret; + struct bech32_encoder_state state; + if ((ret = bech32_encode_begin(&state, (char *) out, n_out, hrp, n_hrp)) < 0) + errx(EX_DATAERR, errmsg((enum bech32_error) ret)); + if (version >= 0 && (ret = bech32_encode_data(&state, (unsigned char *) &version, 5)) < 0 || + (ret = bech32_encode_data(&state, in, n_in * CHAR_BIT)) < 0 || + (ret = bech32_encode_finish(&state, constant)) < 0) + errx(EX_SOFTWARE, errmsg((enum bech32_error) ret)); + out[n_out++] = '\n'; + } + + if (hex && decode ? + fwrite_hex(out, n_out, stdout) < (ssize_t) n_out : + fwrite(out, 1, n_out, stdout) < n_out) + err(EX_IOERR, "error writing to stdout"); + + return exit_version ? version : EX_OK; +} diff --git a/bech32.h b/bech32.h new file mode 100644 index 0000000..de24bb2 --- /dev/null +++ b/bech32.h @@ -0,0 +1,435 @@ +/// @file + +#include +#include +#include +#include + +#ifdef __cplusplus +# define restrict __restrict +extern "C" { +#endif + +static const uint_least32_t BECH32M_CONST = UINT32_C(0x2bc830a3); + +static const size_t + BECH32_HRP_MIN_SIZE = 1, + BECH32_HRP_MAX_SIZE = 83, + BECH32_MIN_SIZE = BECH32_HRP_MIN_SIZE + 1/*separator*/ + 6/*checksum*/, + BECH32_MAX_SIZE = 90, + WITNESS_PROGRAM_MIN_SIZE = 2, + WITNESS_PROGRAM_MAX_SIZE = 40, + WITNESS_PROGRAM_PKH_SIZE = 20, + WITNESS_PROGRAM_SH_SIZE = 32, + SEGWIT_ADDRESS_MIN_SIZE = BECH32_HRP_MIN_SIZE + 1/*separator*/ + 1/*version*/ + + ((WITNESS_PROGRAM_MIN_SIZE * CHAR_BIT + 4) / 5) + 6/*checksum*/; + +static const unsigned WITNESS_MAX_VERSION = 16; + + +/** + * @brief Possible error codes. + */ +enum bech32_error { + BECH32_TOO_SHORT = -1, + BECH32_TOO_LONG = -2, + BECH32_NO_SEPARATOR = -3, + BECH32_MIXED_CASE = -4, + BECH32_ILLEGAL_CHAR = -5, + BECH32_PADDING_ERROR = -6, + BECH32_CHECKSUM_FAILURE = -7, + BECH32_BUFFER_INADEQUATE = -8, + BECH32_HRP_TOO_SHORT = -9, + BECH32_HRP_TOO_LONG = -10, + BECH32_HRP_ILLEGAL_CHAR = -11, + SEGWIT_VERSION_ILLEGAL = -12, + SEGWIT_PROGRAM_TOO_SHORT = -13, + SEGWIT_PROGRAM_TOO_LONG = -14, + SEGWIT_PROGRAM_ILLEGAL_SIZE = -15, +}; + + +/** + * @brief The state of a Bech32 encoder. + */ +struct bech32_encoder_state { + + /** + * @brief A pointer to the next character that the encoder will produce. + */ + char *restrict out; + + /** + * @brief The number of characters of output buffer space remaining at #out. + */ + size_t n_out; + + /** + * @brief The number of bits that have been consumed by the encoder but that do not yet appear in the output. + */ + size_t nbits; + + /** + * @brief The bits that have been consumed by the encoder but that do not yet appear in the output. + * + * Only the #nbits least significant bits of this field are valid. + */ + uint_fast32_t bits; + + /** + * @brief The intermediate checksum state. + */ + uint_fast32_t chk; + +}; + +/** + * @brief Returns the size of the Bech32 encoding of the specified number of data bits. + * @param n_hrp The size of the human-readable prefix in characters. + * @param nbits_in The number of data bits to be encoded. + * @param n_pad The number of excess bytes to include in the returned size. + * @return The size of the specified Bech32 encoding, or @c SIZE_MAX upon overflow. + */ +size_t bech32_encoded_size( + size_t n_hrp, + size_t nbits_in, + size_t n_pad) + __attribute__ ((__nothrow__, __const__)); + +/** + * @brief Begins a Bech32 encoding. + * @param[out] state A pointer to the encoder state to initialize. + * @param[out] out A pointer to a buffer into which the encoder is to write the encoding. + * Call bech32_encoded_size() to calculate the required size of this buffer. + * @param n_out The size of the buffer at @p out. + * @param[in] hrp A pointer to a character sequence specifying the human-readable prefix of the encoding. + * @param n_hrp The size of the human-readable prefix in characters. + * @return 0 if the parameters were accepted and the state structure was initialized, or a negative number if an error occurred, + * which may be + * @c BECH32_HRP_TOO_SHORT because the human-readable prefix is empty, + * @c BECH32_HRP_TOO_LONG because the human-readable prefix is too long, + * @c BECH32_HRP_ILLEGAL_CHAR because the human-readable prefix contains an illegal character, or + * @c BECH32_BUFFER_INADEQUATE because @p n_out is too small. + */ +enum bech32_error bech32_encode_begin( + struct bech32_encoder_state *restrict state, + char *restrict out, + size_t n_out, + const char *restrict hrp, + size_t n_hrp) + __attribute__ ((__access__ (write_only, 1), __access__ (write_only, 2), __access__ (read_only, 4), __nonnull__, __nothrow__, __warn_unused_result__)); + +/** + * @brief Feeds data to the Bech32 encoder. + * @param[in,out] state A pointer to the encoder state, which must previously have been initialized by a call to + * bech32_encode_begin(). + * @param[in] in A pointer to the data to encode. + * @param nbits_in The number of valid bits in the data at @p in. + * If this is not an integer multiple of @c CHAR_BIT, then the valid bits in the last input byte must be aligned to the least + * significant bit. + * @return 0 if the given data bits were consumed, or a negative number if an error occurred, which may be + * @c BECH32_BUFFER_INADEQUATE because insufficient space remains in the output buffer. + */ +enum bech32_error bech32_encode_data( + struct bech32_encoder_state *restrict state, + const unsigned char *restrict in, + size_t nbits_in) + __attribute__ ((__access__ (read_write, 1), __access__ (read_only, 2), __nonnull__, __nothrow__, __warn_unused_result__)); + +/** + * @brief Finishes a Bech32 encoding. + * @param[in,out] state A pointer to the encoder state, which must previously have been initialized by a call to + * bech32_encode_begin(). + * @param constant The constant to add to the checksum. + * It should be 1 for the original Bech32 specification or @c BECH32M_CONST for Bech32m. + * @return 0 if the encoder successfully finished the encoding, or a negative number if an error occurred, which may be + * @c BECH32_BUFFER_INADEQUATE because insufficient space remains in the output buffer or + * @c BECH32_CHECKSUM_FAILURE because the encoding failed its internal checksum check (due to a software bug or hardware failure). + */ +enum bech32_error bech32_encode_finish( + struct bech32_encoder_state *restrict state, + uint_least32_t constant) + __attribute__ ((__access__ (read_write, 1), __nonnull__, __nothrow__, __warn_unused_result__)); + + + +/** + * @brief The state of a Bech32 decoder. + */ +struct bech32_decoder_state { + + /** + * @brief A pointer to the next character that the decoder will consume. + */ + const char *restrict in; + + /** + * @brief The number of characters of input remaining at #in. + */ + size_t n_in; + + /** + * @brief The number of bits that have been consumed by the decoder but that do not yet appear in the output. + */ + size_t nbits; + + /** + * @brief The bits that have been consumed by the decoder but that do not yet appear in the output. + * + * Only the #nbits least significant bits of this field are valid. + */ + uint_fast32_t bits; + + /** + * @brief The intermediate checksum state. + */ + uint_fast32_t chk; + +}; + +/** + * @brief Begins a Bech32 decoding. + * @param[out] state A pointer to the decoder state to initialize. + * @param[in] in A pointer to the encoding to be decoded. + * @param n_in The size of the encoding at @p in. + * @return The size of the human-readable prefix of the encoding at @p in if the parameters were accepted and the state structure + * was initialized, or a negative number if an error occurred, which may be + * @c BECH32_TOO_SHORT because the encoding is too short, + * @c BECH32_TOO_LONG because the encoding is too long, + * @c BECH32_NO_SEPARATOR because the encoding contains no separator, + * @c BECH32_HRP_TOO_SHORT because the human-readable prefix is empty, + * @c BECH32_HRP_TOO_LONG because the human-readable prefix is too long, + * @c BECH32_HRP_ILLEGAL_CHAR because the human-readable prefix contains an illegal character, + * @c BECH32_ILLEGAL_CHAR because the encoding contains an illegal character, or + * @c BECH32_MIXED_CASE because the encoding uses mixed case. + */ +ssize_t bech32_decode_begin( + struct bech32_decoder_state *restrict state, + const char *restrict in, + size_t n_in) + __attribute__ ((__access__ (write_only, 1), __access__ (read_only, 2), __nonnull__, __nothrow__, __warn_unused_result__)); + +/** + * @brief Returns the number of data bits remaining in the Bech32 encoding, including any padding bits but excluding the checksum. + * @param[in] state A pointer to the decoder state, which must previously have been initialized by a call to bech32_decode_begin(). + */ +static inline size_t +__attribute__ ((__access__ (read_only, 1), __nonnull__, __nothrow__, __pure__)) +bech32_decode_bits_remaining(const struct bech32_decoder_state *restrict state) { + return state->nbits + state->n_in * 5; +} + +/** + * @brief Pulls data from the Bech32 decoder. + * @param[in,out] state A pointer to the decoder state, which must previously have been initialized by a call to + * bech32_decode_begin(). + * @param[out] out A pointer to a buffer into which the decoder is to place the decoded data. + * @param nbits_out The number of data bits to place in the buffer at @p out. + * If this is not an integer multiple of @c CHAR_BIT, then the valid bits in the last output byte will be aligned to the least + * significant bit. + * @return 0 if the requested data bits were produced, or a negative number if an error occurred, which may be + * @c BECH32_BUFFER_INADEQUATE because insufficient characters remain in the input buffer or + * @c BECH32_ILLEGAL_CHAR because the decoder encountered an illegal character in the encoding. + */ +enum bech32_error bech32_decode_data( + struct bech32_decoder_state *restrict state, + unsigned char *restrict out, + size_t nbits_out) + __attribute__ ((__access__ (read_write, 1), __access__ (write_only, 2), __nonnull__, __nothrow__, __warn_unused_result__)); + +/** + * @brief Finishes a Bech32 decoding. + * @param[in,out] state A pointer to the decoder state, which must previously have been initialized by a call to + * bech32_decode_begin(). + * @param constant The constant to add to the checksum. + * It should be 1 for the original Bech32 specification or @c BECH32M_CONST for Bech32m. + * @return The number of unconsumed padding bits remaining at the end of the encoding if the decoder successfully finished the + * decoding and verified the checksum, or a negative number if an error occurred, which may be + * @c BECH32_PADDING_ERROR because of a padding error (more than 5 data bits remain unconsumed or an unconsumed data bit is set), + * @c BECH32_ILLEGAL_CHAR because the decoder encountered an illegal character in the encoding, or + * @c BECH32_CHECKSUM_FAILURE because checksum verification failed. + */ +ssize_t bech32_decode_finish( + struct bech32_decoder_state *restrict state, + uint_least32_t constant) + __attribute__ ((__access__ (read_write, 1), __nonnull__, __nothrow__, __warn_unused_result__)); + + +/** + * @brief Encodes a Segregated Witness program into a Bech32 address. + * @param[out] address A pointer to a buffer into which the address is to be written. + * This function will never write more than 91 characters to this buffer, including the null terminator. + * @param n_address The size of the output buffer at @p address. + * @param[in] program A pointer to the witness program to encode. + * @param n_program The size of the witness program at @p program. + * @param[in] hrp A pointer to a character sequence specifying the human-readable prefix to use. + * Should be "bc" for Bitcoin mainnet or "tb" for Bitcoin testnet. + * @param n_hrp The size of the human-readable prefix, not including any null terminator that may be present but is not required. + * @param version The witness version to use. Must be between 0 and 16. + * Addresses using witness version 0 are encoded using Bech32; all others are encoded using Bech32m. + * @return The size of the address (not including the null terminator) if the encoding was successful, or a negative number if an + * error occurred, which may be + * @c SEGWIT_PROGRAM_TOO_SHORT because the witness program is too short, + * @c SEGWIT_PROGRAM_TOO_LONG because the witness program is too long, + * @c SEGWIT_VERSION_ILLEGAL because the witness version is illegal, + * @c SEGWIT_PROGRAM_ILLEGAL_SIZE because the witness program is of an illegal size for the specified witness version, + * @c BECH32_BUFFER_INADEQUATE because @p n_address is too small, + * @c BECH32_HRP_TOO_SHORT because the human-readable prefix is empty, + * @c BECH32_HRP_TOO_LONG because the human-readable prefix is too long, + * @c BECH32_HRP_ILLEGAL_CHAR because the human-readable prefix contains an illegal character, or + * @c BECH32_CHECKSUM_FAILURE because the encoding failed its internal checksum check (due to a software bug or hardware failure). + */ +ssize_t segwit_address_encode( + char *restrict address, + size_t n_address, + const unsigned char *restrict program, + size_t n_program, + const char *restrict hrp, + size_t n_hrp, + unsigned version) + __attribute__ ((__access__ (write_only, 1), __access__ (read_only, 3), __access__ (read_only, 5), __nonnull__, __nothrow__, __warn_unused_result__)); + +/** + * @brief Decodes a Bech32 address into a Segregated Witness program. + * @param[out] program A pointer to a buffer into which the witness program is to be written. + * This function will never write more than 40 bytes to this buffer. + * @param n_program The size of the output buffer at @p program. + * @param[in] address A pointer to the Bech32 address to decode. + * It may be in either the uppercase or lowercase form. + * @param n_address The size of the address at @p address, not including any null terminator that may be present but is not + * required. + * @param[out] n_hrp A pointer to a variable that is to receive the size of the human-readable prefix in characters. + * @param[out] version A pointer to a variable that is to receive the witness version, which will be between 0 and 16. + * Addresses using witness version 0 are decoded using Bech32; all others are decoded using Bech32m. + * @return The size of the witness program if the decoding was successful, or a negative number if an error occurred, which may be + * @c BECH32_TOO_SHORT because the address is too short, + * @c BECH32_TOO_LONG because the address is too long, + * @c BECH32_NO_SEPARATOR because the address contains no separator, + * @c BECH32_HRP_TOO_SHORT because the human-readable prefix is empty, + * @c BECH32_HRP_TOO_LONG because no separator was found, + * @c BECH32_HRP_ILLEGAL_CHAR because the human-readable prefix contains an illegal character, + * @c BECH32_ILLEGAL_CHAR because the address contains an illegal character, + * @c BECH32_MIXED_CASE because the address uses mixed case, + * @c SEGWIT_PROGRAM_TOO_SHORT because the witness program is too short, + * @c SEGWIT_PROGRAM_TOO_LONG because the witness program is too long, + * @c BECH32_BUFFER_INADEQUATE because @p n_program is too small, + * @c SEGWIT_VERSION_ILLEGAL because the witness version is illegal, + * @c SEGWIT_PROGRAM_ILLEGAL_SIZE because the witness program is of an illegal size for the encoded witness version, + * @c BECH32_PADDING_ERROR because of a padding error, or + * @c BECH32_CHECKSUM_FAILURE because checksum verification failed. + */ +ssize_t segwit_address_decode( + unsigned char *restrict program, + size_t n_program, + const char *restrict address, + size_t n_address, + size_t *restrict n_hrp, + unsigned *restrict version) + __attribute__ ((__access__ (write_only, 1), __access__ (read_only, 3), __access__ (write_only, 5), __access__ (write_only, 6), __nonnull__, __nothrow__, __warn_unused_result__)); + + +#ifdef __cplusplus +} // extern "C" +# undef restrict + + +#include +#include +#include +#include +#include +#include +#include + +namespace bech32 { + + +class Error : public std::runtime_error { + +public: + enum ::bech32_error error; + +public: + explicit Error(enum ::bech32_error error); + +}; + + +class Encoder { + +private: + struct ::bech32_encoder_state state; + std::string out; + +public: +#if __cpp_lib_constexpr_string >= 201907L + constexpr +#endif + Encoder() noexcept : state() { } + + explicit Encoder(std::string_view hrp, size_t nbits_reserve = 0) { + this->reset(hrp, nbits_reserve); + } + +public: + void reset(std::string_view hrp, size_t nbits_reserve = 0); + + void write(const void *in, size_t nbits_in); + + std::string finish(uint_least32_t constant = BECH32M_CONST); + +}; + + +class Decoder { + +private: + struct ::bech32_decoder_state state; + std::string_view hrp; + +public: + constexpr Decoder() noexcept : state() { } + + explicit Decoder(std::string_view in) { + this->reset(in); + } + +public: + std::string_view __attribute__ ((__pure__)) prefix() const noexcept { + return hrp; + } + + size_t __attribute__ ((__pure__)) bits_remaining() const noexcept { + return ::bech32_decode_bits_remaining(&state); + } + + void reset(std::string_view in); + + void read(void *out, size_t nbits_out); + + std::vector read(size_t nbits); + + auto read() { + return this->read(this->bits_remaining() & ~static_cast(CHAR_BIT - 1)); + } + + size_t finish(uint_least32_t constant = BECH32M_CONST); + +}; + + +std::string encode_segwit_address( + const void *program, + size_t n_program, + std::string_view hrp, + unsigned version) + __attribute__ ((__access__ (read_only, 1), __nonnull__, __pure__)); + +std::tuple, std::string_view, unsigned> decode_segwit_address( + std::string_view address) + __attribute__ ((__pure__)); + + +} // namespace bech32 + +#endif // defined(__cplusplus) diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..fc51f72 --- /dev/null +++ b/configure.ac @@ -0,0 +1,49 @@ +m4_define([version_major], [1]) +m4_define([version_minor], [0]) + +AC_INIT([libbech32], m4_join([.], [version_major], [version_minor])) +AC_CONFIG_AUX_DIR([build-aux]) +AC_CONFIG_MACRO_DIRS([build-aux/m4]) +AM_INIT_AUTOMAKE([foreign subdir-objects]) +LT_INIT([disable-static]) + +AC_PROG_CC +AC_PROG_CXX +AC_PROG_LN_S +AC_PROG_SED +PKG_INSTALLDIR + +AC_ARG_ENABLE([assertions], + [AS_HELP_STRING([--enable-assertions], [check assertions at runtime [default=no]])], + [enable_assertions=$enableval], + [enable_assertions=no]) +AM_CONDITIONAL([NDEBUG], [test x"$enable_assertions" = xno]) + +AC_ARG_ENABLE([c++], + [AS_HELP_STRING([--disable-c++], [do not include C++ code in the library])], + [enable_cxx=$enable_val], + [enable_cxx=yes]) +AM_CONDITIONAL([BUILD_CXX], [test x"$enable_cxx" = xyes]) +AM_COND_IF([BUILD_CXX], [ + AX_CXX_COMPILE_STDCXX([20]) +]) + +AC_ARG_ENABLE([tests], + [AS_HELP_STRING([--disable-tests], [do not build unit tests [default=enabled if C++ is enabled]])], + [enable_tests=$enableval], + [enable_tests=$enable_cxx]) +AM_CONDITIONAL([BUILD_TESTS], [test x"$enable_tests" = xyes]) +AM_COND_IF([BUILD_TESTS], [ + AM_COND_IF([BUILD_CXX], [], [AC_MSG_ERROR([building tests requires --enable-c++])]) +]) + +DX_DOXYGEN_FEATURE([ON]) +DX_HTML_FEATURE([OFF]) +DX_MAN_FEATURE([ON]) +DX_PDF_FEATURE([OFF]) +DX_PS_FEATURE([OFF]) +DX_INIT_DOXYGEN([libbech32]) +AM_CONDITIONAL([BUILD_MANPAGES], [DX_TEST_FEATURE([man])]) + +AC_CONFIG_FILES([Makefile libbech32.pc]) +AC_OUTPUT diff --git a/libbech32.c b/libbech32.c new file mode 100644 index 0000000..6a8663f --- /dev/null +++ b/libbech32.c @@ -0,0 +1,261 @@ +#define _GNU_SOURCE + +#include "bech32.h" + +#include +#include +#include + +#define _likely(...) __builtin_expect(!!(__VA_ARGS__), 1) +#define _unlikely(...) __builtin_expect(!!(__VA_ARGS__), 0) + +#define _const __attribute__ ((__const__)) +#define _pure __attribute__ ((__pure__)) + + +static inline uint_fast32_t _const polymod(uint_fast32_t chk) { + static const uint_least32_t LUT[32] = { +#define _(i) ( \ + (((i) & 1 << 0) ? UINT32_C(0x3b6a57b2) : 0) ^ \ + (((i) & 1 << 1) ? UINT32_C(0x26508e6d) : 0) ^ \ + (((i) & 1 << 2) ? UINT32_C(0x1ea119fa) : 0) ^ \ + (((i) & 1 << 3) ? UINT32_C(0x3d4233dd) : 0) ^ \ + (((i) & 1 << 4) ? UINT32_C(0x2a1462b3) : 0)) + _( 0), _( 1), _( 2), _( 3), _( 4), _( 5), _( 6), _( 7), _( 8), _( 9), _(10), _(11), _(12), _(13), _(14), _(15), + _(16), _(17), _(18), _(19), _(20), _(21), _(22), _(23), _(24), _(25), _(26), _(27), _(28), _(29), _(30), _(31) +#undef _ + }; + return (chk & UINT32_C(0x1FFFFFF)) << 5 ^ LUT[chk >> 25]; +} + +static inline uint_fast32_t _pure polymod_hrp(uint_fast32_t chk, const char *hrp, size_t n_hrp) { + for (size_t i = 0; i < n_hrp; ++i) + chk = polymod(chk) ^ (hrp[i] >> 5 | (hrp[i] >= 'A' && hrp[i] <= 'Z')); + chk = polymod(chk); + for (size_t i = 0; i < n_hrp; ++i) + chk = polymod(chk) ^ hrp[i] & 0x1F; + return chk; +} + +// No data-dependent branches! Assumes string contains only character codes 0-127. +static inline bool _pure is_mixed_case(const char *in, size_t n_in) { +#if SIZE_MAX >= UINT64_MAX || defined(__x86_64__/*support x32*/) + uint_fast64_t flags = 0; + for (size_t i = 0; i < n_in; ++i) + flags |= (uint_fast64_t) 1 << (in[i] - 1 >> 1 & 0x3F); + return (flags & UINT64_C(0x1FFF00000000)) && (flags & UINT64_C(0x1FFF000000000000)); +#else + uint_fast32_t flags = 0; + for (size_t i = 0; i < n_in; ++i) + flags |= (uint_fast32_t) in[i] >> 6 << (in[i] - 1 >> 1 & 0x1F); + return (flags & 0x1FFF) && (flags & UINT32_C(0x1FFF0000)); +#endif +} + + +size_t bech32_encoded_size(size_t n_hrp, size_t nbits_in, size_t n_pad) { + size_t n_out; + if (_unlikely(__builtin_uaddl_overflow(nbits_in, 4, &nbits_in) || + __builtin_uaddl_overflow(n_hrp, 1/*separator*/ + nbits_in / 5 + 6/*checksum*/, &n_out) || + __builtin_uaddl_overflow(n_out, n_pad, &n_out))) + return SIZE_MAX; + return n_out; +} + +static void encode(struct bech32_encoder_state *restrict state) { + static const char ENCODE[32] = { + 'q', 'p', 'z', 'r', 'y', '9', 'x', '8', 'g', 'f', '2', 't', 'v', 'd', 'w', '0', + 's', '3', 'j', 'n', '5', '4', 'k', 'h', 'c', 'e', '6', 'm', 'u', 'a', '7', 'l' + }; + while (state->nbits >= 5) { + uint_fast32_t v = state->bits >> (state->nbits -= 5) & 0x1F; + state->chk = polymod(state->chk) ^ v; + *state->out++ = ENCODE[v], --state->n_out; + } +} + +enum bech32_error bech32_encode_begin(struct bech32_encoder_state *restrict state, char *restrict out, size_t n_out, const char *restrict hrp, size_t n_hrp) { + if (_unlikely(n_hrp < BECH32_HRP_MIN_SIZE)) + return BECH32_HRP_TOO_SHORT; + if (_unlikely(n_hrp > BECH32_HRP_MAX_SIZE)) + return BECH32_HRP_TOO_LONG; + for (size_t i = 0; i < n_hrp; ++i) + if (_unlikely(hrp[i] < 0x21 || hrp[i] >= 0x7F)) + return BECH32_HRP_ILLEGAL_CHAR; + if (_unlikely(__builtin_usubl_overflow(n_out, n_hrp, &n_out) || n_out < 1/*separator*/ + 6/*checksum*/)) + return BECH32_BUFFER_INADEQUATE; + for (size_t i = 0; i < n_hrp; ++i) + out[i] = hrp[i] | (hrp[i] >= 'A' && hrp[i] <= 'Z' ? 0x20 : 0); + out += n_hrp; + *out++ = '1', --n_out; + state->out = out, state->n_out = n_out; + state->nbits = 0; + state->chk = polymod_hrp(1, hrp, n_hrp); + return 0; +} + +enum bech32_error bech32_encode_data(struct bech32_encoder_state *restrict state, const unsigned char *restrict in, size_t nbits_in) { + size_t nbits; + if (_unlikely(__builtin_uaddl_overflow(state->nbits, nbits_in, &nbits) || state->n_out < nbits / 5)) + return BECH32_BUFFER_INADEQUATE; + for (ssize_t i = 0;;) { + encode(state); + if (nbits_in >= CHAR_BIT) + state->bits = state->bits << CHAR_BIT | in[i++], state->nbits += CHAR_BIT, nbits_in -= CHAR_BIT; + else if (nbits_in) + state->bits = state->bits << nbits_in | in[i++], state->nbits += nbits_in, nbits_in = 0; + else + return 0; + } +} + +enum bech32_error bech32_encode_finish(struct bech32_encoder_state *restrict state, uint_least32_t constant) { + if (_unlikely(state->n_out < !!state->nbits + 6/*checksum*/)) + return BECH32_BUFFER_INADEQUATE; + if (state->nbits) { + state->bits <<= 5 - state->nbits, state->nbits = 5; + encode(state); + } + state->bits = state->chk; + for (int i = 0; i < 6; ++i) + state->bits = polymod(state->bits); + state->bits ^= constant, state->nbits = 30; + encode(state); + if (_unlikely(state->chk != constant)) + return BECH32_CHECKSUM_FAILURE; + return 0; +} + + +static const int8_t DECODE['z' - '0' + 1] = { + 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2 +}; + +static bool decode(struct bech32_decoder_state *restrict state, size_t nbits) { + while (state->nbits < nbits) { + int_fast32_t v = (int_fast32_t) *state->in++ - '0'; --state->n_in; + if (_unlikely(v < 0 || v > 'z' - '0' || (v = DECODE[v]) < 0)) + return false; + state->chk = polymod(state->chk) ^ v; + state->bits = state->bits << 5 | v, state->nbits += 5; + } + return true; +} + +ssize_t bech32_decode_begin(struct bech32_decoder_state *restrict state, const char *restrict in, size_t n_in) { + if (_unlikely(n_in < BECH32_MIN_SIZE)) + return BECH32_TOO_SHORT; + if (_unlikely(n_in > BECH32_MAX_SIZE)) + return BECH32_TOO_LONG; + const char *sep = memrchr(in, '1', n_in); + if (_unlikely(!sep)) + return BECH32_NO_SEPARATOR; + size_t n_hrp = sep - in; + if (_unlikely(n_hrp < BECH32_HRP_MIN_SIZE)) + return BECH32_HRP_TOO_SHORT; + if (_unlikely(n_hrp > BECH32_HRP_MAX_SIZE)) + return BECH32_HRP_TOO_LONG; + for (size_t i = 0; i < n_hrp; ++i) + if (_unlikely(in[i] < 0x21 || in[i] >= 0x7F)) + return BECH32_HRP_ILLEGAL_CHAR; + for (const char *p = in + n_hrp + 1/*separator*/, *end = in + n_in; p != end;) { + int_fast32_t v = (int_fast32_t) *p++ - '0'; + if (_unlikely(v < 0 || v > 'z' - '0' || DECODE[v] < 0)) + return BECH32_ILLEGAL_CHAR; + } + if (_unlikely(is_mixed_case(in, n_in))) + return BECH32_MIXED_CASE; + if (_unlikely(__builtin_usubl_overflow(n_in, n_hrp + 1/*separator*/ + 6/*checksum*/, &n_in))) + return BECH32_TOO_SHORT; + state->in = in + n_hrp + 1/*separator*/, state->n_in = n_in; + state->nbits = 0; + state->chk = polymod_hrp(1, in, n_hrp); + return n_hrp; +} + +enum bech32_error bech32_decode_data(struct bech32_decoder_state *restrict state, unsigned char *restrict out, size_t nbits_out) { + size_t nbits; + if (_unlikely(!__builtin_usubl_overflow(nbits_out, state->nbits, &nbits) && + (__builtin_uaddl_overflow(nbits, 4, &nbits) || state->n_in < nbits / 5))) + return BECH32_BUFFER_INADEQUATE; + for (ssize_t i = 0;;) + if (_unlikely(!decode(state, nbits_out > CHAR_BIT ? CHAR_BIT : nbits_out))) + return BECH32_ILLEGAL_CHAR; + else if (nbits_out >= CHAR_BIT) + out[i++] = (unsigned char) (state->bits >> state->nbits - CHAR_BIT), state->nbits -= CHAR_BIT, nbits_out -= CHAR_BIT; + else if (nbits_out) + out[i++] = (unsigned char) (state->bits >> state->nbits - nbits_out & (1 << nbits_out) - 1), state->nbits -= nbits_out, nbits_out = 0; + else + return 0; +} + +ssize_t bech32_decode_finish(struct bech32_decoder_state *restrict state, uint_least32_t constant) { + ssize_t nbits_pad = state->nbits; + if (_unlikely(state->n_in || nbits_pad && (state->bits & (1 << nbits_pad) - 1))) + return BECH32_PADDING_ERROR; + state->n_in = 6, state->nbits = 0; + if (_unlikely(!decode(state, 30))) + return BECH32_ILLEGAL_CHAR; + state->nbits = 0; + if (_unlikely(state->chk != constant || state->n_in)) + return BECH32_CHECKSUM_FAILURE; + return nbits_pad; +} + + +ssize_t segwit_address_encode(char *restrict address, size_t n_address, const unsigned char *restrict program, size_t n_program, const char *restrict hrp, size_t n_hrp, unsigned version) { + if (_unlikely(n_program < WITNESS_PROGRAM_MIN_SIZE)) + return SEGWIT_PROGRAM_TOO_SHORT; + if (_unlikely(n_program > WITNESS_PROGRAM_MAX_SIZE)) + return SEGWIT_PROGRAM_TOO_LONG; + if (_unlikely(version > WITNESS_MAX_VERSION)) + return SEGWIT_VERSION_ILLEGAL; + if (version == 0 && _unlikely(!(n_program == WITNESS_PROGRAM_PKH_SIZE || n_program == WITNESS_PROGRAM_SH_SIZE))) + return SEGWIT_PROGRAM_ILLEGAL_SIZE; + size_t n_actual = n_hrp + 1/*separator*/ + 1/*version*/ + (n_program * CHAR_BIT + 4) / 5 + 6/*checksum*/; + if (_unlikely(n_address < n_actual + 1/*null terminator*/)) + return BECH32_BUFFER_INADEQUATE; + enum bech32_error error; + struct bech32_encoder_state state; + uint8_t ver = (uint8_t) version; + if (_unlikely((error = bech32_encode_begin(&state, address, n_address, hrp, n_hrp)) < 0 || + (error = bech32_encode_data(&state, &ver, 5)) < 0 || + (error = bech32_encode_data(&state, program, n_program * CHAR_BIT)) < 0 || + (error = bech32_encode_finish(&state, version == 0 ? 1 : BECH32M_CONST)) < 0)) + return error; + address[n_actual] = '\0'; + return (ssize_t) n_actual; +} + +ssize_t segwit_address_decode(unsigned char *restrict program, size_t n_program, const char *restrict address, size_t n_address, size_t *restrict n_hrp, unsigned *restrict version) { + if (_unlikely(n_address < SEGWIT_ADDRESS_MIN_SIZE)) + return BECH32_TOO_SHORT; + ssize_t ret; + struct bech32_decoder_state state; + if (_unlikely((ret = bech32_decode_begin(&state, address, n_address)) < 0)) + return ret; + size_t n_actual = (n_address - ret/*hrp*/ - 1/*separator*/ - 1/*version*/ - 6/*checksum*/) * 5 / CHAR_BIT; + if (_unlikely(n_actual < WITNESS_PROGRAM_MIN_SIZE)) + return SEGWIT_PROGRAM_TOO_SHORT; + if (_unlikely(n_actual > WITNESS_PROGRAM_MAX_SIZE)) + return SEGWIT_PROGRAM_TOO_LONG; + if (_unlikely(n_program < n_actual)) + return BECH32_BUFFER_INADEQUATE; + *n_hrp = (size_t) ret; + uint8_t ver; + if (_unlikely((ret = bech32_decode_data(&state, &ver, 5)) < 0)) + return ret; + if (_unlikely(ver > WITNESS_MAX_VERSION)) + return SEGWIT_VERSION_ILLEGAL; + else if (ver == 0 && _unlikely(!(n_actual == WITNESS_PROGRAM_PKH_SIZE || n_actual == WITNESS_PROGRAM_SH_SIZE))) + return SEGWIT_PROGRAM_ILLEGAL_SIZE; + *version = ver; + if (_unlikely((ret = bech32_decode_data(&state, program, n_actual * CHAR_BIT)) < 0 || + (ret = bech32_decode_finish(&state, ver == 0 ? 1 : BECH32M_CONST)) < 0)) + return ret; + return n_actual; +} diff --git a/libbech32.pc.in b/libbech32.pc.in new file mode 100644 index 0000000..fe074c2 --- /dev/null +++ b/libbech32.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +includedir=@includedir@ +libdir=@libdir@ + +Name: libbech32 +Description: Library for Bech32/m encoding and decoding +URL: https://github.com/whitslack/libbech32 +Version: @VERSION@ +Cflags: -I${includedir} +Libs: -L${libdir} -lbech32 diff --git a/libbech32_c++.cpp b/libbech32_c++.cpp new file mode 100644 index 0000000..ed8d22a --- /dev/null +++ b/libbech32_c++.cpp @@ -0,0 +1,129 @@ +#include "bech32.h" + + +static inline const char * __attribute__ ((__const__)) error_to_message(enum ::bech32_error error) { + switch (error) { + case BECH32_TOO_SHORT: + return "encoding is too short"; + case BECH32_TOO_LONG: + return "encoding is too long"; + case BECH32_NO_SEPARATOR: + return "encoding contains no separator"; + case BECH32_MIXED_CASE: + return "encoding uses mixed case"; + case BECH32_ILLEGAL_CHAR: + return "encoding contains an illegal character"; + case BECH32_PADDING_ERROR: + return "padding error"; + case BECH32_CHECKSUM_FAILURE: + return "checksum verification failed"; + case BECH32_BUFFER_INADEQUATE: // should not be reachable + return "buffer size is inadequate"; + case BECH32_HRP_TOO_SHORT: + return "human-readable prefix is empty"; + case BECH32_HRP_TOO_LONG: + return "human-readable prefix is too long"; + case BECH32_HRP_ILLEGAL_CHAR: + return "human-readable prefix contains an illegal character"; + case SEGWIT_VERSION_ILLEGAL: + return "witness version is illegal"; + case SEGWIT_PROGRAM_TOO_SHORT: + return "witness program is too short"; + case SEGWIT_PROGRAM_TOO_LONG: + return "witness program is too long"; + case SEGWIT_PROGRAM_ILLEGAL_SIZE: + return "witness program is of illegal size"; + } + std::abort(); // should not be reachable +} + + +namespace bech32 { + + +Error::Error(enum ::bech32_error error) : std::runtime_error(::error_to_message(error)), error(error) { +} + + +void Encoder::reset(std::string_view hrp, size_t nbits_reserve) { + out.clear(); + out.resize(::bech32_encoded_size(hrp.size(), nbits_reserve, 0)); + if (auto error = ::bech32_encode_begin(&state, out.data(), out.size(), hrp.data(), hrp.size())) + throw Error(error); + out.resize(state.out - out.data()); +} + +void Encoder::write(const void *in, size_t nbits_in) { + size_t written = out.size(); + out.resize(written + (state.n_out = ::bech32_encoded_size(0, state.nbits + nbits_in, 0) - 1)); + state.out = out.data() + written; + if (auto error = ::bech32_encode_data(&state, static_cast(in), nbits_in)) + throw Error(error); + out.resize(state.out - out.data()); +} + +std::string Encoder::finish(uint_least32_t constant) { + size_t written = out.size(); + out.resize(written + (state.n_out = ::bech32_encoded_size(0, state.nbits, 0) - 1)); + state.out = out.data() + written; + if (auto error = ::bech32_encode_finish(&state, constant)) + throw Error(error); + out.resize(state.out - out.data()); + state = { }; + return std::move(out); +} + + +void Decoder::reset(std::string_view in) { + if (auto ret = ::bech32_decode_begin(&state, in.data(), in.size()); ret < 0) + throw Error(static_cast(ret)); + else + hrp = in.substr(0, static_cast(ret)); +} + +void Decoder::read(void *out, size_t nbits_out) { + if (auto error = ::bech32_decode_data(&state, static_cast(out), nbits_out)) + throw Error(error); +} + +std::vector Decoder::read(size_t nbits) { + if (nbits > this->bits_remaining()) + throw Error(BECH32_TOO_SHORT); + std::vector out((nbits + CHAR_BIT - 1) / CHAR_BIT); + this->read(out.data(), nbits); + return out; +} + +size_t Decoder::finish(uint_least32_t constant) { + if (auto ret = ::bech32_decode_finish(&state, constant); ret < 0) + throw Error(static_cast(ret)); + else + return static_cast(ret); +} + + +std::string encode_segwit_address(const void *program, size_t n_program, std::string_view hrp, unsigned version) { + std::string address; + address.resize(::bech32_encoded_size(hrp.size(), 5/*version*/ + n_program * CHAR_BIT, 0)); + if (auto ret = ::segwit_address_encode(address.data(), address.size() + 1/*null*/, static_cast(program), n_program, hrp.data(), hrp.size(), version); ret < 0) + throw Error(static_cast(ret)); + else + address.resize(static_cast(ret)); + return address; +} + +std::tuple, std::string_view, unsigned> decode_segwit_address(std::string_view address) { + std::tuple, std::string_view, unsigned> ret; + auto &[program, hrp, version] = ret; + program.resize((address.size() - 1/*hrp*/ - 1/*separator*/ - 6/*checksum*/) * 5 / CHAR_BIT); + size_t n_hrp; + if (auto ret = ::segwit_address_decode(reinterpret_cast(program.data()), program.size(), address.data(), address.size(), &n_hrp, &version); ret < 0) + throw Error(static_cast(ret)); + else + program.resize(static_cast(ret)); + hrp = address.substr(0, n_hrp); + return ret; +} + + +} // namespace bech32 diff --git a/mainpage.dox b/mainpage.dox new file mode 100644 index 0000000..f1a398e --- /dev/null +++ b/mainpage.dox @@ -0,0 +1,5 @@ +/** + * @mainpage + * + * Please refer to \ref bech32.h. + */ diff --git a/test.cpp b/test.cpp new file mode 100644 index 0000000..090fa2d --- /dev/null +++ b/test.cpp @@ -0,0 +1,205 @@ +#include "bech32.h" + +#include +#include +#include +#include +#include + + +template requires std::same_as, char> +static inline auto lowercase_view(R &&range) { + return std::views::transform(std::forward(range), static_cast(std::tolower)); +} + +static void test_round_trip(std::string_view encoding, bool bech32m) { + bech32::Decoder decoder(encoding); + auto bytes = decoder.read(); + uint8_t extra_bits = 0; + size_t nbits_extra = decoder.bits_remaining(); + if (nbits_extra) + decoder.read(&extra_bits, nbits_extra); + decoder.finish(bech32m ? BECH32M_CONST : 1); + bech32::Encoder encoder(std::string(decoder.prefix()).c_str(), bytes.size() * CHAR_BIT); + encoder.write(bytes.data(), bytes.size() * CHAR_BIT); + if (nbits_extra) + encoder.write(&extra_bits, nbits_extra); + auto actual = encoder.finish(bech32m ? BECH32M_CONST : 1); + assert(std::ranges::equal(actual, lowercase_view(encoding))); +} + +static void test_invalid(std::string_view encoding, bool bech32m, enum ::bech32_error reason) { + try { + bech32::Decoder decoder(encoding); + decoder.read(); + if (size_t nbits_extra = decoder.bits_remaining()) { + assert(nbits_extra < CHAR_BIT); + uint8_t extra_bits; + decoder.read(&extra_bits, nbits_extra); + } + decoder.finish(bech32m ? BECH32M_CONST : 1); + } + catch (const bech32::Error &e) { + assert(e.error == reason); + return; + } + throw std::logic_error("should have thrown"); +} + +static void test_segwit_round_trip(std::string_view address, unsigned expect_version, std::span expect_program) { + auto [program, hrp, version] = bech32::decode_segwit_address(address); + assert(version == expect_version); + assert(std::ranges::equal(program, expect_program)); + auto actual = bech32::encode_segwit_address(program.data(), program.size(), hrp, version); + assert(std::ranges::equal(actual, lowercase_view(address))); +} + +static void test_segwit_invalid(std::string_view address, enum ::bech32_error reason) { + try { + bech32::decode_segwit_address(address); + } + catch (const bech32::Error &e) { + assert(e.error == reason); + return; + } + throw std::logic_error("should have thrown"); +} + +template requires std::is_trivially_copyable_v()))::element_type> +static inline void test_segwit_round_trip(std::string_view address, unsigned version, T &&expect) { + auto bytes = as_bytes(std::span(std::forward(expect))); + return test_segwit_round_trip(address, version, std::span(bytes)); +} + +static inline void test_segwit_round_trip(std::string_view address, unsigned version, std::initializer_list expect) { + auto bytes = as_bytes(std::span(expect)); + return test_segwit_round_trip(address, version, std::span(bytes)); +} + +int main() { + // HASH160(0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798) + static constexpr uint8_t pkh[20] = { + 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6 + }; + test_segwit_round_trip("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4", 0, pkh); + test_segwit_round_trip("tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx", 0, pkh); + + // SHA256(21 0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 AC) + static constexpr uint8_t sh[32] = { + 0x18, 0x63, 0x14, 0x3c, 0x14, 0xc5, 0x16, 0x68, 0x04, 0xbd, 0x19, 0x20, 0x33, 0x56, 0xda, 0x13, + 0x6c, 0x98, 0x56, 0x78, 0xcd, 0x4d, 0x27, 0xa1, 0xb8, 0xc6, 0x32, 0x96, 0x04, 0x90, 0x32, 0x62 + }; + test_segwit_round_trip("bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3", 0, sh); + test_segwit_round_trip("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", 0, sh); + + test_round_trip("A12UEL5L", false); + test_round_trip("A1LQFN3A", true); + test_round_trip("a12uel5l", false); + test_round_trip("a1lqfn3a", true); + test_round_trip("an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", false); + test_round_trip("an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6", true); + test_round_trip("abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", false); + test_round_trip("abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx", true); + test_round_trip("11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", false); + test_round_trip("11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8", true); + test_round_trip("split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", false); + test_round_trip("split1checkupstagehandshakeupstreamerranterredcaperredlc445v", true); + test_round_trip("?1ezyfcl", false); + test_round_trip("?1v759aa", true); + + test_invalid("A1LQFN3A", false, BECH32_CHECKSUM_FAILURE); + test_invalid("a1lqfn3a", false, BECH32_CHECKSUM_FAILURE); + test_invalid("an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6", false, BECH32_CHECKSUM_FAILURE); + test_invalid("abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx", false, BECH32_CHECKSUM_FAILURE); + test_invalid("11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8", false, BECH32_CHECKSUM_FAILURE); + test_invalid("split1checkupstagehandshakeupstreamerranterredcaperredlc445v", false, BECH32_CHECKSUM_FAILURE); + test_invalid("?1v759aa", false, BECH32_CHECKSUM_FAILURE); + + test_invalid("\x20""1nwldj5", false, BECH32_HRP_ILLEGAL_CHAR); + test_invalid("\x20""1xj0phk", true, BECH32_HRP_ILLEGAL_CHAR); + test_invalid("\x7F""1axkwrx", false, BECH32_HRP_ILLEGAL_CHAR); + test_invalid("\x7F""1g6xzxy", true, BECH32_HRP_ILLEGAL_CHAR); + test_invalid("\x80""1eym55h", false, BECH32_HRP_ILLEGAL_CHAR); + test_invalid("\x80""1vctc34", true, BECH32_HRP_ILLEGAL_CHAR); + test_invalid("an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx", false, BECH32_TOO_LONG); + test_invalid("an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4", true, BECH32_TOO_LONG); + test_invalid("pzry9x0s0muk", false, BECH32_NO_SEPARATOR); + test_invalid("qyrz8wqd2c9m", true, BECH32_NO_SEPARATOR); + test_invalid("1pzry9x0s0muk", false, BECH32_HRP_TOO_SHORT); + test_invalid("1qyrz8wqd2c9m", true, BECH32_HRP_TOO_SHORT); + test_invalid("x1b4n0q5v", false, BECH32_ILLEGAL_CHAR); + test_invalid("y1b0jsk6g", true, BECH32_ILLEGAL_CHAR); + test_invalid("lt1igcx5c0", true, BECH32_ILLEGAL_CHAR); + test_invalid("li1dgmt3", false, BECH32_TOO_SHORT); + test_invalid("in1muywd", true, BECH32_TOO_SHORT); + test_invalid("de1lg7wt\xFF", false, BECH32_ILLEGAL_CHAR); + test_invalid("mm1crxm3i", true, BECH32_ILLEGAL_CHAR); + test_invalid("au1s5cgom", true, BECH32_ILLEGAL_CHAR); + test_invalid("A1G7SGD8", false, BECH32_CHECKSUM_FAILURE); + test_invalid("M1VUXWEZ", true, BECH32_CHECKSUM_FAILURE); + test_invalid("10a06t8", false, BECH32_TOO_SHORT); + test_invalid("16plkw9", true, BECH32_TOO_SHORT); + test_invalid("1qzzfhee", false, BECH32_HRP_TOO_SHORT); + test_invalid("1p2gdwpf", true, BECH32_HRP_TOO_SHORT); + + test_invalid("a12uelsl", false, BECH32_CHECKSUM_FAILURE); + test_invalid("a1lqfn39", true, BECH32_CHECKSUM_FAILURE); + test_invalid("hj1fpjkcmr0ypmk7unvvssszef0zk", false, BECH32_CHECKSUM_FAILURE); + test_invalid("hj1fpjkcmr0ypmk7unvvsssh9er85", true, BECH32_CHECKSUM_FAILURE); + test_invalid("hi1fpjkcmr0ypmx7unvvssszef0zk", false, BECH32_CHECKSUM_FAILURE); + test_invalid("hi1fpjkcmr0ypmx7unvvsssh9er85", true, BECH32_CHECKSUM_FAILURE); + + test_segwit_round_trip("BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", 0, pkh); + test_segwit_round_trip("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", 0, sh); + test_segwit_round_trip("bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y", 1, { + 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6, + 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6 + }); + test_segwit_round_trip("BC1SW50QGDZ25J", 16, { 0x75, 0x1e }); + test_segwit_round_trip("bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs", 2, std::span(pkh).first(16)); + test_segwit_round_trip("tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", 0, { + 0x00, 0x00, 0x00, 0xc4, 0xa5, 0xca, 0xd4, 0x62, 0x21, 0xb2, 0xa1, 0x87, 0x90, 0x5e, 0x52, 0x66, + 0x36, 0x2b, 0x99, 0xd5, 0xe9, 0x1c, 0x6c, 0xe2, 0x4d, 0x16, 0x5d, 0xab, 0x93, 0xe8, 0x64, 0x33 + }); + test_segwit_round_trip("tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c", 1, { + 0x00, 0x00, 0x00, 0xc4, 0xa5, 0xca, 0xd4, 0x62, 0x21, 0xb2, 0xa1, 0x87, 0x90, 0x5e, 0x52, 0x66, + 0x36, 0x2b, 0x99, 0xd5, 0xe9, 0x1c, 0x6c, 0xe2, 0x4d, 0x16, 0x5d, 0xab, 0x93, 0xe8, 0x64, 0x33 + }); + test_segwit_round_trip("bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0", 1, { + 0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce, 0x87, 0x0b, 0x07, + 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, 0x81, 0x5b, 0x16, 0xf8, 0x17, 0x98 + }); + + test_segwit_invalid("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", BECH32_CHECKSUM_FAILURE); + test_segwit_invalid("BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", SEGWIT_VERSION_ILLEGAL); + test_segwit_invalid("bc1rw5uspcuh", BECH32_TOO_SHORT); + test_segwit_invalid("bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", SEGWIT_PROGRAM_TOO_LONG); + test_segwit_invalid("BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", SEGWIT_PROGRAM_ILLEGAL_SIZE); + test_segwit_invalid("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7", BECH32_MIXED_CASE); + test_segwit_invalid("bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", BECH32_PADDING_ERROR); + test_segwit_invalid("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", BECH32_PADDING_ERROR); + test_segwit_invalid("bc1gmk9yu", BECH32_TOO_SHORT); + + test_segwit_invalid("bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd", BECH32_CHECKSUM_FAILURE); + test_segwit_invalid("tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf", BECH32_CHECKSUM_FAILURE); + test_segwit_invalid("BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL", BECH32_CHECKSUM_FAILURE); + test_segwit_invalid("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh", BECH32_CHECKSUM_FAILURE); + test_segwit_invalid("tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47", BECH32_CHECKSUM_FAILURE); + test_segwit_invalid("bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4", BECH32_ILLEGAL_CHAR); + test_segwit_invalid("BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R", SEGWIT_VERSION_ILLEGAL); + test_segwit_invalid("bc1pw5dgrnzv", BECH32_TOO_SHORT); + test_segwit_invalid("bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav", SEGWIT_PROGRAM_TOO_LONG); + test_segwit_invalid("BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", SEGWIT_PROGRAM_ILLEGAL_SIZE); + test_segwit_invalid("tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq", BECH32_MIXED_CASE); + test_segwit_invalid("bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf", BECH32_PADDING_ERROR); + test_segwit_invalid("tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j", BECH32_PADDING_ERROR); + test_segwit_invalid("bc1gmk9yu", BECH32_TOO_SHORT); + + // The below test vectors, which were originally valid under BIP173 (Bech32), are now invalid under BIP350 (Bech32m) because + // they use witness versions greater than 0 but carry Bech32 checksums. + test_segwit_invalid("bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", BECH32_CHECKSUM_FAILURE); + test_segwit_invalid("BC1SW50QA3JX3S", BECH32_CHECKSUM_FAILURE); + test_segwit_invalid("bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", BECH32_CHECKSUM_FAILURE); + + return 0; +}