a collection of lightweight TypeScript packages for AT Protocol, the protocol powering Bluesky
atproto bluesky typescript npm
101
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(multibase): native base58 encode/decode

Mary 76aa79dd d1c6c1e1

+581 -22
+5
.changeset/early-spoons-dream.md
··· 1 + --- 2 + "@atcute/multibase": minor 3 + --- 4 + 5 + native base58 encode/decode
+1
packages/utilities/multibase/.gitignore
··· 1 1 /coverage/ 2 + prebuilds/
+75
packages/utilities/multibase/build.native.js
··· 1 + import { execSync } from 'node:child_process'; 2 + import { existsSync, mkdirSync } from 'node:fs'; 3 + import { join } from 'node:path'; 4 + 5 + import { build } from 'zig-build'; 6 + 7 + const nodeVersion = process.version.slice(1); 8 + const zigBuildDir = join(process.env.HOME || process.env.USERPROFILE, '.zig-build'); 9 + 10 + // zig-build only downloads headers, but Windows linking requires node.lib 11 + const nodeLibDir = join(zigBuildDir, 'node', `v${nodeVersion}`, 'lib'); 12 + if (!existsSync(join(nodeLibDir, 'node.lib'))) { 13 + mkdirSync(nodeLibDir, { recursive: true }); 14 + 15 + const url = `https://nodejs.org/download/release/v${nodeVersion}/win-x64/node.lib`; 16 + execSync(`curl -sL "${url}" -o "${join(nodeLibDir, 'node.lib')}"`); 17 + } 18 + 19 + const shared = { 20 + sources: ['src/base58.c'], 21 + napiVersion: 1, 22 + cflags: ['-Wall', '-Wextra'], 23 + mode: 'fast', 24 + }; 25 + 26 + // ensure output directories exist 27 + for (const dir of [ 28 + 'linux-x64-glibc', 29 + 'linux-x64-musl', 30 + 'linux-arm64-glibc', 31 + 'linux-arm64-musl', 32 + 'darwin-arm64', 33 + 'win32-x64', 34 + ]) { 35 + mkdirSync(join('prebuilds', dir), { recursive: true }); 36 + } 37 + 38 + await build({ 39 + 'linux-x64-glibc': { 40 + ...shared, 41 + target: 'x86_64-linux-gnu', 42 + output: 'prebuilds/linux-x64-glibc/base58.node', 43 + glibc: '2.17', 44 + }, 45 + 'linux-x64-musl': { 46 + ...shared, 47 + target: 'x86_64-linux-musl', 48 + output: 'prebuilds/linux-x64-musl/base58.node', 49 + }, 50 + 'linux-arm64-glibc': { 51 + ...shared, 52 + target: 'aarch64-linux-gnu', 53 + output: 'prebuilds/linux-arm64-glibc/base58.node', 54 + glibc: '2.17', 55 + }, 56 + 'linux-arm64-musl': { 57 + ...shared, 58 + target: 'aarch64-linux-musl', 59 + output: 'prebuilds/linux-arm64-musl/base58.node', 60 + }, 61 + 'darwin-arm64': { 62 + ...shared, 63 + target: 'aarch64-macos', 64 + output: 'prebuilds/darwin-arm64/base58.node', 65 + cflags: [...shared.cflags, '-Wl,-undefined,dynamic_lookup'], 66 + }, 67 + 'win32-x64': { 68 + ...shared, 69 + target: 'x86_64-windows', 70 + output: 'prebuilds/win32-x64/base58.node', 71 + libraries: ['node'], 72 + librariesSearch: [nodeLibDir], 73 + mode: 'small', 74 + }, 75 + });
+61 -18
packages/utilities/multibase/lib/bases/base58.bench.ts
··· 1 1 import { bench, do_not_optimize, run, summary } from 'mitata'; 2 2 3 + import { fromBase58Btc as fromBase58BtcNode, toBase58Btc as toBase58BtcNode } from './base58.node.ts'; 3 4 import { fromBase58Btc, toBase58Btc } from './base58.ts'; 4 5 5 - summary(() => { 6 - bench('fromBase58Btc', () => { 7 - return do_not_optimize(fromBase58Btc(`UXE7GvtEk8XTXs1GF8HSGbVA9FCX9SEBPe`)); 6 + const cases = [ 7 + { 8 + label: 'secp256k1 private multikey payload', 9 + values: Array.from({ length: 34 }, (_, idx) => (idx * 37 + 11) & 0xff), 10 + }, 11 + { 12 + label: 'secp256k1 public multikey payload', 13 + values: Array.from({ length: 35 }, (_, idx) => (idx * 53 + 7) & 0xff), 14 + }, 15 + ]; 16 + 17 + for (const item of cases) { 18 + const bytes = Uint8Array.from(item.values); 19 + const encoded = toBase58Btc(bytes); 20 + 21 + summary(() => { 22 + bench(`fromBase58Btc js ${item.label}`, function* () { 23 + yield { 24 + [0]() { 25 + return encoded; 26 + }, 27 + bench(encoded: string) { 28 + return do_not_optimize(fromBase58Btc(encoded)); 29 + }, 30 + }; 31 + }); 32 + 33 + bench(`fromBase58Btc node ${item.label}`, function* () { 34 + yield { 35 + [0]() { 36 + return encoded; 37 + }, 38 + bench(encoded: string) { 39 + return do_not_optimize(fromBase58BtcNode(encoded)); 40 + }, 41 + }; 42 + }); 8 43 }); 9 - }); 10 44 11 - summary(() => { 12 - bench('toBase58Btc', function* () { 13 - yield { 14 - [0]() { 15 - return Uint8Array.from([ 16 - 68, 101, 99, 101, 110, 116, 114, 97, 108, 105, 122, 101, 32, 101, 118, 101, 114, 121, 116, 104, 105, 17 - 110, 103, 33, 33, 18 - ]); 19 - }, 20 - bench(bytes: Uint8Array) { 21 - return do_not_optimize(toBase58Btc(bytes)); 22 - }, 23 - }; 45 + summary(() => { 46 + bench(`toBase58Btc js ${item.label}`, function* () { 47 + yield { 48 + [0]() { 49 + return Uint8Array.from(item.values); 50 + }, 51 + bench(bytes: Uint8Array) { 52 + return do_not_optimize(toBase58Btc(bytes)); 53 + }, 54 + }; 55 + }); 56 + 57 + bench(`toBase58Btc node ${item.label}`, function* () { 58 + yield { 59 + [0]() { 60 + return Uint8Array.from(item.values); 61 + }, 62 + bench(bytes: Uint8Array) { 63 + return do_not_optimize(toBase58BtcNode(bytes)); 64 + }, 65 + }; 66 + }); 24 67 }); 25 - }); 68 + } 26 69 27 70 await run();
+54
packages/utilities/multibase/lib/bases/base58.node.ts
··· 1 + import { readFileSync } from 'node:fs'; 2 + import { createRequire } from 'node:module'; 3 + import { arch, platform } from 'node:process'; 4 + 5 + import { createBtcBaseDecode, createBtcBaseEncode } from '../utils.ts'; 6 + 7 + const BASE58BTC_CHARSET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; 8 + 9 + type Base58Binding = { 10 + encode: (source: Uint8Array) => string; 11 + decode: (source: string) => Uint8Array<ArrayBuffer>; 12 + }; 13 + 14 + /** 15 + * whether the native base58 module is available for the current runtime. 16 + */ 17 + export let hasNative = false; 18 + 19 + /** 20 + * decodes a base58btc string to a Uint8Array 21 + * @param source base58btc encoded string 22 + * @returns decoded buffer 23 + */ 24 + export let fromBase58Btc: (source: string) => Uint8Array<ArrayBuffer> = 25 + /*#__PURE__*/ createBtcBaseDecode(BASE58BTC_CHARSET); 26 + 27 + /** 28 + * encodes a Uint8Array to a base58btc string 29 + * @param source source buffer 30 + * @returns base58btc encoded string 31 + */ 32 + export let toBase58Btc: (source: Uint8Array) => string = /*#__PURE__*/ createBtcBaseEncode(BASE58BTC_CHARSET); 33 + 34 + try { 35 + const getPrebuildDir = (): string => { 36 + if (platform === 'linux') { 37 + const ldd = readFileSync('/usr/bin/ldd', 'utf-8'); 38 + const libc = ldd.includes('musl') ? 'musl' : ldd.includes('GNU C Library') ? 'glibc' : null; 39 + if (libc === null) { 40 + throw new Error(`unable to detect libc`); 41 + } 42 + return `${platform}-${arch}-${libc}`; 43 + } 44 + return `${platform}-${arch}`; 45 + }; 46 + 47 + const require = createRequire(import.meta.url); 48 + const binding: Base58Binding = require(`../../prebuilds/${getPrebuildDir()}/base58.node`); 49 + 50 + fromBase58Btc = binding.decode; 51 + toBase58Btc = binding.encode; 52 + 53 + hasNative = true; 54 + } catch {}
+20 -1
packages/utilities/multibase/lib/bases/base58.test.ts
··· 1 - import { expect, it, vi } from 'vitest'; 1 + import { describe, expect, it, vi } from 'vitest'; 2 2 3 + import { 4 + fromBase58Btc as fromBase58BtcNode, 5 + hasNative, 6 + toBase58Btc as toBase58BtcNode, 7 + } from './base58.node.ts'; 3 8 import { fromBase58Btc, toBase58Btc } from './base58.ts'; 4 9 5 10 vi.mock('@atcute/uint8array', async (importOriginal) => { ··· 49 54 expect(fromBase58Btc(encoded)).toEqual(buffer); 50 55 } 51 56 }); 57 + 58 + describe.skipIf(!hasNative)('native', () => { 59 + it('encode matches', () => { 60 + for (const { buffer, encoded } of inputs) { 61 + expect(toBase58BtcNode(buffer)).toEqual(encoded); 62 + } 63 + }); 64 + 65 + it('decode matches', () => { 66 + for (const { buffer, encoded } of inputs) { 67 + expect(Uint8Array.from(fromBase58BtcNode(encoded))).toEqual(buffer); 68 + } 69 + }); 70 + });
+1 -1
packages/utilities/multibase/lib/index.ts
··· 11 11 } from '#bases/base64'; 12 12 13 13 export { fromBase32, toBase32 } from './bases/base32.ts'; 14 - export { fromBase58Btc, toBase58Btc } from './bases/base58.ts'; 14 + export { fromBase58Btc, toBase58Btc } from '#bases/base58';
+10 -2
packages/utilities/multibase/package.json
··· 10 10 "files": [ 11 11 "dist/", 12 12 "lib/", 13 + "prebuilds/**/*.node", 14 + "src/", 13 15 "!lib/**/*.bench.ts", 14 16 "!lib/**/*.test.ts" 15 17 ], ··· 25 27 "#bases/base32-encode": { 26 28 "bun": "./dist/bases/base32-encode.bun.js", 27 29 "default": "./dist/bases/base32-encode.js" 30 + }, 31 + "#bases/base58": { 32 + "node": "./dist/bases/base58.node.js", 33 + "default": "./dist/bases/base58.js" 28 34 }, 29 35 "#bases/base64": { 30 36 "bun": "./dist/bases/base64-web.js", ··· 40 46 "access": "public" 41 47 }, 42 48 "scripts": { 49 + "build:native": "node build.native.js", 43 50 "build": "tsgo --project tsconfig.build.json", 44 51 "test": "vitest", 45 - "prepublish": "rm -rf dist; pnpm run build" 52 + "prepublish": "rm -rf dist; pnpm run build:native; pnpm run build" 46 53 }, 47 54 "dependencies": { 48 55 "@atcute/uint8array": "workspace:^" ··· 50 57 "devDependencies": { 51 58 "@types/node": "^25.4.0", 52 59 "@vitest/coverage-v8": "^4.0.18", 53 - "vitest": "^4.0.18" 60 + "vitest": "^4.0.18", 61 + "zig-build": "^0.3.0" 54 62 } 55 63 }
+354
packages/utilities/multibase/src/base58.c
··· 1 + #include <node_api.h> 2 + #include <stdint.h> 3 + #include <stdlib.h> 4 + #include <string.h> 5 + 6 + static const char BASE58BTC_CHARSET[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; 7 + 8 + // inverse lookup: ASCII code -> base58 value, 0xff = invalid 9 + static const uint8_t BASE58BTC_MAP[128] = { 10 + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 11 + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 12 + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 13 + 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 14 + 0xff, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0xff, 0x11, 0x12, 0x13, 0x14, 0x15, 0xff, 15 + 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 16 + 0xff, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0xff, 0x2c, 0x2d, 0x2e, 17 + 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0xff, 0xff, 0xff, 0xff, 0xff, 18 + }; 19 + 20 + #define BASE 58 21 + #define BASE2 (58 * 58) 22 + #define BASE3 (58 * 58 * 58) 23 + 24 + // stack buffer threshold — covers multikey strings (~48 chars) and 25 + // typical base58 inputs without hitting the allocator 26 + #define STACK_STR_MAX 128 27 + #define STACK_BUF_MAX 96 28 + 29 + static void finalize_external_bytes(napi_env env, void *data, void *hint) { 30 + (void)env; 31 + (void)hint; 32 + free(data); 33 + } 34 + 35 + // encode: Uint8Array -> string 36 + static napi_value base58_encode(napi_env env, napi_callback_info info) { 37 + size_t argc = 1; 38 + napi_value argv[1]; 39 + napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 40 + 41 + uint8_t *source; 42 + size_t source_len; 43 + napi_get_typedarray_info(env, argv[0], NULL, &source_len, (void **)&source, NULL, NULL); 44 + 45 + if (source_len == 0) { 46 + napi_value result; 47 + napi_create_string_utf8(env, "", 0, &result); 48 + return result; 49 + } 50 + 51 + // count leading zeroes 52 + size_t zeroes = 0; 53 + size_t pbegin = 0; 54 + while (pbegin < source_len && source[pbegin] == 0) { 55 + pbegin++; 56 + zeroes++; 57 + } 58 + 59 + // allocate output in base-N representation 60 + size_t data_len = source_len - pbegin; 61 + size_t size = (size_t)(data_len * 138 / 100) + 1; 62 + 63 + uint8_t stack_bN[STACK_BUF_MAX]; 64 + uint8_t *bN; 65 + int bN_heap = 0; 66 + 67 + if (size <= STACK_BUF_MAX) { 68 + bN = stack_bN; 69 + memset(bN, 0, size); 70 + } else { 71 + bN = (uint8_t *)calloc(size, 1); 72 + if (!bN) { 73 + napi_throw_error(env, NULL, "allocation failed"); 74 + return NULL; 75 + } 76 + bN_heap = 1; 77 + } 78 + 79 + size_t length = 0; 80 + 81 + // process 3 bytes at a time where possible 82 + { 83 + size_t rem = data_len % 3; 84 + size_t triple_end = source_len - rem; 85 + 86 + while (pbegin < triple_end) { 87 + uint32_t carry = ((uint32_t)source[pbegin] << 16) | 88 + ((uint32_t)source[pbegin + 1] << 8) | 89 + (uint32_t)source[pbegin + 2]; 90 + 91 + size_t i = 0; 92 + for (size_t it = size; (carry != 0 || i < length) && it > 0; it--, i++) { 93 + carry += (uint32_t)16777216 * bN[it - 1]; 94 + bN[it - 1] = carry % BASE; 95 + carry /= BASE; 96 + } 97 + 98 + length = i; 99 + pbegin += 3; 100 + } 101 + } 102 + 103 + // remaining 0-2 bytes 104 + while (pbegin < source_len) { 105 + uint32_t carry = source[pbegin]; 106 + 107 + size_t i = 0; 108 + for (size_t it = size; (carry != 0 || i < length) && it > 0; it--, i++) { 109 + carry += 256u * bN[it - 1]; 110 + bN[it - 1] = carry % BASE; 111 + carry /= BASE; 112 + } 113 + 114 + length = i; 115 + pbegin++; 116 + } 117 + 118 + // skip leading zeroes in bN 119 + size_t it = size - length; 120 + while (it < size && bN[it] == 0) { 121 + it++; 122 + } 123 + 124 + // build output string 125 + size_t out_len = zeroes + (size - it); 126 + 127 + char stack_out[STACK_STR_MAX]; 128 + char *out; 129 + int out_heap = 0; 130 + 131 + if (out_len < STACK_STR_MAX) { 132 + out = stack_out; 133 + } else { 134 + out = (char *)malloc(out_len + 1); 135 + if (!out) { 136 + if (bN_heap) free(bN); 137 + napi_throw_error(env, NULL, "allocation failed"); 138 + return NULL; 139 + } 140 + out_heap = 1; 141 + } 142 + 143 + memset(out, BASE58BTC_CHARSET[0], zeroes); 144 + for (size_t j = zeroes; it < size; it++, j++) { 145 + out[j] = BASE58BTC_CHARSET[bN[it]]; 146 + } 147 + 148 + if (bN_heap) free(bN); 149 + 150 + napi_value result; 151 + napi_create_string_utf8(env, out, out_len, &result); 152 + 153 + if (out_heap) free(out); 154 + return result; 155 + } 156 + 157 + // decode: string -> Uint8Array 158 + static napi_value base58_decode(napi_env env, napi_callback_info info) { 159 + size_t argc = 1; 160 + napi_value argv[1]; 161 + napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 162 + 163 + // single call: read string directly into a stack buffer if it fits 164 + char stack_str[STACK_STR_MAX]; 165 + char *str; 166 + int str_heap = 0; 167 + size_t str_len; 168 + 169 + napi_get_value_string_latin1(env, argv[0], NULL, 0, &str_len); 170 + 171 + if (str_len == 0) { 172 + napi_value ab, result; 173 + napi_create_arraybuffer(env, 0, NULL, &ab); 174 + napi_create_typedarray(env, napi_uint8_array, 0, ab, 0, &result); 175 + return result; 176 + } 177 + 178 + if (str_len < STACK_STR_MAX) { 179 + str = stack_str; 180 + } else { 181 + str = (char *)malloc(str_len + 1); 182 + if (!str) { 183 + napi_throw_error(env, NULL, "allocation failed"); 184 + return NULL; 185 + } 186 + str_heap = 1; 187 + } 188 + napi_get_value_string_latin1(env, argv[0], str, str_len + 1, &str_len); 189 + 190 + // count and skip leading '1's (leader character) 191 + size_t psz = 0; 192 + size_t zeroes = 0; 193 + while (psz < str_len && str[psz] == '1') { 194 + zeroes++; 195 + psz++; 196 + } 197 + 198 + // allocate output in base256 representation 199 + size_t remaining = str_len - psz; 200 + size_t size = (size_t)(remaining * 733 / 1000) + 1; 201 + 202 + uint8_t stack_b256[STACK_BUF_MAX]; 203 + uint8_t *b256; 204 + int b256_heap = 0; 205 + 206 + if (size <= STACK_BUF_MAX) { 207 + b256 = stack_b256; 208 + memset(b256, 0, size); 209 + } else { 210 + b256 = (uint8_t *)calloc(size, 1); 211 + if (!b256) { 212 + if (str_heap) free(str); 213 + napi_throw_error(env, NULL, "allocation failed"); 214 + return NULL; 215 + } 216 + b256_heap = 1; 217 + } 218 + 219 + size_t length = 0; 220 + 221 + // process 3 chars at a time where possible 222 + // BASE^3 = 195112, max carry = 195112 * 255 + 195111 = 49948771, fits uint32 223 + { 224 + size_t rem = remaining % 3; 225 + size_t triple_end = str_len - rem; 226 + 227 + while (psz < triple_end) { 228 + uint8_t r0 = (uint8_t)str[psz]; 229 + uint8_t r1 = (uint8_t)str[psz + 1]; 230 + uint8_t r2 = (uint8_t)str[psz + 2]; 231 + 232 + if ((r0 | r1 | r2) & 0x80) { 233 + goto invalid; 234 + } 235 + 236 + uint8_t c0 = BASE58BTC_MAP[r0]; 237 + uint8_t c1 = BASE58BTC_MAP[r1]; 238 + uint8_t c2 = BASE58BTC_MAP[r2]; 239 + 240 + if (c0 == 0xff || c1 == 0xff || c2 == 0xff) { 241 + goto invalid; 242 + } 243 + 244 + uint32_t carry = ((uint32_t)c0 * BASE + c1) * BASE + c2; 245 + 246 + size_t i = 0; 247 + for (size_t it = size; (carry != 0 || i < length) && it > 0; it--, i++) { 248 + carry += (uint32_t)BASE3 * b256[it - 1]; 249 + b256[it - 1] = carry & 0xff; 250 + carry >>= 8; 251 + } 252 + 253 + length = i; 254 + psz += 3; 255 + } 256 + } 257 + 258 + // remaining 1-2 characters 259 + if (remaining % 3 >= 2) { 260 + uint8_t r0 = (uint8_t)str[psz]; 261 + uint8_t r1 = (uint8_t)str[psz + 1]; 262 + 263 + if ((r0 | r1) & 0x80) { 264 + goto invalid; 265 + } 266 + 267 + uint8_t c0 = BASE58BTC_MAP[r0]; 268 + uint8_t c1 = BASE58BTC_MAP[r1]; 269 + 270 + if (c0 == 0xff || c1 == 0xff) { 271 + goto invalid; 272 + } 273 + 274 + uint32_t carry = (uint32_t)c0 * BASE + c1; 275 + 276 + size_t i = 0; 277 + for (size_t it = size; (carry != 0 || i < length) && it > 0; it--, i++) { 278 + carry += (uint32_t)BASE2 * b256[it - 1]; 279 + b256[it - 1] = carry & 0xff; 280 + carry >>= 8; 281 + } 282 + 283 + length = i; 284 + psz += 2; 285 + } 286 + 287 + if (psz < str_len) { 288 + uint8_t r = (uint8_t)str[psz]; 289 + 290 + if (r & 0x80) { 291 + goto invalid; 292 + } 293 + 294 + uint8_t c = BASE58BTC_MAP[r]; 295 + if (c == 0xff) { 296 + goto invalid; 297 + } 298 + 299 + uint32_t carry = c; 300 + size_t i = 0; 301 + for (size_t it = size; (carry != 0 || i < length) && it > 0; it--, i++) { 302 + carry += (uint32_t)BASE * b256[it - 1]; 303 + b256[it - 1] = carry & 0xff; 304 + carry >>= 8; 305 + } 306 + 307 + length = i; 308 + } 309 + 310 + if (str_heap) free(str); 311 + 312 + { 313 + // skip leading zeroes in b256 314 + size_t it = size - length; 315 + while (it < size && b256[it] == 0) { 316 + it++; 317 + } 318 + 319 + // build output 320 + size_t out_len = zeroes + (size - it); 321 + uint8_t *out = (uint8_t *)malloc(out_len); 322 + if (!out) { 323 + if (b256_heap) free(b256); 324 + napi_throw_error(env, NULL, "allocation failed"); 325 + return NULL; 326 + } 327 + 328 + memset(out, 0, zeroes); 329 + memcpy(out + zeroes, b256 + it, size - it); 330 + 331 + if (b256_heap) free(b256); 332 + 333 + napi_value result; 334 + napi_create_external_buffer(env, out_len, (char *)out, finalize_external_bytes, NULL, &result); 335 + return result; 336 + } 337 + 338 + invalid: 339 + if (str_heap) free(str); 340 + if (b256_heap) free(b256); 341 + napi_throw_error(env, NULL, "invalid string"); 342 + return NULL; 343 + } 344 + 345 + static napi_value init(napi_env env, napi_value exports) { 346 + napi_property_descriptor descs[] = { 347 + { "encode", NULL, base58_encode, NULL, NULL, NULL, napi_default, NULL }, 348 + { "decode", NULL, base58_decode, NULL, NULL, NULL, napi_default, NULL }, 349 + }; 350 + napi_define_properties(env, exports, 2, descs); 351 + return exports; 352 + } 353 + 354 + NAPI_MODULE(NODE_GYP_MODULE_NAME, init)