TeachMeBitcoin

OP_EQUAL vs OP_NUMEQUAL - Critical Difference

From TeachMeBitcoin, the free encyclopedia Reading time: 3 min

3. OP_EQUAL vs OP_NUMEQUAL — Critical Difference

Overview

Bitcoin Script has two equality-comparison opcodes that appear similar at first glance but operate on fundamentally different semantic levels:

Choosing the wrong one is not merely a performance concern — it can make scripts permanently unspendable, create consensus bugs, or introduce subtle vulnerabilities. Understanding this distinction is essential for any Bitcoin Script developer.

Integer Encoding in Bitcoin Script

Bitcoin Script represents integers on the stack using a variable-length, little-endian, sign-magnitude encoding. Key rules:

Encoding rules:
  - Little-endian byte order
  - Most significant bit of the last byte is the sign bit (0 = positive, 1 = negative)
  - Minimal encoding required (no unnecessary leading zero bytes)
  - The number 0 is encoded as an empty byte array: 0x (empty)

Examples:
  Integer 0   → 0x          (empty byte array)
  Integer 1   → 0x01
  Integer -1  → 0x81        (sign bit set in last byte)
  Integer 127 → 0x7F
  Integer 128 → 0x8000      (needs extra byte so sign bit stays clear)
  Integer 255 → 0xFF00
  Integer 256 → 0x0001      (little-endian: low byte first)
  Integer -128 → 0x80       (sign bit of 0x80 is set → negative 128... actually -0 edge case)

Because of this variable-length encoding, the same logical integer value can theoretically be represented in multiple ways (with or without padding bytes). The Script interpreter enforces minimal encoding under BIP62/MINIMALDATA rules, but this still leaves the zero-value ambiguity.

The Zero Problem

The most illustrative example of OP_EQUAL vs OP_NUMEQUAL is the representation of zero:

Canonical CScript zero: 0x  (empty byte array, length 0)
Non-canonical zero:     0x00 (single byte of 0x00, length 1)
Non-canonical zero:     0x80 (negative zero, sign bit set, magnitude zero)

OP_EQUAL comparison:
  0x vs 0x00:
    Different lengths (0 bytes vs 1 byte) → RESULT: 0x00 (NOT EQUAL)

  0x vs 0x80:
    Different lengths → RESULT: 0x00 (NOT EQUAL)

OP_NUMEQUAL comparison:
  0x vs 0x00:
    Decode 0x = integer 0
    Decode 0x00 = integer 0
    0 == 0 → RESULT: 0x01 (EQUAL)

  0x vs 0x80:
    Decode 0x = integer 0
    Decode 0x80 = integer -0 = integer 0 (negative zero normalizes to zero)
    0 == 0 → RESULT: 0x01 (EQUAL)

This means OP_EQUAL would reject equality between two representations of zero, while OP_NUMEQUAL would correctly identify them as the same integer.

OP_NUMEQUAL in Detail

OP_NUMEQUAL (0x9C) decodes both top stack items as CScript integers, compares their integer values, and pushes 0x01 (true) or 0x00 (false). Like OP_EQUAL, it leaves the result on the stack without terminating.

There is also OP_NUMEQUALVERIFY (0x9D), which combines OP_NUMEQUAL with an immediate failure on inequality — the numeric analog of OP_EQUALVERIFY:

OP_NUMEQUAL:
  Pops A and B, treats as integers
  Pushes 0x01 if integer(A) == integer(B), else 0x00

OP_NUMEQUALVERIFY:
  Same, but fails immediately if not equal
  (Does not push anything on success)

Side-by-Side Comparison Table

``` | Scenario | OP_EQUAL | OP_NUMEQUAL | |

☕ Help support TeachMeBitcoin

TeachMeBitcoin is an ad-free, open-source educational repository curated by a passionate team of Bitcoin researchers and educators for public benefit. If you found our articles helpful, please consider supporting our hosting and ongoing content updates with a clean donation:

Ethereum: 0x578417C51783663D8A6A811B3544E1f779D39A85
Bitcoin: bc1q77k9e95rn669kpzyjr8ke9w95zhk7pa5s63qzz
Solana: 4ycT2ayqeMucixj3wS8Ay8Tq9NRDYRPKYbj3UGESyQ4J
Address copied to clipboard!