Numbers in Script are represented as byte vectors of up to 4 bytes and interpreted as signed integers between -2^31+1 and 2^31-1. This is with the exception of OP_0
, OP_1NEGATE
and OP_1
through OP_16
which allow to represent small numbers (from -1 to 16) by using opcodes directly.
These 31 bits numbers are encoded in little-endian with sign-magnitude. Note the byte vector size is not fixed: there is no padding for small integers. Here is a few examples.
Integer |
Script byte vector |
-424242 |
[0x32; 0x79; 0x86] |
-0x80 |
[0x80; 0x80] |
-1 |
[0x81] |
0 |
[] |
12 |
[0x0c] |
0x82 |
[0x82; 0x00] |
2^31-1 |
[0xff; 0xff; 0xff; 0x7f] |
The reference implementation of the serialization can be found here. The Bitcoin Core test framework has a Python reimplementation of the serialization. Here is a simplified and highly commented version:
def ser_script_num(n):
res = bytearray()
# If n is 0, return the empty vector.
if n == 0:
return bytes(res)
# Encode n as little-endian. Done manually to avoid padding.
abs_n = abs(n)
while abs_n > 0:
res.append(abs_n & 0xff)
abs_n >>= 8
# If n is negative and the serialized number does not have its most significant
# bit set, we can just set it by adding 0x80 to its most significant byte. This
# avoids requiring an additional byte just to set the sign bit.
msb_set = res[len(res) - 1] >= 0x80
if not msb_set and n < 0:
res[len(res) -1] += 0x80
# However it does mean that if the serialized number has its most significant
# bit set we need to push an additional byte so 1) it's not treated as negative
# if it's positive 2) it's not substracted from its actual value.
if msb_set:
# If n is positive add a byte so the sign bit isn't set. If it's negative add
# another byte for the sign bit so it's not substracted from the number itself.
if n >= 0:
res.append(0x00)
else:
res.append(0x80)
return bytes(res)