OP_EQUAL vs OP_NUMEQUAL - Critical Difference
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:
-
OP_EQUAL(0x87) — byte-for-byte raw comparison -
OP_NUMEQUAL(0x9C) — integer-semantic numeric comparison
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 | |
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: