gchq/CyberChef · error · OperationError

Invalid witness program length for v0: ${witnessProgram.leng

Error message

Invalid witness program length for v0: ${witnessProgram.length}. Must be 20 or 32 bytes.

What it means

Thrown by encode() in src/core/lib/Bech32.mjs:202 in SegWit mode when witnessVersion is 0 and the program length is neither 20 nor 32 bytes. BIP-0173 fixes v0 programs to exactly 20 bytes (P2WPKH, HASH160) or 32 bytes (P2WSH, SHA256); any other length for v0 is invalid and consensus-illegal, so the library refuses to produce an address that no node would accept.

Source

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

    // Convert HRP to lowercase
    const hrpLower = hrp.toLowerCase();

    let words;
    if (segwit && data.length >= 2) {
        // SegWit encoding: first byte is witness version (0-16), rest is witness program
        const witnessVersion = data[0];
        if (witnessVersion > 16) {
            throw new OperationError(`Invalid witness version: ${witnessVersion}. Must be 0-16.`);
        }
        const witnessProgram = Array.prototype.slice.call(data, 1);

        // Validate witness program length per BIP-0141
        if (witnessProgram.length < 2 || witnessProgram.length > 40) {
            throw new OperationError(`Invalid witness program length: ${witnessProgram.length}. Must be 2-40 bytes.`);
        }
        if (witnessVersion === 0 && witnessProgram.length !== 20 && witnessProgram.length !== 32) {
            throw new OperationError(`Invalid witness program length for v0: ${witnessProgram.length}. Must be 20 or 32 bytes.`);
        }

        // Witness version is kept as single 5-bit value, program is converted
        words = [witnessVersion].concat(toWords(witnessProgram));
    } else {
        // Generic encoding: convert all bytes to 5-bit words
        words = toWords(data);
    }

    // Create checksum
    const checksum = createChecksum(hrpLower, words, encoding);

    // Build result string
    let result = hrpLower + "1";
    for (const w of words.concat(checksum)) {
        result += CHARSET[w];
    }

View on GitHub (pinned to 4290ea7539)

Solutions

  1. For P2WPKH (v0): use HASH160 (ripemd160(sha256(pubkey))) -> exactly 20 bytes.
  2. For P2WSH (v0): use SHA256 of the script -> exactly 32 bytes.
  3. If your program is a different length, use witness version 1+ (Taproot etc.) where 2-40 bytes are allowed.
  4. Re-derive the hash with the correct algorithm and verify the byte count before encoding.

Example fix

// before - v0 program is 21 bytes (invalid)
encode('bc', [0, ...hashWithExtraByte], 'Bech32', true);

// after - 20-byte HASH160 for P2WPKH
encode('bc', [0, ...ripemd160(sha256(pubkey))], 'Bech32', true);
Defensive patterns

Strategy: validation

Validate before calling

function validateV0Program(program) {
  if (program.length !== 20 && program.length !== 32) {
    throw new RangeError(`v0 program must be 20 or 32 bytes, got ${program.length}`);
  }
  return program;
}

Type guard

function isV0ProgramLength(bytes) {
  return Array.isArray(bytes) && (bytes.length === 20 || bytes.length === 32);
}

Try / catch

try {
  encode('bc', [0, ...program], 'Bech32', true);
} catch (e) {
  if (e instanceof OperationError && /witness program length for v0/.test(e.message)) {
    // wrong hash function used; re-hash with HASH160 or SHA256
  }
}

Prevention

When it happens

Trigger: encode('bc', [0, ...15bytes], 'Bech32', true), encode('bc', [0, ...21bytes], 'Bech32', true), encode('bc', [0, ...33bytes], 'Bech32', true). Triggered after the general 2-40 length check passes but the v0-specific check fails.

Common situations: Using a v0 program with the wrong hash length (e.g. SHA256 of a script for what was meant to be P2WPKH); truncating a 20-byte hash to 19; appending a checksum or version byte to the program; mismatch between intended address type (PKH vs SH) and hash function.

Related errors


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