Open luke-jr opened 3 months ago
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:
Advantages of libbase58check over libbase58:
-march=native -O3
). (See benchmarks below.)sysexits.h
) exit codes.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;
}
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
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.
Why not just use (improve?) libbase58?