Secure, audited & 0-dep implementation of base encoding algorithms
—
Low-level utility functions for building custom encoders using functional composition. These utilities enable creating domain-specific encoders by chaining simple operations together, following the same patterns used internally by @scure/base.
Composes multiple coders together in functional style, ensuring encode/decode operations maintain proper order.
/**
* Chains multiple coders together in functional composition
* Automatically handles proper order for encode/decode operations
* @param args - Sequence of coders to chain together
* @returns Single coder combining all operations
*/
function chain<T extends Chain & AsChain<T>>(
...args: T
): Coder<Input<First<T>>, Output<Last<T>>>;
type Chain = [Coder<any, any>, ...Coder<any, any>[]];Usage Examples:
import { utils } from "@scure/base";
// Create custom base32 encoder by chaining operations
const customBase32 = utils.chain(
utils.radix2(5), // Convert bytes to 5-bit values
utils.alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'), // Map to base32 alphabet
utils.padding(5), // Add padding
utils.join('') // Join to string
);
const data = new Uint8Array([0x12, 0x34]);
const encoded = customBase32.encode(data); // Works like base32.encode()
const decoded = customBase32.decode(encoded); // Works like base32.decode()Maps between number arrays and string arrays using a custom alphabet.
/**
* Creates encoder for number array to string array mapping using custom alphabet
* @param letters - Alphabet as string or array of strings
* @returns Coder mapping number indices to alphabet characters
*/
function alphabet(letters: string | string[]): Coder<number[], string[]>;Usage Examples:
import { utils } from "@scure/base";
// Create alphabet mapper
const hexAlphabet = utils.alphabet('0123456789abcdef');
const digits = [15, 14, 13, 12]; // Indices into alphabet
const letters = hexAlphabet.encode(digits); // ['f', 'e', 'd', 'c']
const restored = hexAlphabet.decode(letters); // [15, 14, 13, 12]
// Can also use string array
const customAlphabet = utils.alphabet(['zero', 'one', 'two']);
const words = customAlphabet.encode([0, 2, 1]); // ['zero', 'two', 'one']Convert between arbitrary numeric bases. Note: O(n^2) complexity for non-power-of-2 bases.
/**
* Converts bytes to arbitrary radix representation
* @param num - Target radix/base number
* @returns Coder converting bytes to number array in specified radix
*/
function radix(num: number): Coder<Uint8Array, number[]>;
/**
* Optimized radix conversion for power-of-2 bases
* @param bits - Number of bits per output digit (must be ≤32)
* @param revPadding - Reverse padding direction
* @returns Coder for efficient power-of-2 base conversion
*/
function radix2(bits: number, revPadding?: boolean): Coder<Uint8Array, number[]>;Usage Examples:
import { utils } from "@scure/base";
// Convert to base 58 (O(n^2) - use sparingly)
const base58Radix = utils.radix(58);
const data = new Uint8Array([0x12, 0x34]);
const base58Digits = base58Radix.encode(data); // Array of base58 digit values
// Convert to base 32 (power of 2 - efficient)
const base32Radix = utils.radix2(5); // 2^5 = 32
const base32Digits = base32Radix.encode(data); // Array of 5-bit valuesJoin and split string arrays with configurable separators.
/**
* Joins string arrays into single strings and splits them back
* @param separator - String to use for joining (default: empty string)
* @returns Coder for joining/splitting string arrays
*/
function join(separator?: string): Coder<string[], string>;Usage Examples:
import { utils } from "@scure/base";
// Join without separator (default)
const joiner = utils.join();
const joined = joiner.encode(['a', 'b', 'c']); // "abc"
const split = joiner.decode('abc'); // ['a', 'b', 'c'] - splits into chars
// Join with separator
const dashedJoiner = utils.join('-');
const dashed = dashedJoiner.encode(['hello', 'world']); // "hello-world"
const unDashed = dashedJoiner.decode('hello-world'); // ['hello', 'world']Add and remove padding from string arrays to ensure proper bit alignment.
/**
* Adds/removes padding to ensure string array represents whole number of bits
* @param bits - Number of bits per character in the encoding
* @param chr - Padding character (default: '=')
* @returns Coder for adding/removing padding
*/
function padding(bits: number, chr?: string): Coder<string[], string[]>;Usage Examples:
import { utils } from "@scure/base";
// Base64 uses 6 bits per character, padded with '='
const base64Padding = utils.padding(6, '=');
const data = ['Q', 'W']; // Incomplete group
const padded = base64Padding.encode(data); // ['Q', 'W', '=', '=']
const unpadded = base64Padding.decode(padded); // ['Q', 'W']
// Base32 uses 5 bits per character
const base32Padding = utils.padding(5);
const base32Data = ['A', 'B', 'C'];
const paddedB32 = base32Padding.encode(base32Data); // Adds padding if neededAdd checksum validation to any encoder for error detection.
/**
* Adds checksum validation using provided hash function
* @param len - Number of checksum bytes to append
* @param fn - Hash function for generating checksum
* @returns Coder that appends/validates checksum
*/
function checksum(
len: number,
fn: (data: Uint8Array) => Uint8Array
): Coder<Uint8Array, Uint8Array>;Usage Examples:
import { utils } from "@scure/base";
import { sha256 } from "@noble/hashes/sha2";
// Create checksum validator using SHA-256 (like base58check)
const sha256Checksum = utils.checksum(4, (data) => sha256(sha256(data)));
const originalData = new Uint8Array([0x12, 0x34, 0x56]);
const withChecksum = sha256Checksum.encode(originalData); // Appends 4-byte checksum
const verified = sha256Checksum.decode(withChecksum); // Validates and removes checksum
// Error on invalid checksum
try {
const corrupted = new Uint8Array([...withChecksum]);
corrupted[corrupted.length - 1] = 0; // Corrupt checksum
sha256Checksum.decode(corrupted); // Throws "Invalid checksum"
} catch (error) {
console.log("Checksum validation failed");
}Apply transformation functions during decode operations for input sanitization.
/**
* Applies normalization function during decode operation
* @param fn - Function to transform input during decode
* @returns Coder that applies transformation on decode
*/
function normalize<T>(fn: (val: T) => T): Coder<T, T>;Usage Examples:
import { utils } from "@scure/base";
// Create case-insensitive decoder (like base32crockford)
const caseNormalizer = utils.normalize((s: string) => s.toUpperCase());
const encoded = caseNormalizer.encode("Hello"); // "Hello" (no change on encode)
const decoded = caseNormalizer.decode("hello"); // "HELLO" (normalized on decode)
// Create character substitution (like base32crockford)
const charNormalizer = utils.normalize((s: string) =>
s.toUpperCase().replace(/O/g, '0').replace(/[IL]/g, '1')
);
const normalized = charNormalizer.decode("hel1O"); // "HEL10" (O->0, I/L->1)Low-level radix conversion functions for direct use.
/**
* Direct radix conversion between arbitrary bases (O(n^2) complexity)
* @param data - Input digits in source base
* @param from - Source radix
* @param to - Target radix
* @returns Digits in target radix
*/
function convertRadix(data: number[], from: number, to: number): number[];
/**
* Optimized radix conversion for power-of-2 bases
* @param data - Input digits
* @param from - Source radix (power of 2, ≤32)
* @param to - Target radix (power of 2, ≤32)
* @param padding - Whether to add padding
* @returns Converted digits
*/
function convertRadix2(
data: number[],
from: number,
to: number,
padding: boolean
): number[];Usage Examples:
import { utils } from "@scure/base";
// Convert from base 10 to base 16
const decimal = [1, 2, 3]; // Represents 123 in base 10
const hex = utils.convertRadix(decimal, 10, 16); // Convert to base 16
// Efficient binary to base32 conversion
const binary = [1, 0, 1, 1, 0]; // 5 bits
const base32 = utils.convertRadix2(binary, 2, 32, true); // Convert 2^1 to 2^5Combine utilities to create domain-specific encoders:
import { utils } from "@scure/base";
import { sha256 } from "@noble/hashes/sha2";
// Example: Custom cryptocurrency address encoder
const cryptoAddress = utils.chain(
utils.checksum(4, (data) => sha256(sha256(data))), // Double SHA-256 checksum
utils.radix(58), // Convert to base58 digits
utils.alphabet('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'),
utils.join('') // Join to string
);
// Example: Custom data format with metadata
const dataFormat = utils.chain(
utils.radix2(6), // 6-bit encoding like base64
utils.alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'),
utils.padding(6, '='), // Base64-style padding
utils.join('-'), // Use dashes as separators
utils.normalize((s: string) => s.toLowerCase()) // Normalize to lowercase
);
// Example: Compact binary encoding
const compactBinary = utils.chain(
utils.radix2(8), // Keep as bytes
utils.alphabet('0123456789ABCDEF'), // Hex alphabet
utils.join('') // Standard hex string
);radix() has O(n^2) complexity - use only with small inputsradix2() has O(n) complexity - preferred for power-of-2 basesconvertRadix() and convertRadix2() are the underlying conversion functionsAll utility functions validate inputs and provide descriptive errors:
import { utils } from "@scure/base";
try {
utils.radix(1); // Base must be ≥ 2
} catch (error) {
console.log(error.message); // "convertRadix: invalid from=1, base cannot be less than 2"
}
try {
utils.alphabet('ABC').decode(['D']); // Invalid character
} catch (error) {
console.log(error.message); // "Unknown letter: D. Allowed: ABC"
}
try {
utils.padding(6).decode(['A', 'B', '=', '=', '=']); // Too much padding
} catch (error) {
console.log(error.message); // "padding: invalid, string has too much padding"
}Install with Tessl CLI
npx tessl i tessl/npm-scure--base