Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redundant? #1

Open
luke-jr opened this issue Aug 5, 2024 · 3 comments
Open

Redundant? #1

luke-jr opened this issue Aug 5, 2024 · 3 comments

Comments

@luke-jr
Copy link

luke-jr commented Aug 5, 2024

Why not just use (improve?) libbase58?

@whitslack
Copy link
Owner

whitslack commented Aug 5, 2024

I think either I was not aware of the existence of libbase58 (given that Bitcoin Core does not use it), or I did not realize that libbase58 actually implements Base58Check encoding. (The latter would, of course, be a case of inexcusable ignorance, given that the README is very clear, now that I'm looking at it.)

In any case, both projects appear to be complete, so it could just as easily be argued that libbase58 has been made redundant by libbase58check. 😉 I see that you are the author of libbase58. I assure you, I honestly did not intend to imply any opinion about your work by implementing my own. I had never looked at yours until now.

After a cursory inspection of libbase58, here is my attempt to compare the feature sets of the two libraries…

Advantages of libbase58 over libbase58check:

  • Supports unchecked base58 encodings.
  • Supports (and mandates) a user-defined SHA256 function (the specification of which is not thread-safe).

Advantages of libbase58check over libbase58:

  • Approximately 74% faster at encoding and 34% faster at decoding (on an AMD Ryzen 9 7940HS, both libraries compiled with -march=native -O3). (See benchmarks below.)
  • Supports (but does not mandate) dynamic allocation of output buffers using a user-supplied allocation function (the specification of which is not thread-safe).
  • Does not require implementing a user-defined SHA256 function (but also does not support one). (Only supports OpenSSL's implementation.)
  • Calling program is not responsible for stripping the checksum from the decoded data.
  • Header file is thoroughly documented using Doxygen/Javadoc syntax.
  • Header file declares library functions with function attributes to check the directionality and nullness of arguments.
  • Header file defines C++20 bindings when compiled as C++.
  • Command-line utility supports optional hexadecimal input/output of payload data.
  • Command-line utility supports long options in addition to short options.
  • Command-line utility does not require specifying the length of the data to be decoded.
  • Command-line utility returns standardized (sysexits.h) exit codes.
  • Command-line utility comes with a man page.
  • Build system optionally generates man pages for the library functions from the sources using Doxygen.

libbase58check is "licensed" under the WTFPL, so please feel free to take and use anything you like from it. 😉


I actually cannot figure out how to make libbase58's command-line utility decode a Base58Check encoding:

$ <<<'Hello world!' ./base58 -c
gTazoqFi2U9CKLR6zpJ8CEV

# By the way, specifying a decode size of 0 actually encodes! (Awkward.)
$ <<<'Hello world!' ./base58 -c -d 0
gTazoqFi2U9CKLR6zpJ8CEV

# Try to decode 13 bytes:
$ <<<gTazoqFi2U9CKLR6zpJ8CEV ./base58 -c -d 13 || echo "exit $?"
exit 2

For comparison, libbase58check's command-line utility works fine:

$ <<<'Hello world!' ./base58check
gTazoqFi2U9CKLR6zpJ8CEV

$ <<<gTazoqFi2U9CKLR6zpJ8CEV ./base58check -d || echo "exit $?"
Hello world!

Incidentally, libbase58.h is missing an #include <stdint.h>.


Here are the benchmark figures to support my speed claims above:

1,000,000 iterations...

      libbase58 encode:   1,625,601,825 ns
 libbase58check encode:     423,627,656 ns
      libbase58 decode:     513,080,113 ns
 libbase58check decode:     335,650,742 ns

      libbase58 encode:   1,631,959,142 ns
 libbase58check encode:     424,356,235 ns
      libbase58 decode:     513,587,701 ns
 libbase58check decode:     337,514,703 ns

And here is the program I used to produce those benchmarks:

#include <err.h>
#include <locale.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <time.h>

#include <base58check.h>
#include <libbase58.h>
#include <openssl/sha.h>

#define ITERATIONS 1000000

static bool my_sha256(void *digest, const void *data, size_t datasz) {
	return !!SHA256(data, datasz, digest);
}

static void benchmark_libbase58_encode() {
	unsigned char data[32];
	for (size_t i = 0; i < sizeof data; ++i) data[i] = (unsigned char) i;

	struct timespec start, stop;
	clock_gettime(CLOCK_MONOTONIC, &start);

	for (unsigned i = 0; i < ITERATIONS; ++i) {
		char b58c[64];
		size_t b58c_sz = sizeof b58c;
		if (!b58check_enc(b58c, &b58c_sz, data[0], data + 1, sizeof data - 1)) {
			errx(EX_SOFTWARE, "b58check_enc failed");
		}
	}

	clock_gettime(CLOCK_MONOTONIC, &stop);
	uint64_t ns = (stop.tv_sec - start.tv_sec) * 1000000000 + (stop.tv_nsec - start.tv_nsec);
	printf("%15s %s: %'15" PRIu64 " ns\n", "libbase58", "encode", ns);
}

static void benchmark_libbase58check_encode() {
	unsigned char data[32];
	for (size_t i = 0; i < sizeof data; ++i) data[i] = (unsigned char) i;

	struct timespec start, stop;
	clock_gettime(CLOCK_MONOTONIC, &start);

	for (unsigned i = 0; i < ITERATIONS; ++i) {
		char out[64], *out_ptr = out;
		size_t n_out = sizeof out;
		if (base58check_encode(&out_ptr, &n_out, data, sizeof data, 0) < 0) {
			errx(EX_SOFTWARE, "base58check_encode failed");
		}
	}

	clock_gettime(CLOCK_MONOTONIC, &stop);
	uint64_t ns = (stop.tv_sec - start.tv_sec) * 1000000000 + (stop.tv_nsec - start.tv_nsec);
	printf("%15s %s: %'15" PRIu64 " ns\n", "libbase58check", "encode", ns);
}

static void benchmark_libbase58_decode() {
	const char *b58 = "16qJFWMMHFy3xDdLmvUeyc2S6FrWRhJP51HsvDYdz9d1FsYG";
	size_t b58sz = strlen(b58);

	struct timespec start, stop;
	clock_gettime(CLOCK_MONOTONIC, &start);

	for (unsigned i = 0; i < ITERATIONS; ++i) {
		unsigned char bin[36];
		size_t binsz = sizeof bin;
		if (!b58tobin(bin, &binsz, b58, b58sz)) {
			errx(EX_SOFTWARE, "b58tobin failed");
		}
		if (b58check(bin, binsz, b58, b58sz) < 0) {
			errx(EX_SOFTWARE, "b58check failed");
		}
	}

	clock_gettime(CLOCK_MONOTONIC, &stop);
	uint64_t ns = (stop.tv_sec - start.tv_sec) * 1000000000 + (stop.tv_nsec - start.tv_nsec);
	printf("%15s %s: %'15" PRIu64 " ns\n", "libbase58", "decode", ns);
}

static void benchmark_libbase58check_decode() {
	const char *in = "16qJFWMMHFy3xDdLmvUeyc2S6FrWRhJP51HsvDYdz9d1FsYG";
	size_t n_in = strlen(in);

	struct timespec start, stop;
	clock_gettime(CLOCK_MONOTONIC, &start);

	for (unsigned i = 0; i < ITERATIONS; ++i) {
		unsigned char out[36], *out_ptr = out;
		size_t n_out = sizeof out;
		if (base58check_decode(&out_ptr, &n_out, in, n_in, 0) < 0) {
			errx(EX_SOFTWARE, "base58check_decode failed");
		}
	}

	clock_gettime(CLOCK_MONOTONIC, &stop);
	uint64_t ns = (stop.tv_sec - start.tv_sec) * 1000000000 + (stop.tv_nsec - start.tv_nsec);
	printf("%15s %s: %'15" PRIu64 " ns\n", "libbase58check", "decode", ns);
}

int main() {
	setlocale(LC_NUMERIC, "");
	b58_sha256_impl = my_sha256;

	printf("%'u iterations...\n", ITERATIONS);
	for (int i = 0; i < 2; ++i) {
		putchar('\n');
		benchmark_libbase58_encode();
		benchmark_libbase58check_encode();
		benchmark_libbase58_decode();
		benchmark_libbase58check_decode();
	}
	return 0;
}

@luke-jr
Copy link
Author

luke-jr commented Aug 5, 2024

I'd also add that libbase58 has fewer dependencies (I am surprised a GMP implementation performs better).

OpenSSL in particular is not a GPL-compatible dependency. And WTFPL is non-free since it requires you to act on impulse ;p

@whitslack
Copy link
Owner

whitslack commented Aug 5, 2024

There are smart ways to use GMP and dumb ways. I use only its low-level functions, which really can't be beaten except by inlining the assembly, but that would give up too much maintainability for very little gain in performance.

OpenSSL is licensed under Apache 2.0, which is quite permissive. I don't believe I am violating the terms of that license, am I?

GPL is non-free since it is copyleft/infectious. I usually avoid making use of GPL code for that reason. LGPL is as far as I'll go in that direction.

And WTFPL is non-free since it requires you to act on impulse ;p

I did laugh at this. Interesting argument. Maybe I should switch back to CC0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants