From d0aafd90012bcaacbbf8b49962f93e2cff1db8e6 Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Tue, 12 Sep 2023 10:10:48 -0500 Subject: [PATCH 01/14] WIP --- bip-0364.mediawiki | 195 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 bip-0364.mediawiki diff --git a/bip-0364.mediawiki b/bip-0364.mediawiki new file mode 100644 index 0000000000..7ace820945 --- /dev/null +++ b/bip-0364.mediawiki @@ -0,0 +1,195 @@ +
+  BIP: ??? (suggestion: 364) 
+  Layer: Consensus (soft fork)
+  Title: 64 bit arithmetic operations 
+  Author: Chris Stewart 
+  Comments-Summary: No comments yet.
+  Comments-URI: ??? 
+  Status: Draft 
+  Type: Standards Track
+  Created: 2023-09-11
+  License: PD
+
+ +==Abstract== + +This BIP describes a new set of 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. + +==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 +* the nSequence field of the txin is 0xffffffff; + +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 (stack top == std::numeric_limits::min()), push false onto the stack +* Push the result of negating the stack top onto the stack and push true onto the stack + +The nLockTime field in a transaction prevents the transaction from being mined +until either a certain block height, or block time, has been reached. By +comparing the argument to CHECKLOCKTIMEVERIFY against the nLockTime field, we +indirectly verify that the desired block height or block time has been reached; +until that block height or block time has been reached the transaction output +remains unspendable. + +==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=== + +[OP_INOUT_AMOUNT](https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-September/019420.html) is +part of the [OP_TAPLEAFUPDATE_VERIFY](https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-September/019419.html) 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. + + +==Detailed Specification== + +Refer to the reference implementation, reproduced below, for the precise +semantics and detailed rationale for those semantics. + + 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::max() - a) || + (a < 0 && b < std::numeric_limits::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::min() + b) || + (b < 0 && a > std::numeric_limits::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::max() / b) || + (a > 0 && b < 0 && b < std::numeric_limits::min() / a) || + (a < 0 && b > 0 && a < std::numeric_limits::min() / b) || + (a < 0 && b < 0 && b < std::numeric_limits::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::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::min()) { stack.push_back(vchFalse); break; } + + popstack(stack); + push8_le(stack, -a); + stack.push_back(vchTrue); + } + break; + +https://github.com/Christewart/bitcoin/commits/64bit-arith + +==Deployment== + +todo + +==Credits== + +todo + +==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. + From 54350b53c5308557c89d7a45a249efa261d55f9b Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Wed, 13 Sep 2023 09:13:55 -0500 Subject: [PATCH 02/14] Add section on overflows --- bip-0364.mediawiki | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/bip-0364.mediawiki b/bip-0364.mediawiki index 7ace820945..0424668a0d 100644 --- a/bip-0364.mediawiki +++ b/bip-0364.mediawiki @@ -38,13 +38,6 @@ OP_NEG64 * If the operation results in an overflow (stack top == std::numeric_limits::min()), push false onto the stack * Push the result of negating the stack top onto the stack and push true onto the stack -The nLockTime field in a transaction prevents the transaction from being mined -until either a certain block height, or block time, has been reached. By -comparing the argument to CHECKLOCKTIMEVERIFY against the nLockTime field, we -indirectly verify that the desired block height or block time has been reached; -until that block height or block time has been reached the transaction output -remains unspendable. - ==Motivation== 64 bit arithmetic operations are required to support arithmetic on satoshi values. @@ -65,6 +58,12 @@ 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 From b11e06e1e812ed739d20aaa77787cd9ac266da13 Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Wed, 13 Sep 2023 09:19:23 -0500 Subject: [PATCH 03/14] Add credit to Sanket and Andrew --- bip-0364.mediawiki | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bip-0364.mediawiki b/bip-0364.mediawiki index 0424668a0d..4851cc1043 100644 --- a/bip-0364.mediawiki +++ b/bip-0364.mediawiki @@ -181,7 +181,7 @@ todo ==Credits== -todo +This work is borrowed from work done on the elements project, with implementations done by Sanket Kanjalkar and Andrew Poelstra. ==References== From 8bbf62764d27e970e175dc8e4aa810ec1b7420c1 Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Wed, 13 Sep 2023 09:27:26 -0500 Subject: [PATCH 04/14] fix code formatting --- bip-0364.mediawiki | 260 ++++++++++++++++++++++++++------------------- 1 file changed, 149 insertions(+), 111 deletions(-) diff --git a/bip-0364.mediawiki b/bip-0364.mediawiki index 4851cc1043..1764009af7 100644 --- a/bip-0364.mediawiki +++ b/bip-0364.mediawiki @@ -1,14 +1,14 @@
-  BIP: ??? (suggestion: 364) 
+  BIP: 364
   Layer: Consensus (soft fork)
-  Title: 64 bit arithmetic operations 
+  Title: 64 bit arithmetic operations
   Author: Chris Stewart 
   Comments-Summary: No comments yet.
-  Comments-URI: ??? 
-  Status: Draft 
+  Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0364
+  Status: Draft
   Type: Standards Track
   Created: 2023-09-11
-  License: PD
+  License: BSD-3-Clause
 
==Abstract== @@ -35,7 +35,7 @@ The arithmetic opcodes (OP_ADD64, OP_SUB64, OP_MUL64, OP_DIV64) behave as follow 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 (stack top == std::numeric_limits::min()), push false onto the stack +* 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 ==Motivation== @@ -68,111 +68,149 @@ This gives the user flexibility to deal if they script to have overflows using O Refer to the reference implementation, reproduced below, for the precise semantics and detailed rationale for those semantics. - - 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::max() - a) || - (a < 0 && b < std::numeric_limits::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::min() + b) || - (b < 0 && a > std::numeric_limits::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::max() / b) || - (a > 0 && b < 0 && b < std::numeric_limits::min() / a) || - (a < 0 && b > 0 && a < std::numeric_limits::min() / b) || - (a < 0 && b < 0 && b < std::numeric_limits::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::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::min()) { stack.push_back(vchFalse); break; } - - popstack(stack); - push8_le(stack, -a); - stack.push_back(vchTrue); - } - break; - + + + 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::max () - a) + || (a < 0 && b < std::numeric_limits::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::min () + b) + || (b < 0 && a > std::numeric_limits::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::max () / b) + || (a > 0 && b < 0 + && b < std::numeric_limits::min () / a) + || (a < 0 && b > 0 + && a < std::numeric_limits::min () / b) + || (a < 0 && b < 0 + && b < std::numeric_limits::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::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; + 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::min ()) + { + stack.push_back (vchFalse); + break; + } + popstack (stack); + push8_le (stack, -a); + stack.push_back (vchTrue); + } + break; + + https://github.com/Christewart/bitcoin/commits/64bit-arith ==Deployment== From 5d52ff32123b1aa82a411c0e54a0b2ab7ab614ad Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Wed, 13 Sep 2023 14:56:25 -0500 Subject: [PATCH 05/14] Fix hyperlinks --- bip-0364.mediawiki | 245 +++++++++++++++++++-------------------------- 1 file changed, 105 insertions(+), 140 deletions(-) diff --git a/bip-0364.mediawiki b/bip-0364.mediawiki index 1764009af7..d93a6e3f32 100644 --- a/bip-0364.mediawiki +++ b/bip-0364.mediawiki @@ -47,8 +47,8 @@ we need 64 bit precision. ===OP_INOUT_AMOUNT=== -[OP_INOUT_AMOUNT](https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-September/019420.html) is -part of the [OP_TAPLEAFUPDATE_VERIFY](https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-September/019419.html) soft fork proposal. +[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 @@ -70,145 +70,109 @@ Refer to the reference implementation, reproduced below, for the precise semantics and detailed rationale for those semantics. - 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::max () - a) - || (a < 0 && b < std::numeric_limits::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::min () + b) - || (b < 0 && a > std::numeric_limits::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::max () / b) - || (a > 0 && b < 0 - && b < std::numeric_limits::min () / a) - || (a < 0 && b > 0 - && a < std::numeric_limits::min () / b) - || (a < 0 && b < 0 - && b < std::numeric_limits::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::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); +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::max() - a) || + (a < 0 && b < std::numeric_limits::min() - a)) + stack.push_back(vchFalse); + else { + popstack(stack); + popstack(stack); + push8_le(stack, a + b); + stack.push_back(vchTrue); } - 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::min ()) - { - stack.push_back (vchFalse); - break; - } - popstack (stack); - push8_le (stack, -a); - stack.push_back (vchTrue); - } - break; + break; + case OP_SUB64: + if ((b > 0 && a < std::numeric_limits::min() + b) || + (b < 0 && a > std::numeric_limits::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::max() / b) || + (a > 0 && b < 0 && b < std::numeric_limits::min() / a) || + (a < 0 && b > 0 && a < std::numeric_limits::min() / b) || + (a < 0 && b < 0 && b < std::numeric_limits::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::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::min()) { stack.push_back(vchFalse); break; } + + popstack(stack); + push8_le(stack, -a); + stack.push_back(vchTrue); +} +break; https://github.com/Christewart/bitcoin/commits/64bit-arith @@ -224,6 +188,7 @@ This work is borrowed from work done on the elements project, with implementatio ==References== https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-September/019419.html + https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-September/019420.html ==Copyright== From 464ce8eb4d119f1ab26a57ca654902066360831a Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Mon, 8 Jan 2024 10:45:28 -0600 Subject: [PATCH 06/14] Add link to elements impl --- bip-0364.mediawiki | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bip-0364.mediawiki b/bip-0364.mediawiki index d93a6e3f32..bc2b846729 100644 --- a/bip-0364.mediawiki +++ b/bip-0364.mediawiki @@ -185,6 +185,8 @@ todo 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 From 9da332cefdd3e73003864e838a4033ea1b74ec3f Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Mon, 8 Jan 2024 10:51:37 -0600 Subject: [PATCH 07/14] Add helper methods --- bip-0364.mediawiki | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/bip-0364.mediawiki b/bip-0364.mediawiki index bc2b846729..a61e27a5f2 100644 --- a/bip-0364.mediawiki +++ b/bip-0364.mediawiki @@ -70,6 +70,33 @@ Refer to the reference implementation, reproduced below, for the precise semantics and detailed rationale for those semantics. + + +static inline int64_t cast_signed64(uint64_t v) +{ + uint64_t int64_min = static_cast(std::numeric_limits::min()); + if (v >= int64_min) + return static_cast(v - int64_min) + std::numeric_limits::min(); + return static_cast(v); +} + +static inline int64_t read_le8_signed(const unsigned char* ptr) +{ + return cast_signed64(ReadLE64(ptr)); +} + +static inline void push4_le(std::vector& stack, uint32_t v) +{ + uint32_t v_le = htole32(v); + stack.emplace_back(reinterpret_cast(&v_le), reinterpret_cast(&v_le) + sizeof(v_le)); +} + +static inline void push8_le(std::vector& stack, uint64_t v) +{ + uint64_t v_le = htole64(v); + stack.emplace_back(reinterpret_cast(&v_le), reinterpret_cast(&v_le) + sizeof(v_le)); +} + case OP_ADD64: case OP_SUB64: case OP_MUL64: From b5d0fe5bbdaa72f1c2d8557b0313bc65756b56bc Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Mon, 8 Jan 2024 14:31:34 -0600 Subject: [PATCH 08/14] Add conversion opcodes --- bip-0364.mediawiki | 74 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/bip-0364.mediawiki b/bip-0364.mediawiki index a61e27a5f2..ab25c5c234 100644 --- a/bip-0364.mediawiki +++ b/bip-0364.mediawiki @@ -13,10 +13,13 @@ ==Abstract== -This BIP describes a new set of opcodes (OP_ADD64, OP_SUB64, OP_MUL64, OP_DIV64, OP_NEG64, +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 @@ -38,6 +41,25 @@ OP_NEG64 * 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. @@ -200,6 +222,56 @@ case OP_NEG64: 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(num)); +} +break; https://github.com/Christewart/bitcoin/commits/64bit-arith From 571390589b7d71cab3d08ac4a27021a3c0d4ebc4 Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Tue, 9 Jan 2024 16:17:35 -0600 Subject: [PATCH 09/14] Remove push4_le as its not used in reference impl --- bip-0364.mediawiki | 6 ------ 1 file changed, 6 deletions(-) diff --git a/bip-0364.mediawiki b/bip-0364.mediawiki index ab25c5c234..44509966df 100644 --- a/bip-0364.mediawiki +++ b/bip-0364.mediawiki @@ -107,12 +107,6 @@ static inline int64_t read_le8_signed(const unsigned char* ptr) return cast_signed64(ReadLE64(ptr)); } -static inline void push4_le(std::vector& stack, uint32_t v) -{ - uint32_t v_le = htole32(v); - stack.emplace_back(reinterpret_cast(&v_le), reinterpret_cast(&v_le) + sizeof(v_le)); -} - static inline void push8_le(std::vector& stack, uint64_t v) { uint64_t v_le = htole64(v); From 1c8a5af1d120606c4d7d925926f5fb04e0a50fc0 Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Wed, 10 Jan 2024 14:16:11 -0600 Subject: [PATCH 10/14] Add specifics for OP_DIV64 --- bip-0364.mediawiki | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/bip-0364.mediawiki b/bip-0364.mediawiki index 44509966df..82ea7d8013 100644 --- a/bip-0364.mediawiki +++ b/bip-0364.mediawiki @@ -30,6 +30,17 @@ The arithmetic opcodes (OP_ADD64, OP_SUB64, OP_MUL64, OP_DIV64) behave as follow * If the operation succeeds without overflow, push the result and true onto the stack * the nSequence field of the txin is 0xffffffff; +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 From 243db3fc6791c08d7e6231467a2fc9cc38e0e971 Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Wed, 10 Jan 2024 14:41:28 -0600 Subject: [PATCH 11/14] Remove BIP number --- bip-0364.mediawiki | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bip-0364.mediawiki b/bip-0364.mediawiki index 82ea7d8013..ee98c69c81 100644 --- a/bip-0364.mediawiki +++ b/bip-0364.mediawiki @@ -1,5 +1,5 @@
-  BIP: 364
+  BIP: TBD
   Layer: Consensus (soft fork)
   Title: 64 bit arithmetic operations
   Author: Chris Stewart 

From d1c90b0f0a8f0d4f720c91a4dab100f23b6541a8 Mon Sep 17 00:00:00 2001
From: Chris Stewart 
Date: Wed, 10 Jan 2024 14:43:31 -0600
Subject: [PATCH 12/14] Remove uncessary sequence requirement

---
 bip-0364.mediawiki | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/bip-0364.mediawiki b/bip-0364.mediawiki
index ee98c69c81..21c4ab0b94 100644
--- a/bip-0364.mediawiki
+++ b/bip-0364.mediawiki
@@ -27,8 +27,7 @@ The arithmetic opcodes (OP_ADD64, OP_SUB64, OP_MUL64, OP_DIV64) behave as follow
 * 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 
-* the nSequence field of the txin is 0xffffffff;
+* 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

From 5bebdd18d4d02a3367f6b6ce285a615508417c1a Mon Sep 17 00:00:00 2001
From: Chris Stewart 
Date: Thu, 18 Jan 2024 14:30:28 -0600
Subject: [PATCH 13/14] Remove OP_DIV64 from other opcodes

---
 bip-0364.mediawiki | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bip-0364.mediawiki b/bip-0364.mediawiki
index 21c4ab0b94..f9d5f9f2be 100644
--- a/bip-0364.mediawiki
+++ b/bip-0364.mediawiki
@@ -22,7 +22,7 @@ to convert existing bitcoin protocol numbers (CScriptNum) into 4 and 7 byte litt
 
 ==Summary==
 
-The arithmetic opcodes (OP_ADD64, OP_SUB64, OP_MUL64, OP_DIV64) behave as follows 
+The arithmetic opcodes (OP_ADD64, OP_SUB64, OP_MUL64) behave as follows 
 
 * Fail if less than 2 elements on the stack 
 * Fail if the stacks top 2 elements are not exactly 8 bytes 

From bc417cbd88a96aba275ffc320d82b821aa84c5ba Mon Sep 17 00:00:00 2001
From: Chris Stewart 
Date: Mon, 3 Feb 2025 08:29:32 -0600
Subject: [PATCH 14/14] Remove 64bit specific arithmetic opcodes in favor of
 just repurposing existing airthmetic opcodes

---
 bip-0364.mediawiki | 450 ++++++++++++++++++++++++---------------------
 1 file changed, 242 insertions(+), 208 deletions(-)

diff --git a/bip-0364.mediawiki b/bip-0364.mediawiki
index f9d5f9f2be..63f14f73b3 100644
--- a/bip-0364.mediawiki
+++ b/bip-0364.mediawiki
@@ -13,67 +13,14 @@
 
 ==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) 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
+This BIP re-enables two opcodes: OP_MUL and OP_DIV.
 
+This BIP also expands supported precision for valid operands from `-2^31 +1` to `2^31 -1` to `-2^63 +1...2^63 -1`.
 
 ==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 -
+Math on satoshis required precision of 51 bits. Many bitcoin protocol proposals - such as covenant proposals -
 require Script access to output values. To support the full range of possible output values
 we need 64 bit precision.
 
@@ -92,9 +39,11 @@ 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 propsal retains overflow semantics from the original bitcoin implementation.
 
-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.
+Results from 64bit numeric opcodes may overflow and are valid as long as they are not used in a subsequent numeric operation.
+
+If overflowed results are used in a subsequent numeric operation, the Script terminates immediately.
 
 ==Detailed Specification==
 
@@ -102,180 +51,265 @@ Refer to the reference implementation, reproduced below, for the precise
 semantics and detailed rationale for those semantics.
 
 
+class CScriptNum
+{
+/**
+ * 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.
+ */
+public:
+
+    explicit CScriptNum(const __int128_t& n)
+    {
+        m_value = n;
+    }
 
+    static const size_t nDefaultMaxNumSize = 4;
 
-static inline int64_t cast_signed64(uint64_t v)
-{
-    uint64_t int64_min = static_cast(std::numeric_limits::min());
-    if (v >= int64_min)
-        return static_cast(v - int64_min) + std::numeric_limits::min();
-    return static_cast(v);
-}
+    explicit CScriptNum(const std::vector& vch, bool fRequireMinimal,
+                        const size_t nMaxNumSize = nDefaultMaxNumSize)
+    {
+        if (vch.size() > nMaxNumSize) {
+            throw scriptnum_error("script number overflow");
+        }
+        if (fRequireMinimal && vch.size() > 0) {
+            // Check that the number is encoded with the minimum possible
+            // number of bytes.
+            //
+            // If the most-significant-byte - excluding the sign bit - is zero
+            // then we're not minimal. Note how this test also rejects the
+            // negative-zero encoding, 0x80.
+            if ((vch.back() & 0x7f) == 0) {
+                // One exception: if there's more than one byte and the most
+                // significant bit of the second-most-significant-byte is set
+                // it would conflict with the sign bit. An example of this case
+                // is +-255, which encode to 0xff00 and 0xff80 respectively.
+                // (big-endian).
+                if (vch.size() <= 1 || (vch[vch.size() - 2] & 0x80) == 0) {
+                    throw scriptnum_error("non-minimally encoded script number");
+                }
+            }
+        }
+        m_value = set_vch(vch);
+    }
 
-static inline int64_t read_le8_signed(const unsigned char* ptr)
-{
-    return cast_signed64(ReadLE64(ptr));
-}
+    inline bool operator==(const int64_t& rhs) const    { return m_value == rhs; }
+    inline bool operator!=(const int64_t& rhs) const    { return m_value != rhs; }
+    inline bool operator<=(const int64_t& rhs) const    { return m_value <= rhs; }
+    inline bool operator< (const int64_t& rhs) const    { return m_value <  rhs; }
+    inline bool operator>=(const int64_t& rhs) const    { return m_value >= rhs; }
+    inline bool operator> (const int64_t& rhs) const    { return m_value >  rhs; }
 
-static inline void push8_le(std::vector& stack, uint64_t v)
-{
-    uint64_t v_le = htole64(v);
-    stack.emplace_back(reinterpret_cast(&v_le), reinterpret_cast(&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);
+    inline bool operator==(const CScriptNum& rhs) const { return operator==(rhs.m_value); }
+    inline bool operator!=(const CScriptNum& rhs) const { return operator!=(rhs.m_value); }
+    inline bool operator<=(const CScriptNum& rhs) const { return operator<=(rhs.m_value); }
+    inline bool operator< (const CScriptNum& rhs) const { return operator< (rhs.m_value); }
+    inline bool operator>=(const CScriptNum& rhs) const { return operator>=(rhs.m_value); }
+    inline bool operator> (const CScriptNum& rhs) const { return operator> (rhs.m_value); }
 
-    if (stack.size() < 2)
-        return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
+    inline CScriptNum operator+(   const int64_t& rhs)    const { return CScriptNum(m_value + rhs);}
+    inline CScriptNum operator-(   const int64_t& rhs)    const { return CScriptNum(m_value - rhs);}
+    inline CScriptNum operator+(   const CScriptNum& rhs) const { return operator+(rhs.m_value);   }
+    inline CScriptNum operator-(   const CScriptNum& rhs) const { return operator-(rhs.m_value);   }
+
+    inline CScriptNum& operator+=( const CScriptNum& rhs)       { return operator+=(rhs.m_value);  }
+    inline CScriptNum& operator-=( const CScriptNum& rhs)       { return operator-=(rhs.m_value);  }
 
-    valtype& vcha = stacktop(-2);
-    valtype& vchb = stacktop(-1);
-    if (vchb.size() != 8 || vcha.size() != 8)
-        return set_error(serror, SCRIPT_ERR_EXPECTED_8BYTES);
+    inline CScriptNum operator&(   const int64_t& rhs)    const { return CScriptNum(m_value & rhs);}
+    inline CScriptNum operator&(   const CScriptNum& rhs) const { return operator&(rhs.m_value);   }
 
-    int64_t b = read_le8_signed(vchb.data());
-    int64_t a = read_le8_signed(vcha.data());
+    inline CScriptNum& operator&=( const CScriptNum& rhs)       { return operator&=(rhs.m_value);  }
 
-    switch(opcode)
+    inline CScriptNum operator*(const __int128_t& rhs) const { return CScriptNum(m_value * rhs);}
+    inline CScriptNum operator*(const CScriptNum& rhs) const { return operator*(rhs.m_value);}
+
+    inline CScriptNum operator/(const __int128_t& rhs) const { return CScriptNum(m_value / rhs);}
+    inline CScriptNum operator/(const CScriptNum& rhs) const { return operator/(rhs.m_value);}
+
+    inline CScriptNum operator-()                         const
     {
-        case OP_ADD64:
-            if ((a > 0 && b > std::numeric_limits::max() - a) ||
-                (a < 0 && b < std::numeric_limits::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::min() + b) ||
-                (b < 0 && a > std::numeric_limits::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::max() / b) ||
-                (a > 0 && b < 0 && b < std::numeric_limits::min() / a) ||
-                (a < 0 && b > 0 && a < std::numeric_limits::min() / b) ||
-                (a < 0 && b < 0 && b < std::numeric_limits::max() / a))
-                stack.push_back(vchFalse);
-            else {
-                popstack(stack);
-                popstack(stack);
-                push8_le(stack, a * b);
-                stack.push_back(vchTrue);
-            }
-        break;
-        case OP_DIV64:
+        assert(m_value != std::numeric_limits<__int128_t>::min());
+        return CScriptNum(-m_value);
+    }
+
+    inline CScriptNum& operator=( const int64_t& rhs)
+    {
+        m_value = rhs;
+        return *this;
+    }
+
+    inline CScriptNum& operator+=( const int64_t& rhs)
+    {
+        assert(rhs == 0 || (rhs > 0 && m_value <= std::numeric_limits<__int128_t>::max() - rhs) ||
+                           (rhs < 0 && m_value >= std::numeric_limits<__int128_t>::min() - rhs));
+        m_value += rhs;
+        return *this;
+    }
+
+    inline CScriptNum& operator-=( const int64_t& rhs)
+    {
+        assert(rhs == 0 || (rhs > 0 && m_value >= std::numeric_limits<__int128_t>::min() + rhs) ||
+                           (rhs < 0 && m_value <= std::numeric_limits<__int128_t>::max() + rhs));
+        m_value -= rhs;
+        return *this;
+    }
+
+    inline CScriptNum& operator&=( const int64_t& rhs)
+    {
+        m_value &= rhs;
+        return *this;
+    }
+
+    int getint() const
+    {
+        if (m_value > std::numeric_limits::max())
+            return std::numeric_limits::max();
+        else if (m_value < std::numeric_limits::min())
+            return std::numeric_limits::min();
+        return m_value;
+    }
+
+    int64_t GetInt64() const { return m_value; }
+    __int128_t GetInt128() const {return m_value; }
+    std::vector getvch() const
+    {
+        return serialize(m_value);
+    }
+
+    static std::vector serialize(const __int128_t& value)
+    {
+        if(value == 0) {
+            return std::vector();
+        }
+
+        std::vector result;
+        const bool neg = value < 0;
+        __uint128_t absvalue = neg ? ~static_cast<__uint128_t>(value) + 1 : static_cast<__uint128_t>(value);
+
+        while(absvalue)
         {
-            if (b == 0 || (b == -1 && a == std::numeric_limits::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);
+            result.push_back(absvalue & 0xff);
+            absvalue >>= 8;
         }
-        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);
+//    - If the most significant byte is >= 0x80 and the value is positive, push a
+//    new zero-byte to make the significant byte < 0x80 again.
 
-    valtype& vcha = stacktop(-1);
-    if (vcha.size() != 8)
-        return set_error(serror, SCRIPT_ERR_EXPECTED_8BYTES);
+//    - If the most significant byte is >= 0x80 and the value is negative, push a
+//    new 0x80 byte that will be popped off when converting to an integral.
 
-    int64_t a = read_le8_signed(vcha.data());
-    if (a == std::numeric_limits::min()) { stack.push_back(vchFalse); break; }
+//    - If the most significant byte is < 0x80 and the value is negative, add
+//    0x80 to it, since it will be subtracted and interpreted as a negative when
+//    converting to an integral.
 
-    popstack(stack);
-    push8_le(stack, -a);
-    stack.push_back(vchTrue);
-}
-break;
+        if (result.back() & 0x80) {
+            result.push_back(neg ? 0x80 : 0);
+        }
+        else if (neg) {
+            result.back() |= 0x80;
+        }
 
-case OP_SCRIPTNUMTOLE64:
-{
-    // Opcodes only available post tapscript
-    if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
+        return result;
+    }
 
-    if (stack.size() < 1)
-        return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
+private:
+    static __int128_t set_vch(const std::vector& vch)
+    {
+      if (vch.empty())
+          return 0;
 
-    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);
+      __int128_t result = 0;
+      for (size_t i = 0; i != vch.size(); ++i)
+          result |= static_cast<__int128_t>(vch[i]) << 8*i;
 
-    if (stack.size() < 1)
-        return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
+      // If the input vector's most significant byte is 0x80, remove it from
+      // the result's msb and return a negative.
+      if (vch.back() & 0x80)
+          return -((__int128_t)(result & ~(0x80ULL << (8 * (vch.size() - 1)))));
 
-    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));
+      return result;
     }
-}
-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)
+    __int128_t m_value;
+};
+
+
+
+case OP_ADD:
+case OP_SUB:
+case OP_MUL:
+case OP_DIV:
+case OP_BOOLAND:
+case OP_BOOLOR:
+case OP_NUMEQUAL:
+case OP_NUMEQUALVERIFY:
+case OP_NUMNOTEQUAL:
+case OP_LESSTHAN:
+case OP_GREATERTHAN:
+case OP_LESSTHANOREQUAL:
+case OP_GREATERTHANOREQUAL:
+case OP_MIN:
+case OP_MAX:
+{
+    // (x1 x2 -- out)
+    if (stack.size() < 2)
         return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
+    CScriptNum bn1 = GetCScriptNum(stacktop(-2), fRequireMinimal, sigversion);
+    CScriptNum bn2 = GetCScriptNum(stacktop(-1), fRequireMinimal, sigversion);
+    CScriptNum bn(0);
+    switch (opcode)
+    {
+    case OP_ADD:
+        bn = bn1 + bn2;
+        break;
 
-    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(num));
-}
-break;
+    case OP_SUB:
+        bn = bn1 - bn2;
+        break;
+    case OP_MUL:
+        bn = bn1 * bn2;
+        break;
+    case OP_DIV: {
+        const __int128_t a = bn1.GetInt128();
+        const __int128_t b = bn2.GetInt128();
+        if (b == 0) return set_error(serror,SCRIPT_ERR_ARITHMETIC64);
+        __int128_t r = a % b;
+        __int128_t q = a / b;
+
+        if (r < 0 && b > 0) { r+=b; q-=1;}
+        else if (r < 0 && b > 0) { r -= b; q+=1; }
+        //have to pop the stack here for OP_DIV
+        //as we are pushing two results onto the stack
+        //quotient and remainder
+        popstack(stack);
+        popstack(stack);
+        bn = CScriptNum(q);
+        stack.push_back(CScriptNum(r).getvch());
+        break;
+    }
+    case OP_BOOLAND:             bn = (bn1 != bnZero && bn2 != bnZero); break;
+    case OP_BOOLOR:              bn = (bn1 != bnZero || bn2 != bnZero); break;
+    case OP_NUMEQUAL:            bn = (bn1 == bn2); break;
+    case OP_NUMEQUALVERIFY:      bn = (bn1 == bn2); break;
+    case OP_NUMNOTEQUAL:         bn = (bn1 != bn2); break;
+    case OP_LESSTHAN:            bn = (bn1 < bn2); break;
+    case OP_GREATERTHAN:         bn = (bn1 > bn2); break;
+    case OP_LESSTHANOREQUAL:     bn = (bn1 <= bn2); break;
+    case OP_GREATERTHANOREQUAL:  bn = (bn1 >= bn2); break;
+    case OP_MIN:                 bn = (bn1 < bn2 ? bn1 : bn2); break;
+    case OP_MAX:                 bn = (bn1 > bn2 ? bn1 : bn2); break;
+    default:                     assert(!"invalid opcode"); break;
+    }
+    if (opcode != OP_DIV)
+    {
+        popstack(stack);
+        popstack(stack);
+    }
+    stack.push_back(bn.getvch());
 
 
 https://github.com/Christewart/bitcoin/commits/64bit-arith