Numbers in Script are represented as byte vectors of as much as 4 bytes and interpreted as signed integers between -2^31+1 and a pair of^31-1. That is aside from OP_0
, OP_1NEGATE
and OP_1
via OP_16
which permit to characterize small numbers (from -1 to 16) through the use of opcodes immediately.
These 31 bits numbers are encoded in little-endian with sign-magnitude. Notice the byte vector measurement isn’t mounted: there isn’t any padding for small integers. Here’s a few examples.
Decimal worth | 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 might be discovered right here. The Bitcoin Core check framework has a Python reimplementation of the serialization. Here’s a simplified and extremely commented model:
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. Executed manually to keep away from padding.
abs_n = abs(n)
whereas abs_n > 0:
res.append(abs_n & 0xff)
abs_n >>= 8
# If n is adverse and the serialized quantity doesn't have its most important
# bit set, we will simply set it by including 0x80 to its most important byte. This
# avoids requiring a further byte simply to set the signal bit.
msb_set = res[len(res) - 1] >= 0x80
if not msb_set and n < 0:
res[len(res) -1] += 0x80
# Nevertheless it does imply that if the serialized quantity has its most important
# bit set we have to push a further byte so 1) it isn't handled as adverse
# if it is constructive 2) it isn't substracted from its precise worth.
if msb_set:
# If n is constructive add a byte so the signal bit is not set. If it is adverse add
# one other byte for the signal bit so it isn't substracted from the quantity itself.
if n >= 0:
res.append(0x00)
else:
res.append(0x80)
return bytes(res)