From 3bb870be9d4068489b02a495921d69b9e06cf115 Mon Sep 17 00:00:00 2001 From: Matt Whitlock Date: Mon, 18 Mar 2024 09:26:55 -0400 Subject: [PATCH] support Blech32/Blech32m Note: The `--bech32m` option in the command-line utility is renamed to `--modified`, but the old spelling, although now undocumented, is retained for backward compatibility. Specifying both `-m`/`--modified` and `-l`/`--blech` is allowed, but the historical `--bech32m` may not be combined with `-l`/`--blech`. --- .gitignore | 1 + Doxyfile | 2 +- Makefile.am | 29 ++++- README.md | 30 ++++-- bech32.1 => bech32.1.in | 45 +++++++- bech32.c | 231 +++++++++++++++++++++++++++++++--------- bech32.h | 102 +++++++++++++++++- configure.ac | 6 ++ libbech32.c | 89 ++++++++++++++-- libbech32.pc.in | 2 +- libbech32_c++.cpp | 34 +++++- 11 files changed, 497 insertions(+), 74 deletions(-) rename bech32.1 => bech32.1.in (79%) diff --git a/.gitignore b/.gitignore index 0aafbbb..269dc7e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.o /doxygen-doc/ /bech32 +/bech32.1 /libbech32.pc /test diff --git a/Doxyfile b/Doxyfile index e0806af..2e183ce 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,5 +1,5 @@ PROJECT_NAME = libbech32 -PROJECT_BRIEF = "Library for Bech32/Bech32m encoding and decoding" +PROJECT_BRIEF = "Library for Bech32/Bech32m/Blech32/Blech32m encoding/decoding" OUTPUT_DIRECTORY = $(DOCDIR) OPTIMIZE_OUTPUT_FOR_C = YES INLINE_SIMPLE_STRUCTS = YES diff --git a/Makefile.am b/Makefile.am index 0a21821..ab09025 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4,6 +4,9 @@ AM_CPPFLAGS = if NDEBUG AM_CPPFLAGS += -DNDEBUG endif +if DISABLE_BLECH32 +AM_CPPFLAGS += -DDISABLE_BLECH32 +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 @@ -13,7 +16,15 @@ include_HEADERS = bech32.h pkgconfig_DATA = libbech32.pc EXTRA_DIST = $(pkgconfig_DATA) -dist_man_MANS = bech32.1 +man_MANS = bech32.1 +MOSTLYCLEANFILES = $(man_MANS) + +bech32.1 : bech32.1.in +if DISABLE_BLECH32 + sed -Ee '/^@@IF_BLECH32@@$$/,/^@@(ELSE|ENDIF)_BLECH32@@$$/d' -e '/^@@ENDIF_BLECH32@@$$/d' $< >$@ +else + sed -Ee '/^@@ELSE_BLECH32@@$$/,/^@@ENDIF_BLECH32@@$$/d' -e '/^@@(END)?IF_BLECH32@@$$/d' $< >$@ +endif lib_LTLIBRARIES = libbech32.la libbech32_la_SOURCES = libbech32.c @@ -52,7 +63,7 @@ check-local: endif # BUILD_TESTS @DX_RULES@ -MOSTLYCLEANFILES = $(DX_CLEANFILES) +MOSTLYCLEANFILES += $(DX_CLEANFILES) if BUILD_MANPAGES man3_MANS = $(addprefix doxygen-doc/man/man3/,$(addsuffix .3,bech32.h \ @@ -64,7 +75,21 @@ 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 ; } +if !DISABLE_BLECH32 + cd $(DESTDIR)$(bindir) && { \ + test -e blech32$(EXEEXT) || $(LN_S) -n bech32$(EXEEXT) blech32$(EXEEXT) ; \ + test -e blech32m$(EXEEXT) || $(LN_S) -n bech32$(EXEEXT) blech32m$(EXEEXT) ; \ + } + cd $(DESTDIR)$(man1dir) && { \ + test -e blech32.1 || $(LN_S) -n bech32.1 blech32.1 ; \ + test -e blech32m.1 || $(LN_S) -n bech32.1 blech32m.1 ; \ + } +endif uninstall-hook: rm -f $(DESTDIR)$(bindir)/bech32m$(EXEEXT) rm -f $(DESTDIR)$(man1dir)/bech32m.1 +if !DISABLE_BLECH32 + rm -f $(addprefix $(DESTDIR)$(bindir)/,$(addsuffix $(EXEEXT),blech32 blech32m)) + rm -f $(addprefix $(DESTDIR)$(man1dir)/,$(addsuffix .1,blech32 blech32m)) +endif diff --git a/README.md b/README.md index 33fb0a7..8349f54 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # libbech32 -**Library for Bech32/Bech32m encoding and decoding** +**Library for Bech32/Bech32m/Blech32/Blech32m encoding/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. +Libbech32 is a C library for encoding and decoding SegWit addresses and arbitrary bit strings in Bech32/Bech32m/Blech32/Blech32m 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/Blech32/Blech32m 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. +The low-level API allows encoding/decoding arbitrary bit strings to/from Bech32/Bech32m/Blech32/Blech32m 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 @@ -136,6 +136,10 @@ int main() { } ``` +### Blech32/Blech32m + +Unless configured with `--disable-blech32`, the low-level API supports Blech32/Blech32m encoding/decoding via structures and functions whose names are prefixed by `blech32_` instead of `bech32_`. Aside from the names, the API is the same. Likewise, the C++ wrappers are in the `blech32` namespace instead of `bech32`. + ## High-level API The high-level API allows encoding/decoding a SegWit address with a single function call. @@ -230,11 +234,19 @@ int main() { } ``` +### Blech32/Blech32m + +Unless configured with `--disable-blech32`, the high-level API supports encoding/decoding of blinding SegWit addresses via functions whose names are prefixed by `blech32_` instead of `segwit_`. Aside from the names, the API is the same. Likewise, the C++ wrappers are in the `blech32` namespace instead of `bech32`. + ## 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*] } +**Usage:** +`bech32` \[`-h`] \[`-l`] \[`-m`] *hrp* { \[*version*] | `-d` \[`-v`|*version*] } +`bech32m` \[`-h`] *hrp* { \[*version*] | `-d` \[`-v`|*version*] } +`blech32` \[`-h`] *hrp* { \[*version*] | `-d` \[`-v`|*version*] } +`blech32m` \[`-h`] *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. @@ -248,9 +260,13 @@ If version is given, assert that it matches the version field in the da
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.
+
-l,--blech
+
Use Blech32/Blech32m instead of Bech32/Bech32m. +Implied if the command is invoked as blech32 or blech32m.
+ +
-m,--modified
+
Use Bech32m/Blech32m instead of Bech32/Blech32. +Implied if the command is invoked as bech32m or blech32m.
-v,--exit-version
Extract a 5-bit SegWit version field and return it as the exit status.
diff --git a/bech32.1 b/bech32.1.in similarity index 79% rename from bech32.1 rename to bech32.1.in index d2836ed..39c79da 100644 --- a/bech32.1 +++ b/bech32.1.in @@ -1,11 +1,20 @@ .TH BECH32 1 2024-03-08 libbech32 . .SH NAME -bech32 \- encode/decode data in Bech32/Bech32m format +bech32 \- encode/decode data in +@@IF_BLECH32@@ +Bech32/Bech32m/Blech32/Blech32m +@@ELSE_BLECH32@@ +Bech32/Bech32m +@@ENDIF_BLECH32@@ +format . .SH SYNOPSIS .SY bech32 .OP \-h +@@IF_BLECH32@@ +.OP \-l +@@ENDIF_BLECH32@@ .OP \-m .I hrp { @@ -23,6 +32,26 @@ bech32 \- encode/decode data in Bech32/Bech32m format .B \-d [\fB\-v\fR|\fIversion\fR] } +@@IF_BLECH32@@ +.SY blech32 +.OP \-h +.I hrp +{ +[\fIversion\fR] +| +.B \-d +[\fB\-v\fR|\fIversion\fR] +} +.SY blech32m +.OP \-h +.I hrp +{ +[\fIversion\fR] +| +.B \-d +[\fB\-v\fR|\fIversion\fR] +} +@@ENDIF_BLECH32@@ .YS . .SH DESCRIPTION @@ -40,10 +69,22 @@ If \fIversion\fR is given, assert that it matches the version field in the data. Use hexadecimal for data input/output. If this option is not specified, the data are read/written in raw binary. .TP -.BR \-m ", " \-\-bech32m +@@IF_BLECH32@@ +.BR \-l ", " \-\-blech +Use Blech32/Blech32m instead of Bech32/Bech32m. +Implied if the command is invoked as +.BR blech32 " or " blech32m . +.TP +.BR \-m ", " \-\-modified +Use Bech32m/Blech32m instead of Bech32/Blech32. +Implied if the command is invoked as +.BR bech32m " or " blech32m . +@@ELSE_BLECH32@@ +.BR \-m ", " \-\-modified Use Bech32m instead of Bech32. Implied if the command is invoked as .BR bech32m . +@@ENDIF_BLECH32@@ .TP .BR \-v ", " \-\-exit\-version Extract a 5-bit SegWit version field and return it as the exit status. diff --git a/bech32.c b/bech32.c index fb21e15..a7bb1d6 100644 --- a/bech32.c +++ b/bech32.c @@ -15,19 +15,50 @@ 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" + const char *implied = strcmp(program_invocation_short_name, "bech32m") == 0 ? "Bech32m" : NULL; +#ifndef DISABLE_BLECH32 + if (!implied) + if (strcmp(program_invocation_short_name, "blech32") == 0) + implied = "Blech32"; + else if (strcmp(program_invocation_short_name, "blech32m") == 0) + implied = "Blech32m"; +#endif + fprintf(stderr, "usage: %1$s [-h]%2$s { [] | -d [-v|] }\n\n" + "Reads data from stdin and writes its %3$s encoding to stdout. If \n" + "is 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" + " Decode a %3$s 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" + "%4$s" +#ifndef DISABLE_BLECH32 + "%5$s" +#endif "-v,--exit-version\n" " Extract a 5-bit SegWit version field and return it as the exit status.\n", - program_invocation_short_name); + program_invocation_short_name, + implied ? "" : +#ifndef DISABLE_BLECH32 + " [-l]" +#endif + " [-m]", + implied ?: "Bech32", +#ifndef DISABLE_BLECH32 + implied ? "" : + "-l,--blech\n" + " Use Blech32/Blech32m instead of Bech32/Bech32m. Implied if invoked as\n" + " 'blech32' or 'blech32m'.\n", +#endif + implied ? "" : + "-m,--modified\n" +#ifdef DISABLE_BLECH32 + " Use Bech32m instead of Bech32. Implied if invoked as 'bech32m'.\n" +#else + " Use Bech32m/Blech32m instead of Bech32/Blech32. Implied if invoked as\n" + " 'bech32m' or 'blech32m'.\n" +#endif + ); } static int gethex() { @@ -97,15 +128,31 @@ 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' }, +#ifndef DISABLE_BLECH32 + { .name = "blech", .has_arg = no_argument, .val = 'l' }, +#endif + { .name = "modified", .has_arg = no_argument, .val = 'm' }, + { .name = "bech32m", .has_arg = no_argument, .val = 3 }, // retained for backward compatibility { .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;) { + bool modified = strcmp(program_invocation_short_name, "bech32m") == 0; + bool implied = modified, decode = false, hex = false, exit_version = false; +#ifndef DISABLE_BLECH32 + int blech = 0; + if (!implied) + if (strcmp(program_invocation_short_name, "blech32") == 0) + implied = true, blech = 1; + else if (strcmp(program_invocation_short_name, "blech32m") == 0) + implied = true, modified = true, blech = 1; +#endif + for (int opt; (opt = getopt_long(argc, argv, "dh" +#ifndef DISABLE_BLECH32 + "l" +#endif + "mv", longopts, NULL)) >= 0;) switch (opt) { case 1: print_usage(); @@ -119,30 +166,59 @@ int main(int argc, char *argv[]) { case 'h': hex = true; break; +#ifndef DISABLE_BLECH32 + case 'l': + if (implied || blech < 0) + goto usage_error; + blech = 1; + break; +#endif + case 3: +#ifndef DISABLE_BLECH32 + if (blech > 0) + goto usage_error; + blech = -1; + // fall through +#endif case 'm': - constant = BECH32M_CONST; + if (implied) + goto usage_error; + modified = true; break; case 'v': exit_version = true; break; default: + usage_error: 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) + size_t n_hrp = strlen(hrp), nmin_hrp, nmax_hrp; +#ifndef DISABLE_BLECH32 + if (blech > 0) + nmin_hrp = BLECH32_HRP_MIN_SIZE, nmax_hrp = BLECH32_HRP_MAX_SIZE; + else +#endif + nmin_hrp = BECH32_HRP_MIN_SIZE, nmax_hrp = BECH32_HRP_MAX_SIZE; + if (n_hrp < nmin_hrp) errx(EX_USAGE, errmsg(BECH32_HRP_TOO_SHORT)); - if (n_hrp > BECH32_HRP_MAX_SIZE) + if (n_hrp > nmax_hrp) 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) - BECH32_CHECKSUM_SIZE) * 5 / CHAR_BIT; + size_t n_in = 0, nmax_in; +#ifndef DISABLE_BLECH32 + if (blech > 0) + nmax_in = decode ? BLECH32_MAX_SIZE : + (BLECH32_MAX_SIZE - n_hrp - 1/*separator*/ - (version >= 0) - BLECH32_CHECKSUM_SIZE) * 5 / CHAR_BIT; + else +#endif + nmax_in = decode ? BECH32_MAX_SIZE : + (BECH32_MAX_SIZE - n_hrp - 1/*separator*/ - (version >= 0) - BECH32_CHECKSUM_SIZE) * 5 / CHAR_BIT; + unsigned char in[nmax_in]; if (decode || hex) { for (int c;;) { if (decode ? (c = getchar()) < 0 || c == '\n' : (c = gethex()) < 0) { @@ -163,44 +239,97 @@ int main(int argc, char *argv[]) { errx(EX_DATAERR, errmsg(BECH32_TOO_LONG)); } - unsigned char out[BECH32_MAX_SIZE + 1/*'\n'*/]; - size_t n_out = 0; + size_t n_out = 0, nmax_out; +#ifndef DISABLE_BLECH32 + if (blech > 0) + nmax_out = BLECH32_MAX_SIZE + 1/*'\n'*/; + else +#endif + nmax_out = BECH32_MAX_SIZE + 1/*'\n'*/; + unsigned char out[nmax_out]; 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) +#ifndef DISABLE_BLECH32 + if (blech > 0) { + if (n_in < BLECH32_MIN_SIZE) errx(EX_DATAERR, errmsg(BECH32_TOO_SHORT)); - int8_t expected_version = version; - if ((ret = bech32_decode_data(&state, (unsigned char *) &version, 5)) < 0) + ssize_t ret; + struct blech32_decoder_state state; + if ((ret = blech32_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 (blech32_decode_bits_remaining(&state) < 5) + errx(EX_DATAERR, errmsg(BECH32_TOO_SHORT)); + int8_t expected_version = version; + if ((ret = blech32_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 = blech32_decode_bits_remaining(&state) / CHAR_BIT; + assert(n_out <= nmax_out); + if ((ret = blech32_decode_data(&state, out, n_out * CHAR_BIT)) < 0 || + (ret = blech32_decode_finish(&state, modified ? BLECH32M_CONST : 1)) < 0) + errx(EX_DATAERR, errmsg((enum bech32_error) ret)); + } + else +#endif + { + 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 <= nmax_out); + if ((ret = bech32_decode_data(&state, out, n_out * CHAR_BIT)) < 0 || + (ret = bech32_decode_finish(&state, modified ? BECH32M_CONST : 1)) < 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 + BECH32_CHECKSUM_SIZE; - 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'; +#ifndef DISABLE_BLECH32 + if (blech > 0) { + n_out = n_hrp + 1/*separator*/ + (version >= 0) + (n_in * CHAR_BIT + 4) / 5 + BLECH32_CHECKSUM_SIZE; + assert(n_out <= nmax_out); + ssize_t ret; + struct blech32_encoder_state state; + if ((ret = blech32_encode_begin(&state, (char *) out, n_out, hrp, n_hrp)) < 0) + errx(EX_DATAERR, errmsg((enum bech32_error) ret)); + if (version >= 0 && (ret = blech32_encode_data(&state, (unsigned char *) &version, 5)) < 0 || + (ret = blech32_encode_data(&state, in, n_in * CHAR_BIT)) < 0 || + (ret = blech32_encode_finish(&state, modified ? BLECH32M_CONST : 1)) < 0) + errx(EX_SOFTWARE, errmsg((enum bech32_error) ret)); + out[n_out++] = '\n'; + } + else +#endif + { + n_out = n_hrp + 1/*separator*/ + (version >= 0) + (n_in * CHAR_BIT + 4) / 5 + BECH32_CHECKSUM_SIZE; + assert(n_out <= nmax_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, modified ? BECH32M_CONST : 1)) < 0) + errx(EX_SOFTWARE, errmsg((enum bech32_error) ret)); + out[n_out++] = '\n'; + } } if (hex && decode ? diff --git a/bech32.h b/bech32.h index c15318d..0772d1d 100644 --- a/bech32.h +++ b/bech32.h @@ -2,6 +2,48 @@ #ifndef BECH32_H_INCLUDED #define BECH32_H_INCLUDED +#ifdef INCLUDED_FOR_BLECH32 +# define bech32_checksum_t blech32_checksum_t +# define bech32_constant_t blech32_constant_t +# define BECH32M_CONST BLECH32M_CONST +# define bech32_encoder_state blech32_encoder_state +# define bech32_encoded_size blech32_encoded_size +# define bech32_encode_begin blech32_encode_begin +# define bech32_encode_data blech32_encode_data +# define bech32_encode_finish blech32_encode_finish +# define bech32_decoder_state blech32_decoder_state +# define bech32_decode_begin blech32_decode_begin +# define bech32_decode_bits_remaining blech32_decode_bits_remaining +# define bech32_decode_data blech32_decode_data +# define bech32_decode_finish blech32_decode_finish +# define bech32_address_encode blech32_address_encode +# define bech32_address_decode blech32_address_decode +#else +# ifndef DISABLE_BLECH32 +# undef BECH32_H_INCLUDED +# define INCLUDED_FOR_BLECH32 +# include "bech32.h" +# undef INCLUDED_FOR_BLECH32 +# define BECH32_H_SECOND_PASS +# endif +# undef bech32 +# undef bech32_address_decode +# undef bech32_address_encode +# undef bech32_decode_finish +# undef bech32_decode_data +# undef bech32_decode_bits_remaining +# undef bech32_decode_begin +# undef bech32_decoder_state +# undef bech32_encode_finish +# undef bech32_encode_data +# undef bech32_encode_begin +# undef bech32_encoded_size +# undef bech32_encoder_state +# undef BECH32M_CONST +# undef bech32_constant_t +# undef bech32_checksum_t +#endif + #include #include #include @@ -12,6 +54,8 @@ extern "C" { #endif +#ifndef INCLUDED_FOR_BLECH32 + typedef uint_fast32_t bech32_checksum_t; typedef uint_least32_t bech32_constant_t; @@ -27,9 +71,36 @@ static const size_t WITNESS_PROGRAM_MAX_SIZE = 40, WITNESS_PROGRAM_PKH_SIZE = 20, WITNESS_PROGRAM_SH_SIZE = 32, + WITNESS_PROGRAM_TR_SIZE = 32, SEGWIT_ADDRESS_MIN_SIZE = BECH32_HRP_MIN_SIZE + 1/*separator*/ + 1/*version*/ + ((WITNESS_PROGRAM_MIN_SIZE * CHAR_BIT + 4) / 5) + BECH32_CHECKSUM_SIZE; +#else // defined(INCLUDED_FOR_BLECH32) + +typedef uint_fast64_t blech32_checksum_t; +typedef uint_least64_t blech32_constant_t; + +static const blech32_constant_t BLECH32M_CONST = UINT64_C(0x455972a3350f7a1); + +static const size_t + BLECH32_CHECKSUM_SIZE = 12, + BLECH32_HRP_MIN_SIZE = 1, + BLECH32_MAX_SIZE = 1000, + BLECH32_HRP_MAX_SIZE = BLECH32_MAX_SIZE - 1/*separator*/ - BLECH32_CHECKSUM_SIZE, + BLECH32_MIN_SIZE = BLECH32_HRP_MIN_SIZE + 1/*separator*/ + BLECH32_CHECKSUM_SIZE, + BLINDING_PUBKEY_SIZE = 33, + BLINDING_PROGRAM_MIN_SIZE = 2 + 0/*reference implementation omits blinding pubkey here*/, + BLINDING_PROGRAM_MAX_SIZE = 40 + BLINDING_PUBKEY_SIZE, + BLINDING_PROGRAM_PKH_SIZE = 20 + BLINDING_PUBKEY_SIZE, + BLINDING_PROGRAM_SH_SIZE = 32 + BLINDING_PUBKEY_SIZE, + BLINDING_PROGRAM_TR_SIZE = 32 + BLINDING_PUBKEY_SIZE, + BLINDING_ADDRESS_MIN_SIZE = BLECH32_HRP_MIN_SIZE + 1/*separator*/ + 1/*version*/ + + ((BLINDING_PROGRAM_MIN_SIZE * CHAR_BIT + 4) / 5) + BLECH32_CHECKSUM_SIZE; + +#endif // defined(INCLUDED_FOR_BLECH32) + +#ifndef BECH32_H_SECOND_PASS + static const unsigned WITNESS_MAX_VERSION = 16; @@ -54,6 +125,8 @@ enum bech32_error { SEGWIT_PROGRAM_ILLEGAL_SIZE = -15, }; +#endif // !defined(BECH32_H_SECOND_PASS) + /** * @brief The state of a Bech32 encoder. @@ -285,7 +358,7 @@ ssize_t bech32_decode_finish( * @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( +ssize_t bech32_address_encode( char *restrict address, size_t n_address, const unsigned char *restrict program, @@ -324,7 +397,7 @@ ssize_t segwit_address_encode( * @c BECH32_PADDING_ERROR because of a padding error, or * @c BECH32_CHECKSUM_FAILURE because checksum verification failed. */ -ssize_t segwit_address_decode( +ssize_t bech32_address_decode( unsigned char *restrict program, size_t n_program, const char *restrict address, @@ -334,11 +407,23 @@ ssize_t segwit_address_decode( __attribute__ ((__access__ (write_only, 1), __access__ (read_only, 3), __access__ (write_only, 5), __access__ (write_only, 6), __nonnull__, __nothrow__, __warn_unused_result__)); +#ifndef BECH32_H_SECOND_PASS +ssize_t segwit_address_encode // line break so we don't generate man pages for these deprecated symbols + (char *restrict, size_t, const unsigned char *restrict, size_t, const char *restrict, size_t, unsigned) + __attribute__ ((__deprecated__ ("renamed to bech32_address_encode"))); +ssize_t segwit_address_decode // ditto + (unsigned char *restrict, size_t, const char *restrict, size_t, size_t *restrict, unsigned *restrict) + __attribute__ ((__deprecated__ ("renamed to bech32_address_decode"))); +#endif + + #ifdef __cplusplus } // extern "C" # undef restrict +#ifndef BECH32_H_SECOND_PASS + #include #include #include @@ -361,6 +446,16 @@ class Error : public std::runtime_error { }; +} // namespace bech32 + +#endif // !defined(BECH32_H_SECOND_PASS) + +#ifdef INCLUDED_FOR_BLECH32 +# define bech32 blech32 +#endif +namespace bech32 { + + class Encoder { private: @@ -437,7 +532,10 @@ std::tuple, std::string_view, unsigned> decode_segwit_add } // namespace bech32 +#undef bech32 #endif // defined(__cplusplus) +#undef BECH32_H_SECOND_PASS + #endif // !defined(BECH32_H_INCLUDED) diff --git a/configure.ac b/configure.ac index fba0ae9..6c3d8b2 100644 --- a/configure.ac +++ b/configure.ac @@ -19,6 +19,12 @@ AC_ARG_ENABLE([assertions], [enable_assertions=no]) AM_CONDITIONAL([NDEBUG], [test x"$enable_assertions" = xno]) +AC_ARG_ENABLE([blech32], + [AS_HELP_STRING([--disable-blech32], [do not include Blech32 code in the library])], + [enable_blech32=$enableval], + [enable_blech32=yes]) +AM_CONDITIONAL([DISABLE_BLECH32], [test x"$enable_blech32" = xno]) + AC_ARG_ENABLE([c++], [AS_HELP_STRING([--disable-c++], [do not include C++ code in the library])], [enable_cxx=$enableval], diff --git a/libbech32.c b/libbech32.c index 833e8cb..d680e91 100644 --- a/libbech32.c +++ b/libbech32.c @@ -1,7 +1,50 @@ #define _GNU_SOURCE +#ifdef INCLUDED_FOR_BLECH32 +# define BECH32_CHECKSUM_SIZE BLECH32_CHECKSUM_SIZE +# define BECH32_HRP_MIN_SIZE BLECH32_HRP_MIN_SIZE +# define BECH32_MAX_SIZE BLECH32_MAX_SIZE +# define BECH32_HRP_MAX_SIZE BLECH32_HRP_MAX_SIZE +# define BECH32_MIN_SIZE BLECH32_MIN_SIZE +# define WITNESS_PROGRAM_MIN_SIZE BLINDING_PROGRAM_MIN_SIZE +# define WITNESS_PROGRAM_MAX_SIZE BLINDING_PROGRAM_MAX_SIZE +# define WITNESS_PROGRAM_PKH_SIZE BLINDING_PROGRAM_PKH_SIZE +# define WITNESS_PROGRAM_SH_SIZE BLINDING_PROGRAM_SH_SIZE +# define SEGWIT_ADDRESS_MIN_SIZE BLINDING_ADDRESS_MIN_SIZE +# define polymod blech32_polymod +# define polymod_hrp blech32_polymod_hrp +# define encode blech32_encode +# define decode blech32_decode +#else +# ifndef DISABLE_BLECH32 +# define INCLUDED_FOR_BLECH32 +# include "libbech32.c" +# undef INCLUDED_FOR_BLECH32 +# undef BECH32_H_INCLUDED +# define DISABLE_BLECH32 +# define BECH32_H_SECOND_PASS +# define LIBBECH32_C_SECOND_PASS +# endif +# undef decode +# undef encode +# undef polymod_hrp +# undef polymod +# undef SEGWIT_ADDRESS_MIN_SIZE +# undef WITNESS_PROGRAM_SH_SIZE +# undef WITNESS_PROGRAM_PKH_SIZE +# undef WITNESS_PROGRAM_MAX_SIZE +# undef WITNESS_PROGRAM_MIN_SIZE +# undef BECH32_MIN_SIZE +# undef BECH32_HRP_MAX_SIZE +# undef BECH32_MAX_SIZE +# undef BECH32_HRP_MIN_SIZE +# undef BECH32_CHECKSUM_SIZE +#endif + #include "bech32.h" +#ifndef LIBBECH32_C_SECOND_PASS + #include #include #include @@ -12,7 +55,10 @@ #define _const __attribute__ ((__const__)) #define _pure __attribute__ ((__pure__)) +#endif // !defined(LIBBECH32_C_SECOND_PASS) + +#ifndef INCLUDED_FOR_BLECH32 static inline bech32_checksum_t _const polymod(bech32_checksum_t chk) { static const uint_least32_t LUT[32] = { #define _(i) ( \ @@ -27,6 +73,22 @@ static inline bech32_checksum_t _const polymod(bech32_checksum_t chk) { }; return (chk & UINT32_C(0x1FFFFFF)) << 5 ^ LUT[chk >> 25]; } +#else +static inline blech32_checksum_t _const polymod(blech32_checksum_t chk) { + static const uint_least64_t LUT[32] = { +#define _(i) ( \ + (((i) & 1 << 0) ? UINT64_C(0x7d52fba40bd886) : 0) ^ \ + (((i) & 1 << 1) ? UINT64_C(0x5e8dbf1a03950c) : 0) ^ \ + (((i) & 1 << 2) ? UINT64_C(0x1c3a3c74072a18) : 0) ^ \ + (((i) & 1 << 3) ? UINT64_C(0x385d72fa0e5139) : 0) ^ \ + (((i) & 1 << 4) ? UINT64_C(0x7093e5a608865b) : 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 & UINT64_C(0x7FFFFFFFFFFFFF)) << 5 ^ LUT[chk >> 55]; +} +#endif static inline bech32_checksum_t _pure polymod_hrp(bech32_checksum_t chk, const char *hrp, size_t n_hrp) { for (size_t i = 0; i < n_hrp; ++i) @@ -37,6 +99,7 @@ static inline bech32_checksum_t _pure polymod_hrp(bech32_checksum_t chk, const c return chk; } +#ifndef LIBBECH32_C_SECOND_PASS // 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*/) @@ -51,6 +114,7 @@ static inline bool _pure is_mixed_case(const char *in, size_t n_in) { return (flags & 0x1FFF) && (flags & UINT32_C(0x1FFF0000)); #endif } +#endif // !defined(LIBBECH32_C_SECOND_PASS) size_t bech32_encoded_size(size_t n_hrp, size_t nbits_in, size_t n_pad) { @@ -62,11 +126,13 @@ size_t bech32_encoded_size(size_t n_hrp, size_t nbits_in, size_t n_pad) { return n_out; } +#ifndef LIBBECH32_C_SECOND_PASS +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' +}; +#endif 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) { bech32_checksum_t v = state->bits >> (state->nbits -= 5) & 0x1F; state->chk = polymod(state->chk) ^ v; @@ -127,6 +193,7 @@ enum bech32_error bech32_encode_finish(struct bech32_encoder_state *restrict sta } +#ifndef LIBBECH32_C_SECOND_PASS 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, @@ -134,6 +201,7 @@ static const int8_t DECODE['z' - '0' + 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 }; +#endif static bool decode(struct bech32_decoder_state *restrict state, size_t nbits) { while (state->nbits < nbits) { @@ -207,7 +275,7 @@ ssize_t bech32_decode_finish(struct bech32_decoder_state *restrict state, bech32 } -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) { +ssize_t bech32_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)) @@ -231,7 +299,7 @@ ssize_t segwit_address_encode(char *restrict address, size_t n_address, const un 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) { +ssize_t bech32_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; @@ -259,3 +327,12 @@ ssize_t segwit_address_decode(unsigned char *restrict program, size_t n_program, return ret; return n_actual; } + + +#ifndef LIBBECH32_C_SECOND_PASS +// define weak aliases for ABI backward compatibility +ssize_t segwit_address_encode(char *restrict, size_t, const unsigned char *restrict, size_t, const char *restrict, size_t, unsigned) + __attribute__ ((__weak__, __alias__ ("bech32_address_encode"))); +ssize_t segwit_address_decode(unsigned char *restrict, size_t, const char *restrict, size_t, size_t *restrict, unsigned *restrict) + __attribute__ ((__weak__, __alias__ ("bech32_address_decode"))); +#endif diff --git a/libbech32.pc.in b/libbech32.pc.in index 024b081..5707ebd 100644 --- a/libbech32.pc.in +++ b/libbech32.pc.in @@ -4,7 +4,7 @@ includedir=@includedir@ libdir=@libdir@ Name: libbech32 -Description: Library for Bech32/Bech32m encoding and decoding +Description: Library for Bech32/Bech32m/Blech32/Blech32m encoding/decoding URL: https://github.com/whitslack/libbech32 Version: @VERSION@ Cflags: -I${includedir} diff --git a/libbech32_c++.cpp b/libbech32_c++.cpp index 8b1f073..2812c38 100644 --- a/libbech32_c++.cpp +++ b/libbech32_c++.cpp @@ -1,6 +1,22 @@ +#ifdef INCLUDED_FOR_BLECH32 +# define BECH32_CHECKSUM_SIZE BLECH32_CHECKSUM_SIZE +#else +# ifndef DISABLE_BLECH32 +# define INCLUDED_FOR_BLECH32 +# include "libbech32_c++.cpp" +# undef INCLUDED_FOR_BLECH32 +# undef BECH32_H_INCLUDED +# define DISABLE_BLECH32 +# define BECH32_H_SECOND_PASS +# define LIBBECH32_CXX_CPP_SECOND_PASS +# endif +# undef BECH32_CHECKSUM_SIZE +#endif + #include "bech32.h" +#ifndef LIBBECH32_CXX_CPP_SECOND_PASS static inline const char * __attribute__ ((__const__)) error_to_message(enum ::bech32_error error) { switch (error) { case BECH32_TOO_SHORT: @@ -36,8 +52,10 @@ static inline const char * __attribute__ ((__const__)) error_to_message(enum ::b } std::abort(); // should not be reachable } +#endif +#ifndef LIBBECH32_CXX_CPP_SECOND_PASS namespace bech32 { @@ -45,6 +63,17 @@ Error::Error(enum ::bech32_error error) : std::runtime_error(::error_to_message( } +} // namespace bech32 +#endif // !defined(LIBBECH32_CXX_CPP_SECOND_PASS) + + +#ifdef INCLUDED_FOR_BLECH32 +using namespace bech32; +# define bech32 blech32 +#endif +namespace bech32 { + + void Encoder::reset(std::string_view hrp, size_t nbits_reserve) { out.clear(); out.resize(::bech32_encoded_size(hrp.size(), nbits_reserve, 0)); @@ -105,7 +134,7 @@ size_t Decoder::finish(bech32_constant_t constant) { 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) + if (auto ret = ::bech32_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)); @@ -117,7 +146,7 @@ std::tuple, std::string_view, unsigned> decode_segwit_add auto &[program, hrp, version] = ret; program.resize((address.size() - 1/*hrp*/ - 1/*separator*/ - BECH32_CHECKSUM_SIZE) * 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) + if (auto ret = ::bech32_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)); @@ -127,3 +156,4 @@ std::tuple, std::string_view, unsigned> decode_segwit_add } // namespace bech32 +#undef bech32