UNPKG

ox

Version:

Ethereum Standard Library

179 lines 6.02 kB
import { BaseError } from './Errors.js'; import { InvalidPaddingError as SharedInvalidPaddingError, alphabet as sharedAlphabet, alphabetMap as sharedAlphabetMap, convertBits as sharedConvertBits, } from './internal/codec/bech32-base32.js'; /** * Encodes data bytes with a human-readable part (HRP) into a bech32m string (BIP-350). * * @example * ```ts twoslash * import { Bech32m } from 'ox' * * const encoded = Bech32m.encode('tempo', new Uint8Array(20)) * // @log: 'tempo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqwa7xtm' * ``` * * @param hrp - The human-readable part (e.g. `"tempo"`, `"tempoz"`). * @param data - The data bytes to encode. * @returns The bech32m-encoded string. */ export function encode(hrp, data, options = {}) { const { limit = 90 } = options; hrp = hrp.toLowerCase(); if (hrp.length === 0) throw new InvalidHrpError(); for (let i = 0; i < hrp.length; i++) { const c = hrp.charCodeAt(i); if (c < 33 || c > 126) throw new InvalidHrpError(); } const data5 = convertBits(data, 8, 5, true); if (hrp.length + 1 + data5.length + 6 > limit) throw new ExceedsLengthError({ limit }); const checksum = createChecksum(hrp, data5); let result = hrp + '1'; for (const d of data5.concat(checksum)) result += alphabet[d]; return result; } /** * Decodes a bech32m string (BIP-350) into a human-readable part and data bytes. * * @example * ```ts twoslash * import { Bech32m } from 'ox' * * const { hrp, data } = Bech32m.decode( * 'tempo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqwa7xtm' * ) * // @log: { hrp: 'tempo', data: Uint8Array(20) } * ``` * * @param str - The bech32m-encoded string to decode. * @returns The decoded HRP and data bytes. */ export function decode(str, options = {}) { const { limit = 90 } = options; if (str.length > limit) throw new ExceedsLengthError({ limit }); if (str !== str.toLowerCase() && str !== str.toUpperCase()) throw new MixedCaseError(); const lower = str.toLowerCase(); const pos = lower.lastIndexOf('1'); if (pos === -1) throw new NoSeparatorError(); if (pos === 0) throw new InvalidHrpError(); if (pos + 7 > lower.length) throw new InvalidChecksumError(); const hrp = lower.slice(0, pos); for (let i = 0; i < hrp.length; i++) { const c = hrp.charCodeAt(i); if (c < 33 || c > 126) throw new InvalidHrpError(); } const dataChars = lower.slice(pos + 1); const data5 = []; for (const c of dataChars) { const v = alphabetMap[c]; if (v === undefined) throw new InvalidCharacterError({ character: c }); data5.push(v); } if (!verifyChecksum(hrp, data5)) throw new InvalidChecksumError(); const data8 = convertBits(data5.slice(0, -6), 5, 8, false); return { hrp, data: new Uint8Array(data8) }; } /** Thrown when a bech32m string has no separator. */ export class NoSeparatorError extends BaseError { name = 'Bech32m.NoSeparatorError'; constructor() { super('Bech32m string has no separator.'); } } /** Thrown when a bech32m string has an invalid checksum. */ export class InvalidChecksumError extends BaseError { name = 'Bech32m.InvalidChecksumError'; constructor() { super('Invalid bech32m checksum.'); } } /** Thrown when a bech32m string contains an invalid character. */ export class InvalidCharacterError extends BaseError { name = 'Bech32m.InvalidCharacterError'; constructor({ character }) { super(`Invalid bech32m character: "${character}".`); } } /** * Thrown when the padding bits are invalid during base32 conversion. * * Re-exported from `internal/codec/bech32-base32.ts` for shared identity * with `Base32`'s padding validation. */ export const InvalidPaddingError = SharedInvalidPaddingError; /** Thrown when a bech32m string contains mixed case. */ export class MixedCaseError extends BaseError { name = 'Bech32m.MixedCaseError'; constructor() { super('Bech32m string must not contain mixed case.'); } } /** Thrown when the HRP is invalid (empty or contains non-ASCII characters). */ export class InvalidHrpError extends BaseError { name = 'Bech32m.InvalidHrpError'; constructor() { super('Invalid bech32m human-readable part (HRP). Must be 1+ characters in ASCII range 33-126.'); } } /** Thrown when the encoded string exceeds the length limit. */ export class ExceedsLengthError extends BaseError { name = 'Bech32m.ExceedsLengthError'; constructor({ limit }) { super(`Bech32m string exceeds length limit of ${limit}.`); } } /** Shared with `Base32` (BIP-173). */ const alphabet = sharedAlphabet; const alphabetMap = sharedAlphabetMap; /** @internal */ const BECH32M_CONST = 0x2bc830a3; /** @internal */ const GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; /** @internal */ function polymod(values) { let chk = 1; for (const v of values) { const b = chk >> 25; chk = ((chk & 0x1ffffff) << 5) ^ v; for (let i = 0; i < 5; i++) if ((b >> i) & 1) chk ^= GEN[i]; } return chk; } /** @internal */ function hrpExpand(hrp) { const ret = []; for (let i = 0; i < hrp.length; i++) ret.push(hrp.charCodeAt(i) >> 5); ret.push(0); for (let i = 0; i < hrp.length; i++) ret.push(hrp.charCodeAt(i) & 31); return ret; } /** @internal */ function createChecksum(hrp, data) { const values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]); const mod = polymod(values) ^ BECH32M_CONST; const ret = []; for (let i = 0; i < 6; i++) ret.push((mod >> (5 * (5 - i))) & 31); return ret; } /** @internal */ function verifyChecksum(hrp, data) { return polymod(hrpExpand(hrp).concat(data)) === BECH32M_CONST; } /** Shared with `Base32`. */ const convertBits = sharedConvertBits; //# sourceMappingURL=Bech32m.js.map