Base32 / Base58 / Base62 / Base85 Encoder — TypeScript source
Encode text to Base32, Base58, Base62, or Ascii85 - or decode it back. UTF-8 safe, runs entirely in your browser, with a shareable link to your exact input.
This is the TypeScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
// Byte-array base encoders: Base32 (RFC 4648), Base58 (Bitcoin), Base62,
// and Base85 (Ascii85). All four operate on the UTF-8 bytes of the input text.
// Pure + deterministic, zero npm deps - the unit-test surface for the
// Base Encoder tool.
export type Scheme = 'base32' | 'base58' | 'base62' | 'base85';
// ---------------------------------------------------------------------------
// Base32 - RFC 4648 alphabet, padded to a multiple of 8 chars with '='.
// ---------------------------------------------------------------------------
const B32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
function encode32(bytes: Uint8Array): string {
let out = '';
for (let i = 0; i < bytes.length; i += 5) {
const chunk = bytes.subarray(i, i + 5);
const b = [0, 0, 0, 0, 0];
for (let j = 0; j < chunk.length; j++) b[j] = chunk[j];
// Pack 5 bytes (40 bits) into 8 base32 digits (5 bits each, big-endian).
const digits = [
(b[0] >> 3) & 0x1f,
((b[0] << 2) & 0x1f) | ((b[1] >> 6) & 0x1f),
(b[1] >> 1) & 0x1f,
((b[1] << 4) & 0x1f) | ((b[2] >> 4) & 0x1f),
((b[2] << 1) & 0x1f) | ((b[3] >> 7) & 0x1f),
(b[3] >> 2) & 0x1f,
((b[3] << 3) & 0x1f) | ((b[4] >> 5) & 0x1f),
b[4] & 0x1f,
];
let group = '';
for (const d of digits) group += B32_ALPHABET[d];
if (chunk.length < 5) {
// Truncate to the data chars and pad with '=' to 8 total.
const outLen = [0, 2, 4, 5, 7][chunk.length];
group = group.slice(0, outLen) + '='.repeat(8 - outLen);
}
out += group;
}
return out;
}
function decode32(s: string): Uint8Array | null {
const bytes: number[] = [];
let buffer = 0;
let bits = 0;
for (const c of s) {
if (c === '=') break; // padding marks the end
const idx = B32_ALPHABET.indexOf(c);
if (idx === -1) return null;
buffer = (buffer << 5) | idx;
bits += 5;
if (bits >= 8) {
bits -= 8;
bytes.push((buffer >>> bits) & 0xff);
buffer &= (1 << bits) - 1; // keep only the leftover bits
}
}
return new Uint8Array(bytes);
}
// ---------------------------------------------------------------------------
// Base58 - Bitcoin alphabet. Leading 0x00 bytes → leading '1' (count preserved).
// ---------------------------------------------------------------------------
const B58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
function encode58(bytes: Uint8Array): string {
// Count leading zero bytes - each maps to a leading '1'.
let zeros = 0;
while (zeros < bytes.length && bytes[zeros] === 0) zeros++;
// Big-endian byte array → BigInt (skipping the leading zeros).
let num = 0n;
for (let i = zeros; i < bytes.length; i++) num = (num << 8n) | BigInt(bytes[i]);
// Base-convert to 58 digits (collected least-significant first).
const digits: number[] = [];
let v = num;
while (v > 0n) {
const rem = v % 58n;
digits.push(Number(rem));
v = v / 58n;
}
let out = '1'.repeat(zeros);
for (let i = digits.length - 1; i >= 0; i--) out += B58_ALPHABET[digits[i]];
return out;
}
function decode58(s: string): Uint8Array | null {
// Count leading '1's - each maps to a 0x00 byte.
let zeros = 0;
while (zeros < s.length && s[zeros] === '1') zeros++;
let num = 0n;
for (let i = zeros; i < s.length; i++) {
const idx = B58_ALPHABET.indexOf(s[i]);
if (idx === -1) return null;
num = num * 58n + BigInt(idx);
}
// BigInt → big-endian bytes.
const bytes: number[] = [];
let v = num;
while (v > 0n) {
bytes.unshift(Number(v & 0xffn));
v = v >> 8n;
}
const out = new Uint8Array(zeros + bytes.length);
for (let i = 0; i < bytes.length; i++) out[zeros + i] = bytes[i];
return out;
}
// ---------------------------------------------------------------------------
// Base62 - standard BigInt base-conversion of the byte array (no leading-zero
// special-casing beyond standard big-int).
// ---------------------------------------------------------------------------
const B62_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
function encode62(bytes: Uint8Array): string {
if (bytes.length === 0) return '';
let num = 0n;
for (const b of bytes) num = (num << 8n) | BigInt(b);
if (num === 0n) return '0';
const digits: number[] = [];
let v = num;
while (v > 0n) {
const rem = v % 62n;
digits.push(Number(rem));
v = v / 62n;
}
let out = '';
for (let i = digits.length - 1; i >= 0; i--) out += B62_ALPHABET[digits[i]];
return out;
}
function decode62(s: string): Uint8Array | null {
if (s.length === 0) return new Uint8Array(0);
let num = 0n;
for (const c of s) {
const idx = B62_ALPHABET.indexOf(c);
if (idx === -1) return null;
num = num * 62n + BigInt(idx);
}
const bytes: number[] = [];
let v = num;
while (v > 0n) {
bytes.unshift(Number(v & 0xffn));
v = v >> 8n;
}
return new Uint8Array(bytes);
}
// ---------------------------------------------------------------------------
// Base85 - Ascii85. 4 bytes → 5 chars in '!'(33)..'u'(117); a full 4-zero
// group is shortened to 'z'. No <~ ~> delimiters. Partial final groups emit
// one fewer char than (bytes+1) would suggest; decode reverses, padding with
// 'u' (value 84).
// ---------------------------------------------------------------------------
function encode85(bytes: Uint8Array): string {
let out = '';
for (let i = 0; i < bytes.length; i += 4) {
const chunk = bytes.subarray(i, i + 4);
const isFull = chunk.length === 4;
const b = [0, 0, 0, 0];
for (let j = 0; j < chunk.length; j++) b[j] = chunk[j];
const u = b[0] * 16777216 + b[1] * 65536 + b[2] * 256 + b[3];
if (isFull && u === 0) {
out += 'z'; // zero-group shorthand
continue;
}
const digits = [0, 0, 0, 0, 0];
let v = u;
for (let k = 4; k >= 0; k--) {
digits[k] = v % 85;
v = Math.floor(v / 85);
}
let chars = '';
for (const d of digits) chars += String.fromCharCode(d + 33);
if (!isFull) chars = chars.slice(0, chunk.length + 1); // n bytes → n+1 chars
out += chars;
}
return out;
}
function decode85(s: string): Uint8Array | null {
const bytes: number[] = [];
let group: number[] = [];
for (let i = 0; i < s.length; i++) {
const c = s[i];
if (c === 'z') {
// 'z' is only valid at a group boundary (an empty accumulator).
if (group.length !== 0) return null;
bytes.push(0, 0, 0, 0);
continue;
}
const code = c.charCodeAt(0);
if (code < 33 || code > 117) return null;
group.push(code - 33);
if (group.length === 5) {
let v = 0;
for (const d of group) v = v * 85 + d;
if (v > 0xffffffff) return null; // a 5-char group must fit in 32 bits
bytes.push((v >>> 24) & 0xff, (v >>> 16) & 0xff, (v >>> 8) & 0xff, v & 0xff);
group = [];
}
}
// Handle a partial final group (2-4 chars → 1-3 bytes).
if (group.length > 0) {
const m = group.length;
if (m < 2) return null; // a lone trailing char is malformed
while (group.length < 5) group.push(84); // pad with 'u'
let v = 0;
for (const d of group) v = v * 85 + d;
if (v > 0xffffffff) return null;
const all = [(v >>> 24) & 0xff, (v >>> 16) & 0xff, (v >>> 8) & 0xff, v & 0xff];
for (let k = 0; k < m - 1; k++) bytes.push(all[k]);
}
return new Uint8Array(bytes);
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
function encodeBytes(bytes: Uint8Array, scheme: Scheme): string {
switch (scheme) {
case 'base32':
return encode32(bytes);
case 'base58':
return encode58(bytes);
case 'base62':
return encode62(bytes);
case 'base85':
return encode85(bytes);
}
}
function decodeBytes(encoded: string, scheme: Scheme): Uint8Array | null {
switch (scheme) {
case 'base32':
return decode32(encoded);
case 'base58':
return decode58(encoded);
case 'base62':
return decode62(encoded);
case 'base85':
return decode85(encoded);
}
}
/** Encode the UTF-8 bytes of `text` per `scheme`. Empty text → ''. */
export function encode(text: string, scheme: Scheme): string {
const bytes = new TextEncoder().encode(text);
return encodeBytes(bytes, scheme);
}
/** Decode `encoded` back to UTF-8 text. Invalid chars / malformed → null. */
export function decode(encoded: string, scheme: Scheme): string | null {
const bytes = decodeBytes(encoded, scheme);
if (bytes === null) return null;
return new TextDecoder().decode(bytes);
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →