-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
BIP draft: 64-bit arithmetic opcodes #1538
Open
Christewart
wants to merge
13
commits into
bitcoin:master
Choose a base branch
from
Christewart:2023-09-11-64bit-arith
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 12 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
d0aafd9
WIP
Christewart 54350b5
Add section on overflows
Christewart b11e06e
Add credit to Sanket and Andrew
Christewart 8bbf627
fix code formatting
Christewart 5d52ff3
Fix hyperlinks
Christewart 464ce8e
Add link to elements impl
Christewart 9da332c
Add helper methods
Christewart b5d0fe5
Add conversion opcodes
Christewart 5713905
Remove push4_le as its not used in reference impl
Christewart 1c8a5af
Add specifics for OP_DIV64
Christewart 243db3f
Remove BIP number
Christewart d1c90b0
Remove uncessary sequence requirement
Christewart 5bebdd1
Remove OP_DIV64 from other opcodes
Christewart File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,302 @@ | ||
<pre> | ||
BIP: TBD | ||
Layer: Consensus (soft fork) | ||
Title: 64 bit arithmetic operations | ||
Author: Chris Stewart <stewart.chris1234@gmail.com> | ||
Comments-Summary: No comments yet. | ||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0364 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please refrain from issuing your own BIP numbers. |
||
Status: Draft | ||
Type: Standards Track | ||
Created: 2023-09-11 | ||
License: BSD-3-Clause | ||
</pre> | ||
|
||
==Abstract== | ||
|
||
This BIP describes a new set of arithmetic opcodes (OP_ADD64, OP_SUB64, OP_MUL64, OP_DIV64, OP_NEG64, | ||
OP_LESSTHAN64, OP_LESSTHANOREQUAL64, OP_GREATERTHAN64, OP_GREATERTHANOREQUAL64) | ||
that allows 64 bit signed integer math in the bitcoin protocol. | ||
|
||
This BIP also describes a set of conversion opcodes (OP_SCRIPTNUMTOLE64, OP_LE64TOSCRIPTNUM, OP_LE32TOLE64) | ||
to convert existing bitcoin protocol numbers (CScriptNum) into 4 and 7 byte little endian representations. | ||
|
||
==Summary== | ||
|
||
The arithmetic opcodes (OP_ADD64, OP_SUB64, OP_MUL64, OP_DIV64) behave as follows | ||
|
||
* Fail if less than 2 elements on the stack | ||
* Fail if the stacks top 2 elements are not exactly 8 bytes | ||
* If the operation results in an overflow, push false onto the stack | ||
* If the operation succeeds without overflow, push the result and true onto the stack | ||
|
||
OP_DIV64 | ||
* Fail if less than 2 elements on the stack | ||
* Fail if the stacks top 2 elements are not exactly 8 bytes | ||
* If the stack top is zero (denominator), push false onto the stack | ||
* If the stack top is -1 and the numerator is -9223372036854775808, push false onto the stack | ||
* Calculate the remainder (`r = a % b`) and quotient (`q = a / b`) | ||
* If the remainder is negative, invert it to be positive | ||
* Push the remainder onto the stack | ||
* Push the quotient onto the stack | ||
* Push true onto the stack | ||
|
||
64 bit comparison opcodes (OP_LESSTHAN64, OP_LESSTHANOREQUAL64, OP_GREATERTHAN64, OP_GREATERTHANOREQUAL64) | ||
* Fail if less than 2 elements on the stack | ||
* Fail if the stacks top 2 elements are not exactly 8 bytes | ||
* Push the boolean result of the comparison onto the stack | ||
|
||
OP_NEG64 | ||
* Fail if less than 1 element on the stack | ||
* Fail if the stacks top is not exactly 8 bytes | ||
* If the operation results in an overflow, push false onto the stack | ||
* Push the result of negating the stack top onto the stack and push true onto the stack | ||
|
||
OP_SCRIPTNUMTOLE64 | ||
* Fail if less than 1 element on the stack | ||
* Interpret the stack top as a CScriptNum | ||
* Push the 8 byte little endian representation of the number onto the stack | ||
|
||
OP_LE64TOSCRIPTNUM | ||
* Fail if less than 1 element on the stack | ||
* Fail if the stack top is not exactly 8 bytes | ||
* Interpret the stack top as a 8 byte little endian number | ||
* Fail if the 8 byte little endian number would overflow CScriptNum | ||
* Push the byte representation of CScriptNum onto the stack | ||
|
||
OP_LE32TOLE64 | ||
* Fail if less than 1 element on the stack | ||
* Fail if the stack top is not exactly 4 bytes in size | ||
* Interpret the stack top as a 32 bit little endian number | ||
* Push the 8 byte little endian number onto the stack | ||
|
||
|
||
==Motivation== | ||
|
||
64 bit arithmetic operations are required to support arithmetic on satoshi values. | ||
Math on satoshis required precision of 51 bits. Many bitcoin protocol proposals - such as covenants - | ||
require Script access to output values. To support the full range of possible output values | ||
we need 64 bit precision. | ||
|
||
===OP_INOUT_AMOUNT=== | ||
|
||
[https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-September/019420.html OP_INOUT_AMOUNT] is | ||
part of the [https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-September/019419.html OP_TAPLEAFUPDATE_VERIFY] soft fork proposal. | ||
This opcode pushes two values onto the stack, the amount from this | ||
input's utxo, and the amount in the corresponding output, and then expect | ||
anyone using OP_TLUV to use maths operators to verify that funds are being | ||
appropriately retained in the updated scriptPubKey. | ||
|
||
Since the value of the utxos can be up to 51 bits in value, we require 64 bit | ||
arithmetic operations. | ||
|
||
|
||
==Overflows== | ||
|
||
When dealing with overflows, we explicitly return the success bit as a CScriptNum at the top of the stack and the result being the second element from the top. If the operation overflows, first the operands are pushed onto the stack followed by success bit. [a_second a_top] overflows, the stack state after the operation is [a_second a_top 0] and if the operation does not overflow, the stack state is [res 1]. | ||
|
||
This gives the user flexibility to deal if they script to have overflows using OP_IF\OP_ELSE or OP_VERIFY the success bit if they expect that operation would never fail. When defining the opcodes which can fail, we only define the success path, and assume the overflow behavior as stated above. | ||
|
||
==Detailed Specification== | ||
|
||
Refer to the reference implementation, reproduced below, for the precise | ||
semantics and detailed rationale for those semantics. | ||
|
||
<source lang="cpp"> | ||
|
||
|
||
static inline int64_t cast_signed64(uint64_t v) | ||
{ | ||
uint64_t int64_min = static_cast<uint64_t>(std::numeric_limits<int64_t>::min()); | ||
if (v >= int64_min) | ||
return static_cast<int64_t>(v - int64_min) + std::numeric_limits<int64_t>::min(); | ||
return static_cast<int64_t>(v); | ||
} | ||
|
||
static inline int64_t read_le8_signed(const unsigned char* ptr) | ||
{ | ||
return cast_signed64(ReadLE64(ptr)); | ||
} | ||
|
||
static inline void push8_le(std::vector<valtype>& stack, uint64_t v) | ||
{ | ||
uint64_t v_le = htole64(v); | ||
stack.emplace_back(reinterpret_cast<unsigned char*>(&v_le), reinterpret_cast<unsigned char*>(&v_le) + sizeof(v_le)); | ||
} | ||
|
||
case OP_ADD64: | ||
case OP_SUB64: | ||
case OP_MUL64: | ||
case OP_DIV64: | ||
case OP_LESSTHAN64: | ||
case OP_LESSTHANOREQUAL64: | ||
case OP_GREATERTHAN64: | ||
case OP_GREATERTHANOREQUAL64: | ||
{ | ||
// Opcodes only available post tapscript | ||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); | ||
|
||
if (stack.size() < 2) | ||
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); | ||
|
||
valtype& vcha = stacktop(-2); | ||
valtype& vchb = stacktop(-1); | ||
if (vchb.size() != 8 || vcha.size() != 8) | ||
return set_error(serror, SCRIPT_ERR_EXPECTED_8BYTES); | ||
|
||
int64_t b = read_le8_signed(vchb.data()); | ||
int64_t a = read_le8_signed(vcha.data()); | ||
|
||
switch(opcode) | ||
{ | ||
case OP_ADD64: | ||
if ((a > 0 && b > std::numeric_limits<int64_t>::max() - a) || | ||
(a < 0 && b < std::numeric_limits<int64_t>::min() - a)) | ||
stack.push_back(vchFalse); | ||
else { | ||
popstack(stack); | ||
popstack(stack); | ||
push8_le(stack, a + b); | ||
stack.push_back(vchTrue); | ||
} | ||
break; | ||
case OP_SUB64: | ||
if ((b > 0 && a < std::numeric_limits<int64_t>::min() + b) || | ||
(b < 0 && a > std::numeric_limits<int64_t>::max() + b)) | ||
stack.push_back(vchFalse); | ||
else { | ||
popstack(stack); | ||
popstack(stack); | ||
push8_le(stack, a - b); | ||
stack.push_back(vchTrue); | ||
} | ||
break; | ||
case OP_MUL64: | ||
if ((a > 0 && b > 0 && a > std::numeric_limits<int64_t>::max() / b) || | ||
(a > 0 && b < 0 && b < std::numeric_limits<int64_t>::min() / a) || | ||
(a < 0 && b > 0 && a < std::numeric_limits<int64_t>::min() / b) || | ||
(a < 0 && b < 0 && b < std::numeric_limits<int64_t>::max() / a)) | ||
stack.push_back(vchFalse); | ||
else { | ||
popstack(stack); | ||
popstack(stack); | ||
push8_le(stack, a * b); | ||
stack.push_back(vchTrue); | ||
} | ||
break; | ||
case OP_DIV64: | ||
{ | ||
if (b == 0 || (b == -1 && a == std::numeric_limits<int64_t>::min())) { stack.push_back(vchFalse); break; } | ||
int64_t r = a % b; | ||
int64_t q = a / b; | ||
if (r < 0 && b > 0) { r += b; q-=1;} // ensures that 0<=r<|b| | ||
else if (r < 0 && b < 0) { r -= b; q+=1;} // ensures that 0<=r<|b| | ||
popstack(stack); | ||
popstack(stack); | ||
push8_le(stack, r); | ||
push8_le(stack, q); | ||
stack.push_back(vchTrue); | ||
} | ||
break; | ||
break; | ||
case OP_LESSTHAN64: popstack(stack); popstack(stack); stack.push_back( (a < b) ? vchTrue : vchFalse ); break; | ||
case OP_LESSTHANOREQUAL64: popstack(stack); popstack(stack); stack.push_back( (a <= b) ? vchTrue : vchFalse ); break; | ||
case OP_GREATERTHAN64: popstack(stack); popstack(stack); stack.push_back( (a > b) ? vchTrue : vchFalse ); break; | ||
case OP_GREATERTHANOREQUAL64: popstack(stack); popstack(stack); stack.push_back( (a >= b) ? vchTrue : vchFalse ); break; | ||
default: assert(!"invalid opcode"); break; | ||
} | ||
} | ||
break; | ||
case OP_NEG64: | ||
{ | ||
// Opcodes only available post tapscript | ||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); | ||
|
||
if (stack.size() < 1) | ||
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); | ||
|
||
valtype& vcha = stacktop(-1); | ||
if (vcha.size() != 8) | ||
return set_error(serror, SCRIPT_ERR_EXPECTED_8BYTES); | ||
|
||
int64_t a = read_le8_signed(vcha.data()); | ||
if (a == std::numeric_limits<int64_t>::min()) { stack.push_back(vchFalse); break; } | ||
|
||
popstack(stack); | ||
push8_le(stack, -a); | ||
stack.push_back(vchTrue); | ||
} | ||
break; | ||
|
||
case OP_SCRIPTNUMTOLE64: | ||
{ | ||
// Opcodes only available post tapscript | ||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); | ||
|
||
if (stack.size() < 1) | ||
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); | ||
|
||
int64_t num = CScriptNum(stacktop(-1), fRequireMinimal).getint(); | ||
popstack(stack); | ||
push8_le(stack, num); | ||
} | ||
break; | ||
case OP_LE64TOSCRIPTNUM: | ||
{ | ||
// Opcodes only available post tapscript | ||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); | ||
|
||
if (stack.size() < 1) | ||
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); | ||
|
||
valtype& vchnum = stacktop(-1); | ||
if (vchnum.size() != 8) | ||
return set_error(serror, SCRIPT_ERR_EXPECTED_8BYTES); | ||
valtype vchscript_num = CScriptNum(read_le8_signed(vchnum.data())).getvch(); | ||
if (vchscript_num.size() > CScriptNum::nDefaultMaxNumSize) { | ||
return set_error(serror, SCRIPT_ERR_ARITHMETIC64); | ||
} else { | ||
popstack(stack); | ||
stack.push_back(std::move(vchscript_num)); | ||
} | ||
} | ||
break; | ||
case OP_LE32TOLE64: | ||
{ | ||
// Opcodes only available post tapscript | ||
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); | ||
|
||
if (stack.size() < 1) | ||
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); | ||
|
||
valtype& vchnum = stacktop(-1); | ||
if (vchnum.size() != 4) | ||
return set_error(serror, SCRIPT_ERR_ARITHMETIC64); | ||
uint32_t num = ReadLE32(vchnum.data()); | ||
popstack(stack); | ||
push8_le(stack, static_cast<int64_t>(num)); | ||
} | ||
break; | ||
</source> | ||
|
||
https://github.com/Christewart/bitcoin/commits/64bit-arith | ||
|
||
==Deployment== | ||
|
||
todo | ||
|
||
==Credits== | ||
|
||
This work is borrowed from work done on the elements project, with implementations done by Sanket Kanjalkar and Andrew Poelstra. | ||
|
||
https://github.com/ElementsProject/elements/pull/1020/files | ||
|
||
==References== | ||
|
||
https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-September/019419.html | ||
|
||
https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-September/019420.html | ||
|
||
==Copyright== | ||
|
||
This document is placed in the public domain. | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe: