UNPKG

safex-base58

Version:

CryptoNote-style chunked Base-58 encoder/decoder for Safex addresses

93 lines (85 loc) 3.06 kB
/*! * safex-base58 * CryptoNote-style Base-58 encoder/decoder & var-int helpers * SPDX-License-Identifier: MIT * Copyright © 2025 Daniel Dabek <github.com/safex> */ const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; const MAP = Object.fromEntries([...ALPHABET].map((c, i) => [c, i])); const FULL_BLOCK_SIZE = 8; // bytes const FULL_ENC_SIZE = 11; // chars const ENC_SIZES = [0, 2, 3, 5, 6, 7, 9, 10]; // tail-block encode sizes /** Little-endian 64-bit → base-58 string of fixed length */ function encodeBlock(data, size) { let num = 0n; for (let i = size - 1; i >= 0; i--) num = (num << 8n) | BigInt(data[i]); const outSize = size === FULL_BLOCK_SIZE ? FULL_ENC_SIZE : ENC_SIZES[size]; const out = Array(outSize).fill('1'); for (let k = outSize - 1; k >= 0; k--) { const div = num / 58n; const rem = Number(num % 58n); out[k] = ALPHABET[rem]; num = div; } return out.join(''); } /** Base-58 chunked (CryptoNote) encoder. Accepts Buffer or hex string. */ export function base58Encode(input) { const buf = Buffer.isBuffer(input) ? input : Buffer.from(input, 'hex'); let res = ''; for (let i = 0; i < buf.length; i += FULL_BLOCK_SIZE) { const blk = buf.subarray(i, i + FULL_BLOCK_SIZE); res += encodeBlock(blk, blk.length); } return res; } /** Decode a CryptoNote base-58 string → Buffer. Throws on malformed input. */ export function base58Decode(str) { const chunks = []; let i = 0; while (i < str.length) { const rem = str.length - i; const encSize = rem >= FULL_ENC_SIZE ? FULL_ENC_SIZE : rem; const blkSize = encSize === FULL_ENC_SIZE ? FULL_BLOCK_SIZE : ENC_SIZES.indexOf(encSize); if (blkSize <= 0) throw new Error('Invalid block length in CN Base58'); let num = 0n; for (let k = 0; k < encSize; k++) { const char = str[i + k]; const val = MAP[char]; if (val === undefined) throw new Error('Invalid Base58 char'); num = num * 58n + BigInt(val); } const blk = Buffer.alloc(blkSize); for (let j = 0; j < blkSize; j++) { blk[j] = Number(num & 0xffn); num >>= 8n; } chunks.push(blk); i += encSize; } return Buffer.concat(chunks); } /** CryptoNote var-int encoder (little-endian 7-bit groups) → Buffer */ export function encodeVarint(n) { const out = []; let num = n >>> 0; while (num >= 0x80) { out.push((num & 0x7f) | 0x80); num >>= 7; } out.push(num); return Buffer.from(out); } /** Decoder counterpart. Returns {value, bytesRead}. */ export function decodeVarint(buf, offset = 0) { let res = 0; let shift = 0; let i = offset; while (i < buf.length) { const byte = buf[i]; res |= (byte & 0x7f) << shift; if (!(byte & 0x80)) break; shift += 7; i++; } return { value: res >>> 0, bytesRead: i - offset + 1 }; }