# 64 bit arithmetic soft fork

**URL:** <https://delvingbitcoin.org/t/64-bit-arithmetic-soft-fork/397>\
**Category:** Protocol Design\
**Tags:** softfork\
**Created:** [January 10, 2024, 3:36pm UTC](https://delvingbitcoin.org/t/64-bit-arithmetic-soft-fork/397 "2024-01-10T15:36:12Z")\
**Posts on this page:** 12\
**Page:** 3

<div class="post-metadata">

**Author:** ![ajtowns](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/ajtowns/32/417_2.png) [@ajtowns](https://delvingbitcoin.org/u/ajtowns)\
**Post date:** [February 4, 2024, 7:30am UTC](https://delvingbitcoin.org/t/64-bit-arithmetic-soft-fork/397/42 "2024-02-04T07:30:50Z")

</div>

If your goal is to allow varying size big nums so that you can do maths in the secp scalar field, you don’t want to enforce minimaldata – otherwise if you take the hash of a blockheader to generate a scalar, and naturally end up with leading zeroes, you have to strip those zeroes before you can do any maths on that hash. Stripping an arbitrary number of zeroes is then also awkward with minimaldata rules, though not impossible.

(I think a minimaldata variant that only applies to values pulled directly from the witness stack, but not from program pushes or the results of operations) would be interesting)

---

<div class="post-metadata">

**Author:** ![dgpv](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/dgpv/32/75_2.png) [@dgpv](https://delvingbitcoin.org/u/dgpv)\
**Post date:** [February 4, 2024, 7:43am UTC](https://delvingbitcoin.org/t/64-bit-arithmetic-soft-fork/397/43 "2024-02-04T07:43:53Z")

</div>

> [@ajtowns](#):
>
> you have to strip those zeroes before you can do any maths on that hash

if there is `FROMFIXNUM` opcode that takes the size of fixed-size integer as one argument and byte blob of that size as another argument and turns it into a variable-size integer it will be easy (and also `BYTEREV` to deal with endianness)

---

<div class="post-metadata">

**Author:** ![Chris\_Stewart\_5](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/chris_stewart_5/32/296_2.png) [@Chris\_Stewart\_5](https://delvingbitcoin.org/u/Chris_Stewart_5)\
**Post date:** [February 12, 2024, 3:02pm UTC](https://delvingbitcoin.org/t/64-bit-arithmetic-soft-fork/397/44 "2024-02-12T15:02:02Z")

</div>

If anyone is interested to see how this set of proposals could be used by other op codes please see my new post about `OP_INOUT_AMOUNT`

> [@Op\_inout\_amount](https://delvingbitcoin.org/t/op-inout-amount/549):
>
> Hi all, I’ve attempted to implement OP\_INOUT\_AMOUNT. The purpose of OP\_INOUT\_AMOUNT is to push the input value and a set of output values onto the Script interpreter stack. Here is my draft BIP: [https://github.com/Christewart/bips/blob/92c108136a0400b3a2fd66ea6c291ec317ee4a01/bip-op-inout-amount.mediawiki](https://github.com/Christewart/bips/blob/92c108136a0400b3a2fd66ea6c291ec317ee4a01/bip-op-inout-amount.mediawiki) Here is the implementation. [https://github.com/Christewart/bitcoin/tree/op-inout-amount](https://github.com/Christewart/bitcoin/tree/op-inout-amount) One purpose of implementing this is to see how this works in conjunction with my [64bit op code PR](https://github.com/bitcoin/bitcoin/pull/29221). …

Here is a direct link to a few test scripts in the python test framework

[https://github.com/Christewart/bitcoin/commit/c617c5c3b0d21499b196184b5279b45627060cb5#diff-2e2556d1d2e3a75b547bbb86bd68567f275ba6d17b9bf4541facef9f2b1cfb30R124](https://github.com/Christewart/bitcoin/commit/c617c5c3b0d21499b196184b5279b45627060cb5#diff-2e2556d1d2e3a75b547bbb86bd68567f275ba6d17b9bf4541facef9f2b1cfb30R124)

---

<div class="post-metadata">

**Author:** ![halseth](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/halseth/32/198_2.png) [@halseth](https://delvingbitcoin.org/u/halseth)\
**Post date:** [February 27, 2024, 2:12pm UTC](https://delvingbitcoin.org/t/64-bit-arithmetic-soft-fork/397/45 "2024-02-27T14:12:41Z")

</div>

Nice!

How would the IN\_OUT opcode change if one decided to go with 64-bit ScriptNum type encoding instead of the LE variant?

I guess not a large change since you would just pushed a ScriptNum encoded integer on the stack instead?

```c++
  CScriptNum bn(fundingAmount);
  stack.push_back(bn.getvch());

```

---

<div class="post-metadata">

**Author:** ![sjors](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/sjors/32/59_2.png) [@sjors](https://delvingbitcoin.org/u/sjors)\
**Post date:** [February 28, 2024, 10:22am UTC](https://delvingbitcoin.org/t/64-bit-arithmetic-soft-fork/397/46 "2024-02-28T10:22:38Z")

</div>

> [@Chris\_Stewart\_5](#):
>
> This means this proposal would increase the blockchain size by is `0.17%` since the genesis block.

Another metric to look at is who’s paying for those extra bytes. So for a set of typical transactions (in existence now and expected in the future), how much do those increase?

---

<div class="post-metadata">

**Author:** ![Chris\_Stewart\_5](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/chris_stewart_5/32/296_2.png) [@Chris\_Stewart\_5](https://delvingbitcoin.org/u/Chris_Stewart_5)\
**Post date:** [February 28, 2024, 1:27pm UTC](https://delvingbitcoin.org/t/64-bit-arithmetic-soft-fork/397/47 "2024-02-28T13:27:22Z")

</div>

I’m going to code this up to confirm ergonomics - so mistakes are likely in this post. Call them out if you see them. Here is my understanding without actually writing the code yet

If we were to continue with `CScriptNum`, as [my `OP_INOUT_AMOUNT` implementation](https://github.com/Christewart/bitcoin/tree/op-inout-amount) works currently

1. Read `int64_t` representing satoshis from `BaseTransactionSignatureChecker.GetTransactionData()`
2. Convert the `int64_t` into a minimally encoded `CScriptNum`. I don’t think this necessarily has to be done by an op code, could be done in the impl of `OP_INOUT_AMOUNT` itself
3. Call `CScriptNum` constructor, [modifying the `nMaxNumSize` parameter](https://github.com/Christewart/bitcoin/blob/c617c5c3b0d21499b196184b5279b45627060cb5/src/script/script.h#L265) to support 8 bytes.
4. Push `CScriptNum` onto stack
5. Wherever the satoshi value on the stack top is consumed by another op code, we need to figure out how to allow for `nMaxNumSize` to be 8 bytes.

As an example for step `5`, lets assume we are using _THE OLD_ (pre-64bit) numeric op codes

You see we interpret the stack top as `CScriptNum`, however that `CScriptNum` has a `nMaxNumSize=4` rather than 8 bytes. This leads to an [overflow exception](https://github.com/Christewart/bitcoin/blob/c617c5c3b0d21499b196184b5279b45627060cb5/src/script/script.h#L268) being thrown by `CScriptNum`. This same problem applies to any opcode (another example is `OP_WITHIN`) that uses `CScriptNum` to interpret the stack top.

> <https://github.com/Christewart/bitcoin/blob/c617c5c3b0d21499b196184b5279b45627060cb5/src/script/interpreter.cpp#L983>

---

<div class="post-metadata">

**Author:** ![Chris\_Stewart\_5](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/chris_stewart_5/32/296_2.png) [@Chris\_Stewart\_5](https://delvingbitcoin.org/u/Chris_Stewart_5)\
**Post date:** [March 19, 2024, 2:07pm UTC](https://delvingbitcoin.org/t/64-bit-arithmetic-soft-fork/397/48 "2024-03-19T14:07:48Z")

</div>

Hi everyone, we have a bitcoin core pr review club tomorrow (3/20/2024) at 17:00 UTC for this implementation.

> **[Implement 64 bit arithmetic op codes in the Script interpreter](https://bitcoincore.reviews/29221)**
>
> Many future extensions of the bitcoin protocol - such as OP\_TLUV - want to create smart contracts based on the amount of satoshis in a bitcoin output.

Happy to answer any questions about the PR, and talk through the ‘Design Questions’ section that I think still have open questions. Hope to see some of yall tomorrow at 17:00 UTC.

---

<div class="post-metadata">

**Author:** ![Chris\_Stewart\_5](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/chris_stewart_5/32/296_2.png) [@Chris\_Stewart\_5](https://delvingbitcoin.org/u/Chris_Stewart_5)\
**Post date:** [June 2, 2024, 4:50pm UTC](https://delvingbitcoin.org/t/64-bit-arithmetic-soft-fork/397/49 "2024-06-02T16:50:39Z")

</div>

Hi everyone, a new update:

> **[GitHub - Christewart/bitcoin at 64bit-arith-implicit](https://github.com/Christewart/bitcoin/tree/64bit-arith-implicit)**
>
> 64bit-arith-implicit

This version of 64bit arithmetic

- Removes 64bit specific opcodes in favor repurposing existing opcodes (`OP_ADD64` → `OP_ADD`, `OP_SUB64` → `OP_SUB`, `OP_MUL64` → `OP_MUL`, `OP_DIV64` → `OP_DIV` etc).
- Every opcode in `interpreter.cpp` that use to accept a `CScriptNum` input now accepts a `int64_t` stack parameter. For instance, `OP_1ADD` accepts a `int64_t` stack top argument, and pushes a `int64_t` back onto the stack along with a bool indicating if the `OP_1ADD` execution was successful.
- Removes casting opcodes (`OP_SCRIPTNUMTOLE64`, `OP_LE64TOSCRIPTNUM`, `OP_LE32TOLE64`)

I think this PR provides for a better developer experience as Script developers

1. No longer have to think about which opcode to use (`OP_ADD` or `OP_ADD64`, `OP_LESSTHAN` or `OP_LESSTHAN64`, etc)
2. No longer have to worry about casting the stack top with previous casting op codes (`OP_SCRIPTNUMTOLE64`, `OP_LE64TOSCRIPTNUM`, `OP_LE32TOLE64`)

This 64bit implementation would mean existing Scripts that typically use _constant_ numeric arguments – such as `OP_CHECKLOCKTIMEVERIFY`/`OP_CHECKSEQUENCEVERIFY` would need to be rewritten to pass in 8 byte parameters rather than 5 byte parameters.

This PR heavily relies on pattern matching on `SigVersion` to determine what the implementation of the opcode should do.

For instance, here is the implementation of `OP_DEPTH`

> <https://github.com/Christewart/bitcoin/blob/019907039c7d342f3c1fe3c7f3dd9db879661c9e/src/script/interpreter.cpp#L1185>

In the future, if we want to redefine semantics of `OP_DEPTH` we can now pattern match on the `SigVersion` and substitute the new implementation.

I think this provides us a nice framework for upgrading the interpreter in the future. We will have the compiler give errors in places where we aren’t handling a new `SigVersion` introduced in the codebase (exhaustiveness checks), and force us to handle that case.

---

<div class="post-metadata">

**Author:** ![ajtowns](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/ajtowns/32/417_2.png) [@ajtowns](https://delvingbitcoin.org/u/ajtowns)\
**Post date:** [June 2, 2024, 11:29pm UTC](https://delvingbitcoin.org/t/64-bit-arithmetic-soft-fork/397/50 "2024-06-02T23:29:04Z")

</div>

> [@ajtowns](#):
>
> you get an overflow indicator in every result

FWIW, the big concern I have with this is people writing scripts where they don’t think an overflow is possible, so they just do an OP\_DROP for the overflow indicator, and then someone thinks a bit harder, and figures out how to steal money via an overflow, and then they do exactly that. That’s arguably easily mitigated: just use `OP_VERIFY` to guarantee there wasn’t an overflow, but I noticed that an example script in [review club](https://bitcoincore.reviews/29221#l-86) used the more obvious DROP:

```auto
<Chris_Stewart_5> Script: 0x000e876481700000 0x000e876481700000 OP_ADD64 OP_DROP OP_LE64TOSCRIPTNUM OP_SIZE OP_8 OP_EQUALVERIFY OP_SCRIPTNUMTOLE64 0x001d0ed902e00000 OP_EQUAL

```

Worries me a bit when the obvious way of doing something (“this won’t ever overflow, so just drop it”) is risky.

You could imagine introducing two opcodes: “OP\_ADD64” and “OP\_ADD64VERIFY” the latter of which does an implicit VERIFY, and hence fails the script if there was overflow; but that would effectively be the existing behaviour of OP\_ADD. So I guess what I’m saying is: maybe consider an approach along the lines that sipa suggested:

> [@sipa](#):
>
> I was suggesting not changing any semantics at all; only changing the acceptable range of inputs to existing opcodes.

where you change ADD to work with 64bit numbers (in whatever format), and add a new `ADD_OF`, `MUL_OF` (here OF implies “flag on stack” instead of “link in bio”)

---

<div class="post-metadata">

**Author:** ![Chris\_Stewart\_5](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/chris_stewart_5/32/296_2.png) [@Chris\_Stewart\_5](https://delvingbitcoin.org/u/Chris_Stewart_5)\
**Post date:** [June 3, 2024, 11:28am UTC](https://delvingbitcoin.org/t/64-bit-arithmetic-soft-fork/397/51 "2024-06-03T11:28:52Z")

</div>

> [@ajtowns](#):
>
> Worries me a bit when the obvious way of doing something (“this won’t ever overflow, so just drop it”) is risky.

Unfortunately as language designers we can only give people tools to build safe programs, we can’t force them to _always_ use those tools correctly. There will always be developers that figure out innovative ways to unsafely use your PL.

For those that _want_ to be writing safe programs, we now have the tools available to them do so. Those don’t exist at the moment.

---

<div class="post-metadata">

**Author:** ![Chris\_Stewart\_5](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/chris_stewart_5/32/296_2.png) [@Chris\_Stewart\_5](https://delvingbitcoin.org/u/Chris_Stewart_5)\
**Post date:** [June 18, 2024, 12:05pm UTC](https://delvingbitcoin.org/t/64-bit-arithmetic-soft-fork/397/52 "2024-06-18T12:05:19Z")

</div>

Here is the `CScriptNum` extension prototype ([PR against master](https://github.com/Christewart/bitcoin/pull/3)):

> **[GitHub - Christewart/bitcoin at 64bit-arith-cscriptnum](https://github.com/Christewart/bitcoin/tree/64bit-arith-cscriptnum)**
>
> 64bit-arith-cscriptnum

## Highlights

- Re-enables the `OP_MUL` and `OP_DIV` opcodes
- Support 8 byte computation w/ arithmetic and comparison op codes.
- Doesn’t add any new opcodes, rather repurposes existing opcodes based on `SigVersion`.
- Changes the underlying impl type in `CScriptNum` from `int64_t` → `__int128_t`
- Preserves behavior of the old `CScriptNum` (variable length encoding, allows overflow results, but no computation on overflowed results).

For those that may not be familiar with the existing behavior of `CScriptNum`, I would suggest reading [this comment in `script.h`](https://github.com/Christewart/bitcoin/blob/3ec4552f572ec1eff56534c2034293bb7c053c25/src/script/script.h#L219)

```auto
/**
 * Numeric opcodes (OP_1ADD, etc) are restricted to operating on 4-byte integers.
 * The semantics are subtle, though: operands must be in the range [-2^31 +1...2^31 -1],
 * but results may overflow (and are valid as long as they are not used in a subsequent
 * numeric operation). CScriptNum enforces those semantics by storing results as
 * an int64 and allowing out-of-range values to be returned as a vector of bytes but
 * throwing an exception if arithmetic is done or the result is interpreted as an integer.
 */

```

The intention of this branch is to retain this behavior, only extending the supported range of values to `[-2^63 +1...2^63 -1]`.

## Consensus risk

This PR changes the behavior of `CScriptNum`. For instance, [the constructor for `CScriptNum`](https://github.com/Christewart/bitcoin/blob/3ec4552f572ec1eff56534c2034293bb7c053c25/src/script/script.h#L229) now takes a `__int128_t` as a parameter rather than `int64_t`. This constructor is called for _all_ `SigVersion` (not just `SigVersion::TAPSCRIPT_64BIT`). This seems like it could lead to some consensus risk with old nodes if someone crafts a specific transaction using segwit v0 or tapscript that exceeds `std::numeric_limits<int64_t>::max()` but is less than `std::numeric_limits<__int128_t>::max()`.

### More problems with `__int128_t`

I chose to use `__in128_t` as it seemed like the logical thing to do to extend the existing behavior for support for overflow values.

I’m not an expert on c++, but it appears that `__int128_t` is not supported properly on windows. I’m seeing [CI failures](https://github.com/Christewart/bitcoin/actions/runs/9553273776/job/26331741590?pr=3#step:20:6261) on windows that look like this

```auto
D:\a\bitcoin\bitcoin\src\script\script.h(229,25): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int [D:\a\bitcoin\bitcoin\build_msvc\libbitcoin_qt\libbitcoin_qt.vcxproj]
D:\a\bitcoin\bitcoin\src\script\script.h(229,41): error C2143: syntax error: missing ',' before '&' [D:\a\bitcoin\bitcoin\build_msvc\libbitcoin_qt\libbitcoin_qt.vcxproj]
D:\a\bitcoin\bitcoin\src\script\script.h(290,33): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int [D:

```

Perhaps there is a better way to implement this while avoiding using `__int128_t`. I’m open to ideas.

---

<div class="post-metadata">

**Author:** ![ajtowns](https://delvingbitcoin.org/user_avatar/delvingbitcoin.org/ajtowns/32/417_2.png) [@ajtowns](https://delvingbitcoin.org/u/ajtowns)\
**Post date:** [June 20, 2024, 4:25pm UTC](https://delvingbitcoin.org/t/64-bit-arithmetic-soft-fork/397/53 "2024-06-20T16:25:30Z")

</div>

> [@Chris\_Stewart\_5](#):
>
> Perhaps there is a better way to implement this while avoiding using `__int128_t`. I’m open to ideas.

For comparison, crypto/muhash.h uses `__int128_t` conditionally.

Could maybe use `uint64_t absval; bool negated;` instead, with various manual checks to see if things end up out of bounds? Otherwise, just doing a bignum class might make sense (like arith\_uint256).

[Previous page](https://delvingbitcoin.org/t/64-bit-arithmetic-soft-fork/397.md?page=2)
