gchq/CyberChef · error · OperationError

Invalid padding: non-zero bits in padding

Error message

Invalid padding: non-zero bits in padding

What it means

Thrown by fromWords() in src/core/lib/Bech32.mjs:155 when, after consuming all 5-bit words into bytes, between 1 and 4 residual bits remain and at least one of them is non-zero. BIP-0173 requires padding bits to be zero; a non-zero residual means the encoding is non-canonical (the same data could be represented more than one way) and is treated as invalid.

Source

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

        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"
 * @param {boolean} segwit - If true, treat first byte as witness version (for Bitcoin SegWit)
 * @returns {string} - Encoded Bech32/Bech32m string
 */
export function encode(hrp, data, encoding = "Bech32", segwit = false) {
    // Validate HRP
    if (!hrp || hrp.length === 0) {

View on GitHub (pinned to 4290ea7539)

Solutions

  1. Treat this as data corruption: re-obtain the Bech32 string from its source.
  2. If you control encoding, ensure your toWords equivalent zero-pads the final word (this library's toWords at line 119-121 already does).
  3. Validate the checksum first; non-zero padding almost always coincides with checksum failure.
  4. Avoid hand-editing individual characters of a Bech32 string.

Example fix

// before - last word has non-zero padding bits
const bytes = fromWords([0x1f, 0x1f, 0x1f, 0x10]); // residual bits may be non-zero

// after - use canonical encoding via toWords/encode round-trip
const encoded = encode('bc', originalBytes, 'Bech32');
const { data } = decode(encoded);
Defensive patterns

Strategy: try-catch

Validate before calling

function wordsHaveZeroPadding(words) {
  let value = 0, bits = 0;
  for (const w of words) { value = (value << 5) | w; bits += 5; while (bits >= 8) { bits -= 8; } }
  if (bits === 0) return true;
  return ((value << (8 - bits)) & 255) === 0;
}

Type guard

function isCanonicalBech32Words(words) {
  return Array.isArray(words) && wordsHaveZeroPadding(words);
}

Try / catch

try {
  const bytes = fromWords(words);
} catch (e) {
  if (e instanceof OperationError && /non-zero bits in padding/.test(e.message)) {
    // non-canonical encoding; treat as corrupt
  }
}

Prevention

When it happens

Trigger: fromWords(words) where the residual 1-4 bits encode a non-zero value — e.g. words ending in a value whose low bits are set beyond the last byte boundary. Reached internally by decode() for strings like a hand-edited Bech32 whose last data word has stray low bits, or via direct fromWords call on non-canonical input.

Common situations: A character in the data part was altered (corruption) producing a word whose residual bits are non-zero; a non-canonical encoder that did not zero-pad; test vectors from an implementation that ignores BIP-0173's canonical-padding rule.

Related errors


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