<p>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 <code>OP_0</code>, <code>OP_1NEGATE</code> and <code>OP_1</code> through <code>OP_16</code> which allow to represent small numbers (from -1 to 16) by using opcodes directly.</p>
<p>These 31 bits numbers are encoded in little-endian with <a href="https://en.wikipedia.org/wiki/Signed_number_representations#Sign%E2%80%93magnitude" rel="nofollow noreferrer">sign-magnitude</a>. Note the byte vector size is not fixed: there is no padding for small integers. Here is a few examples.</p>
<div class="s-table-container"><table class="s-table">
<thead>
<tr>
<th>Integer</th>
<th>Script byte vector</th>
</tr>
</thead>
<tbody>
<tr>
<td>-424242</td>
<td><code>[0x32; 0x79; 0x86]</code></td>
</tr>
<tr>
<td>-0x80</td>
<td><code>[0x80; 0x80]</code></td>
</tr>
<tr>
<td>-1</td>
<td><code>[0x81]</code></td>
</tr>
<tr>
<td>0</td>
<td><code>[]</code></td>
</tr>
<tr>
<td>12</td>
<td><code>[0x0c]</code></td>
</tr>
<tr>
<td>0x82</td>
<td><code>[0x82; 0x00]</code></td>
</tr>
<tr>
<td>2^31-1</td>
<td><code>[0xff; 0xff; 0xff; 0x7f]</code></td>
</tr>
</tbody>
</table></div>
<p>The reference implementation of the serialization can be found <a href="https://github.com/bitcoin/bitcoin/blob/74f517b4411d80afa7733c088da961e9c736a97d/src/script/script.h#L348-L379" rel="nofollow noreferrer">here</a>. The Bitcoin Core <a href="https://github.com/bitcoin/bitcoin/blob/43a66c55ec8770cf7c21112aac9b997f3f2fb704/test/functional/test_framework/script.py#L407-L421" rel="nofollow noreferrer">test framework has a Python reimplementation</a> of the serialization. Here is a simplified and highly commented version:</p>
<pre class="lang-python prettyprint-override"><code>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 &gt; 0:
        res.append(abs_n &amp; 0xff)
        abs_n &gt;&gt;= 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] &gt;= 0x80
    if not msb_set and n &lt; 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 &gt;= 0:
            res.append(0x00)
        else:
            res.append(0x80)

    return bytes(res)
</code></pre>

CC BY-SA 4.0
2024-05-08T11:00:29.277Z
2024-05-08T12:42:19.197Z
2024-05-08T12:42:19.197Z
Antoine Poinsot
<p>What is a simple explanation of <a href="https://github.com/bitcoin/bitcoin/blob/74f517b4411d80afa7733c088da961e9c736a97d/src/script/script.h#L225" rel="nofollow noreferrer">CScriptNum</a>, the number encoding used by arithmetic opcodes in Bitcoin Script?</p>
<p>What would an illustrative pseudocode/python example look like?</p>
<p>What should the encoding of the following integers be (in hexadecimal)?</p>
<ul>
<li>42</li>
<li>88</li>
<li>257</li>
<li>17000</li>
<li>8589934592</li>
</ul>
<p>When is it not 4 bytes?</p>
<hr />
<p>See Also:</p>
<p><a href="https://bitcoin.stackexchange.com/questions/49787/why-are-arithmetic-script-opcodes-limited-to-4-byte-operands">Why are arithmetic script opcodes limited to 4 byte operands?</a></p>
<p><a href="https://bitcoin.stackexchange.com/questions/29435/arithmetic-operations-in-bitcoin-op-add-etc">Arithmetic operations in Bitcoin (OP_ADD, etc)</a></p>
<p><a href="https://bitcoin.stackexchange.com/questions/38378/compact-integer-in-scripts/46785#46785">Compact integer in Scripts</a></p>

<bitcoin-core><script><terminology><encoding><specification>
Understanding Script's number encoding, CScriptNum
Lee
# Understanding Script's number encoding, CScriptNum

(Asked by: username_0 on 2024-05-08)

What is a simple explanation of [CScriptNum](https://github.com/bitcoin/bitcoin/blob/74f517b4411d80afa7733c088da961e9c736a97d/src/script/script.h#L225), 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?

---

See Also:

[Why are arithmetic script opcodes limited to 4 byte operands?](https://bitcoin.stackexchange.com/questions/49787/why-are-arithmetic-script-opcodes-limited-to-4-byte-operands)

[Arithmetic operations in Bitcoin (OP\_ADD, etc)](https://bitcoin.stackexchange.com/questions/29435/arithmetic-operations-in-bitcoin-op-add-etc)

[Compact integer in Scripts](https://bitcoin.stackexchange.com/questions/38378/compact-integer-in-scripts/46785#46785)

**Tags:** bitcoin-core, script, terminology, encoding, specification


---

## 1 answers

### Answer by username_1 on 2024-05-08. Score: 3 | Accepted answer

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](https://en.wikipedia.org/wiki/Signed_number_representations#Sign%E2%80%93magnitude). 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](https://github.com/bitcoin/bitcoin/blob/74f517b4411d80afa7733c088da961e9c736a97d/src/script/script.h#L348-L379). The Bitcoin Core [test framework has a Python reimplementation](https://github.com/bitcoin/bitcoin/blob/43a66c55ec8770cf7c21112aac9b997f3f2fb704/test/functional/test_framework/script.py#L407-L421) of the serialization. Here is a simplified and highly commented version:

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

```


---

