Skip to content

Commit

Permalink
Implement OP_ADD64, OP_SUB64, OP_MUL64, OP_DIV64, OP_LESSTHAN64, OP_L…
Browse files Browse the repository at this point in the history
…ESSTHANOREQUAL64, OP_GREATERTHAN64, OP_GREATERTHANOREQUAL64, OP_SCRIPTNUMTOLE64, OP_LE64TOSCRIPTNUM, OP_LE32TOLE64

Remove liquid args

WIP

Get simple OP_1 functional test case working

Get tests for arithmetic and comparison opcodes working

Get all functional tests passing

Rename test case to Arithmetic64bitTest

Rename file to feature_64bit_arithmetic_opcodes.py, add it to test_runner.py

Get tests passing in feature_taproot.py

Remove unused push_le4

Revert test fixture setup

Cleanup

Fix linting

test: Add leaf_version parameter to taproot_tree_helper()

Fix bug

Fix bugs

Fix compile

Fix missing sigversion checks

Fix htole64 -> htole64_internal due to bitcoin#29263
  • Loading branch information
Christewart committed Mar 6, 2024
1 parent 2f98dca commit bc772fe
Show file tree
Hide file tree
Showing 15 changed files with 582 additions and 32 deletions.
182 changes: 181 additions & 1 deletion src/script/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,25 @@ static inline void popstack(std::vector<valtype>& stack)
stack.pop_back();
}

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_internal(v);
stack.emplace_back(reinterpret_cast<unsigned char*>(&v_le), reinterpret_cast<unsigned char*>(&v_le) + sizeof(v_le));
}

bool static IsCompressedOrUncompressedPubKey(const valtype &vchPubKey) {
if (vchPubKey.size() < CPubKey::COMPRESSED_SIZE) {
// Non-canonical public key: too short
Expand Down Expand Up @@ -396,6 +415,7 @@ static bool EvalChecksig(const valtype& sig, const valtype& pubkey, CScript::con
case SigVersion::WITNESS_V0:
return EvalChecksigPreTapscript(sig, pubkey, pbegincodehash, pend, flags, checker, sigversion, serror, success);
case SigVersion::TAPSCRIPT:
case SigVersion::TAPSCRIPT_64BIT:
return EvalChecksigTapscript(sig, pubkey, execdata, flags, checker, sigversion, serror, success);
case SigVersion::TAPROOT:
// Key path spending in Taproot has no script, so this is unreachable.
Expand All @@ -415,7 +435,7 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
static const valtype vchTrue(1, 1);

// sigversion cannot be TAPROOT here, as it admits no script execution.
assert(sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0 || sigversion == SigVersion::TAPSCRIPT);
assert(sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0 || sigversion == SigVersion::TAPSCRIPT || sigversion == SigVersion::TAPSCRIPT_64BIT);

CScript::const_iterator pc = script.begin();
CScript::const_iterator pend = script.end();
Expand Down Expand Up @@ -1213,6 +1233,159 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
}
}
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_64bit
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0 || sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT) 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_64bit
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0 || sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT) 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_64bit
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0 || sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT) 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_64bit
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0 || sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT) 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_64bit
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0 || sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT) 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;

default:
return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
Expand Down Expand Up @@ -1939,6 +2112,13 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
execdata.m_validation_weight_left_init = true;
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::TAPSCRIPT, checker, execdata, serror);
}
if ((control[0] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT_64BIT) {
// Tapscript (leaf version 0x66)
exec_script = CScript(script.begin(), script.end());
execdata.m_validation_weight_left = ::GetSerializeSize(witness.stack) + VALIDATION_WEIGHT_OFFSET;
execdata.m_validation_weight_left_init = true;
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::TAPSCRIPT_64BIT, checker, execdata, serror);
}
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION) {
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION);
}
Expand Down
1 change: 1 addition & 0 deletions src/script/interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ static constexpr size_t WITNESS_V1_TAPROOT_SIZE = 32;

static constexpr uint8_t TAPROOT_LEAF_MASK = 0xfe;
static constexpr uint8_t TAPROOT_LEAF_TAPSCRIPT = 0xc0;
static constexpr uint8_t TAPROOT_LEAF_TAPSCRIPT_64BIT = 0x66;
static constexpr size_t TAPROOT_CONTROL_BASE_SIZE = 33;
static constexpr size_t TAPROOT_CONTROL_NODE_SIZE = 32;
static constexpr size_t TAPROOT_CONTROL_MAX_NODE_COUNT = 128;
Expand Down
20 changes: 20 additions & 0 deletions src/script/script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,20 @@ std::string GetOpName(opcodetype opcode)
// Opcode added by BIP 342 (Tapscript)
case OP_CHECKSIGADD : return "OP_CHECKSIGADD";

//64bit arithmetic opcodes
case OP_ADD64 : return "OP_ADD64";
case OP_SUB64 : return "OP_SUB64";
case OP_MUL64 : return "OP_MUL64";
case OP_DIV64 : return "OP_DIV64";
case OP_NEG64 : return "OP_NEG64";
case OP_LESSTHAN64 : return "OP_LESSTHAN64";
case OP_LESSTHANOREQUAL64 : return "OP_LESSTHANOREQUAL64";
case OP_GREATERTHAN64 : return "OP_GREATERTHAN64";
case OP_GREATERTHANOREQUAL64 : return "OP_GREATERTHANOREQUAL64";
case OP_SCRIPTNUMTOLE64 : return "OP_SCRIPTNUMTOLE64";
case OP_LE64TOSCRIPTNUM : return "OP_LE64TOSCRIPTNUM";
case OP_LE32TOLE64 : return "OP_LE32TOLE64";

case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE";

default:
Expand Down Expand Up @@ -349,6 +363,12 @@ bool IsOpSuccess(const opcodetype& opcode, SigVersion sigversion)
(opcode >= 141 && opcode <= 142) || (opcode >= 149 && opcode <= 153) ||
(opcode >= 187 && opcode <= 254);
break;
case SigVersion::TAPSCRIPT_64BIT:
return opcode == 80 || opcode == 98 || (opcode >= 126 && opcode <= 129) ||
(opcode >= 131 && opcode <= 134) || (opcode >= 137 && opcode <= 138) ||
(opcode >= 141 && opcode <= 142) || (opcode >= 149 && opcode <= 153) ||
(opcode >= 187 && opcode <= 214) || (opcode >= 227 && opcode <= 254);
break;
case SigVersion::BASE:
case SigVersion::WITNESS_V0:
case SigVersion::TAPROOT:
Expand Down
16 changes: 16 additions & 0 deletions src/script/script.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,22 @@ enum opcodetype
// Opcode added by BIP 342 (Tapscript)
OP_CHECKSIGADD = 0xba,

// Arithmetic opcodes
OP_ADD64 = 0xd7,
OP_SUB64 = 0xd8,
OP_MUL64 = 0xd9,
OP_DIV64 = 0xda,
OP_NEG64 = 0xdb,
OP_LESSTHAN64 = 0xdc,
OP_LESSTHANOREQUAL64 = 0xdd,
OP_GREATERTHAN64 = 0xde,
OP_GREATERTHANOREQUAL64 = 0xdf,

// Conversion opcodes
OP_SCRIPTNUMTOLE64 = 0xe0,
OP_LE64TOSCRIPTNUM = 0xe1,
OP_LE32TOLE64 = 0xe2,

OP_INVALIDOPCODE = 0xff,
};

Expand Down
4 changes: 4 additions & 0 deletions src/script/script_error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ std::string ScriptErrorString(const ScriptError serror)
return "Using OP_CODESEPARATOR in non-witness script";
case SCRIPT_ERR_SIG_FINDANDDELETE:
return "Signature is found in scriptCode";
case SCRIPT_ERR_EXPECTED_8BYTES:
return "Arithmetic opcodes expect 8 bytes operands";
case SCRIPT_ERR_ARITHMETIC64:
return "Arithmetic opcode error";
case SCRIPT_ERR_UNKNOWN_ERROR:
case SCRIPT_ERR_ERROR_COUNT:
default: break;
Expand Down
7 changes: 6 additions & 1 deletion src/script/script_error.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,12 @@ typedef enum ScriptError_t
SCRIPT_ERR_OP_CODESEPARATOR,
SCRIPT_ERR_SIG_FINDANDDELETE,

SCRIPT_ERR_ERROR_COUNT
SCRIPT_ERR_ERROR_COUNT,

/* 64bit arithmetic opcode errors */
SCRIPT_ERR_EXPECTED_8BYTES,
SCRIPT_ERR_ARITHMETIC64

} ScriptError;

#define SCRIPT_ERR_LAST SCRIPT_ERR_ERROR_COUNT
Expand Down
1 change: 1 addition & 0 deletions src/script/sigversion.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ enum class SigVersion
WITNESS_V0 = 1, //!< Witness v0 (P2WPKH and P2WSH); see BIP 141
TAPROOT = 2, //!< Witness v1 with 32-byte program, not BIP16 P2SH-wrapped, key path spending; see BIP 341
TAPSCRIPT = 3, //!< Witness v1 with 32-byte program, not BIP16 P2SH-wrapped, script path spending, leaf version 0xc0; see BIP 342
TAPSCRIPT_64BIT = 4, //!< Witness v1 with 32 byte program script path spending with 64bit arithmetic
};
#endif // BITCOIN_SCRIPT_SIGVERSION_H
1 change: 1 addition & 0 deletions src/wallet/feebumper.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ struct SignatureWeights
break;
case SigVersion::TAPROOT:
case SigVersion::TAPSCRIPT:
case SigVersion::TAPSCRIPT_64BIT:
assert(false);
}
}
Expand Down

0 comments on commit bc772fe

Please sign in to comment.