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
- Ensure you pass only the data words (checksum stripped) to fromWords — the library does this internally in decode().
- Validate the source string's checksum before decoding; a bad checksum often signals truncation.
- 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).
- 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
- Decode through the public decode() API rather than calling fromWords directly.
- Always slice off the 6 checksum words before converting data words to bytes.
- Validate the checksum first; truncation usually fails checksum too.
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
- Invalid padding: non-zero bits in padding
- Invalid Bech32 string: exceeds maximum length of 90 characte
- Invalid Bech32 string: mixed case is not allowed. Use all up
- Error: Base64 padding character (${pad}) not used in the cor
- Error: Base64 not padded to a multiple of 4.
AI-assisted analysis of gchq/CyberChef@4290ea7539 (2026-08-13).
Data as JSON: /api/errors/3a0eeb2bbb12d206.
Report an issue: GitHub.