gchq/CyberChef · error · OperationError

Invalid padding: too many bits remaining

Error message

Invalid padding: too many bits remaining

What it means

Thrown by fromWords() in src/core/lib/Bech32.mjs:149 when converting 5-bit Bech32 words back to 8-bit bytes leaves 5 or more uncommitted bits. Per BIP-0173, the data part of a valid Bech32 string must not leave a residual of 5+ bits after the final byte — such a residual can only arise from a malformed or truncated word sequence, because each word contributes 5 bits and a full byte consumes 8.

Source

Thrown at src/core/lib/Bech32.mjs:149

export function fromWords(words) {
    let value = 0;
    let bits = 0;
    const result = [];

    for (let i = 0; i < words.length; i++) {
        value = (value << 5) | words[i];
        bits += 5;

        while (bits >= 8) {
            bits -= 8;
            result.push((value >> bits) & 255);
        }
    }

    // Check for invalid padding per BIP-0173
    // Condition 1: Cannot have 5+ bits remaining (would indicate incomplete byte)
    if (bits >= 5) {
        throw new OperationError("Invalid padding: too many bits remaining");
    }
    // Condition 2: Remaining padding bits must all be zero
    if (bits > 0) {
        const paddingValue = (value << (8 - bits)) & 255;
        if (paddingValue !== 0) {
            throw new OperationError("Invalid padding: non-zero bits in padding");
        }
    }

    return result;
}

/**
 * Encode data to Bech32/Bech32m string
 *
 * @param {string} hrp - Human-readable part
 * @param {number[]|Uint8Array} data - Data bytes to encode
 * @param {string} encoding - "Bech32" or "Bech32m"

View on GitHub (pinned to 4290ea7539)

Solutions

  1. Ensure you pass only the data words (checksum stripped) to fromWords — the library does this internally in decode().
  2. Validate the source string's checksum before decoding; a bad checksum often signals truncation.
  3. If calling fromWords directly, confirm words.length * 5 leaves a residual < 5 bits (i.e. (words.length*5) % 8 must not produce 5-7 leftover).
  4. Re-acquire the Bech32 string from its origin; truncation is the usual cause.

Example fix

// before - passing a too-short / malformed word list
const bytes = fromWords([0]);

// after - decode through the public API which strips checksum and validates
const { data } = decode(bech32String);
Defensive patterns

Strategy: validation

Validate before calling

function wordCountIsValid(words) {
  // After consuming words, residual bits = (words.length * 5) % 8; must be < 5
  return ((words.length * 5) % 8) < 5;
}

Type guard

function isPlausibleBech32WordArray(words) {
  return Array.isArray(words) && words.every(w => w >= 0 && w < 32) && ((words.length * 5) % 8) < 5;
}

Try / catch

try {
  const bytes = fromWords(words);
} catch (e) {
  if (e instanceof OperationError && /too many bits remaining/.test(e.message)) {
    // word list is truncated or malformed; re-acquire source
  }
}

Prevention

When it happens

Trigger: fromWords(words) where words.length produces bits in {5,6,7} at the end with no further word to flush — e.g. a single word [0] gives bits=5 after the loop, throwing. In practice reached via decode() of a string whose data part (minus checksum) has a length that leaves >=5 residual bits, or by calling fromWords directly with a short array.

Common situations: Decoding a truncated Bech32 string that passed checksum by accident (or with checksum disabled upstream); hand-crafted test vectors with wrong word counts; feeding the full data part including the 6-word checksum into fromWords instead of slicing it off first.

Related errors


AI-assisted analysis of gchq/CyberChef@4290ea7539 (2026-08-13). Data as JSON: /api/errors/3a0eeb2bbb12d206. Report an issue: GitHub.