Understanding Script's number encoding, CScriptNum

What is a simple explanation of CScriptNum, the number encoding used by arithmetic opcodes in Bitcoin Script?

What would an illustrative pseudocode/python example look like?

What should the encoding of the following integers be (in hexadecimal)?

• 42
• 88
• 257
• 17000
• 8589934592

When is it not 4 bytes?

Why are arithmetic script opcodes limited to 4 byte operands?

Arithmetic operations in Bitcoin (OP_ADD, etc)

Compact integer in Scripts

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)
``````