From 3c5a946ffcb1da0b07bee5743ff55b7acd0e3cbb Mon Sep 17 00:00:00 2001 From: Fernando Pelliccioni Date: Thu, 9 Jan 2025 16:43:09 +0100 Subject: [PATCH] feat: partial restore of knuth vm --- include/kth/domain/chain/script.hpp | 18 +- include/kth/domain/chain/script_basis.hpp | 2 +- include/kth/domain/config/parser.hpp | 7 +- include/kth/domain/constants/common.hpp | 17 +- .../kth/domain/impl/machine/interpreter.ipp | 885 +++++++++++++++--- include/kth/domain/impl/machine/operation.ipp | 52 +- include/kth/domain/impl/machine/program.ipp | 88 +- include/kth/domain/machine/interpreter.hpp | 101 +- include/kth/domain/machine/metrics.hpp | 98 ++ include/kth/domain/machine/opcode.hpp | 609 ++++++------ include/kth/domain/machine/operation.hpp | 9 +- include/kth/domain/machine/program.hpp | 34 +- include/kth/domain/machine/script_limits.hpp | 93 ++ src/chain/script.cpp | 175 ++-- src/chain/script_basis.cpp | 4 +- src/machine/interpreter.cpp | 56 +- src/machine/opcode.cpp | 196 ++-- test/machine/operation.cpp | 3 +- 18 files changed, 1788 insertions(+), 659 deletions(-) create mode 100644 include/kth/domain/machine/metrics.hpp create mode 100644 include/kth/domain/machine/script_limits.hpp diff --git a/include/kth/domain/chain/script.hpp b/include/kth/domain/chain/script.hpp index f7a1f20c1..a774b8f35 100644 --- a/include/kth/domain/chain/script.hpp +++ b/include/kth/domain/chain/script.hpp @@ -100,7 +100,7 @@ class KD_API script : public script_basis { //------------------------------------------------------------------------- static - hash_digest generate_signature_hash(transaction const& tx, + std::pair generate_signature_hash(transaction const& tx, uint32_t input_index, script const& script_code, uint8_t sighash_type, @@ -108,7 +108,7 @@ class KD_API script : public script_basis { uint64_t value = max_uint64); static - bool check_signature(ec_signature const& signature, + std::pair check_signature(ec_signature const& signature, uint8_t sighash_type, data_chunk const& public_key, script const& script_code, @@ -231,6 +231,16 @@ class KD_API script : public script_basis { bool is_pay_to_script_hash(uint32_t forks) const; + // Validation. + //----------------------------------------------------------------------------- + + //TODO: move to script_basis (?) + static + code verify(transaction const& tx, uint32_t input_index, uint32_t forks, script const& input_script, script const& prevout_script, uint64_t /*value*/); + + static + code verify(transaction const& tx, uint32_t input, uint32_t forks); + private: static size_t serialized_size(operation::list const& ops); @@ -239,10 +249,10 @@ class KD_API script : public script_basis { data_chunk operations_to_data(operation::list const& ops); static - hash_digest generate_unversioned_signature_hash(transaction const& tx, uint32_t input_index, script const& script_code, uint8_t sighash_type); + std::pair generate_unversioned_signature_hash(transaction const& tx, uint32_t input_index, script const& script_code, uint8_t sighash_type); static - hash_digest generate_version_0_signature_hash(transaction const& tx, + std::pair generate_version_0_signature_hash(transaction const& tx, uint32_t input_index, script const& script_code, uint64_t value, diff --git a/include/kth/domain/chain/script_basis.hpp b/include/kth/domain/chain/script_basis.hpp index 68e0e8334..7c5d3edc2 100644 --- a/include/kth/domain/chain/script_basis.hpp +++ b/include/kth/domain/chain/script_basis.hpp @@ -284,7 +284,7 @@ class KD_API script_basis { // hash_digest generate_unversioned_signature_hash(transaction const& tx, uint32_t input_index, script_basis const& script_code, uint8_t sighash_type); static - hash_digest generate_version_0_signature_hash(transaction const& tx, uint32_t input_index, script_basis const& script_code, uint64_t value, uint8_t sighash_type); + std::pair generate_version_0_signature_hash(transaction const& tx, uint32_t input_index, script_basis const& script_code, uint64_t value, uint8_t sighash_type); void find_and_delete_(data_chunk const& endorsement); diff --git a/include/kth/domain/config/parser.hpp b/include/kth/domain/config/parser.hpp index 246e3be01..3c3951c10 100644 --- a/include/kth/domain/config/parser.hpp +++ b/include/kth/domain/config/parser.hpp @@ -178,7 +178,7 @@ kth::infrastructure::config::checkpoint::list default_checkpoints(config::networ checkpoints.emplace_back("00000000e4627a1a0bf9aaae007af5cea32720fb54cf2ccf0aa20b02a18392ab", 16869); //mediantime: 1605444236. New rules activated in this block. } else if (network == domain::config::network::chipnet) { - checkpoints.reserve(12); + checkpoints.reserve(17); checkpoints.emplace_back("000000001dd410c49a788668ce26751718cc797474d3152a5fc073dd44fd9f7b", 0); @@ -208,6 +208,11 @@ kth::infrastructure::config::checkpoint::list default_checkpoints(config::networ // A block significantly after Upgrade 10 activated (which activated on Nov. 15, 2023) checkpoints.emplace_back("000000003c37cc0372a5b9ccacca921786bbfc699722fc41e9fdbb1de4146ef1", 178140); + checkpoints.emplace_back("00000000146a073b9d4e172adbee5252014a8b4d75c56cce36858311565ae251", 206364); + + // A block after Upgrade 11 activated (Nov. 15, 2024), first block after upgrade: 227229 + checkpoints.emplace_back("00000000144b00db5736b33bd572b3a3a52aa9b4c26ba59fc212aeb68a9b7a20", 228000); + checkpoints.emplace_back("0000000017d92f88ed2c81885c57f999184860a042250510be06b3edd12e0dc5", 232000); } else if (network == domain::config::network::mainnet) { checkpoints.reserve(60); diff --git a/include/kth/domain/constants/common.hpp b/include/kth/domain/constants/common.hpp index 12c953cd3..1fdc4c41a 100644 --- a/include/kth/domain/constants/common.hpp +++ b/include/kth/domain/constants/common.hpp @@ -35,12 +35,27 @@ constexpr size_t max_counted_ops = 201; constexpr size_t max_stack_size = 1000; constexpr size_t max_script_size = 10000; // constexpr size_t max_push_data_size = 520; +constexpr size_t max_push_data_size_legacy = 520; constexpr size_t max_script_public_keys = 20; constexpr size_t multisig_default_sigops = 20; -constexpr size_t max_number_size = 4; +constexpr size_t max_number_size_32_bits = 4; +constexpr size_t max_number_size_64_bits = 8; constexpr size_t max_check_locktime_verify_number_size = 5; constexpr size_t max_check_sequence_verify_number_size = 5; +// The below constants are used after activation of the May 2025 upgrade (Targeted VM Limits CHIP) +namespace may2025 { + + // Maximum number of bytes pushable to the stack + constexpr size_t max_push_data_size = max_script_size; // BCHN: MAX_SCRIPT_ELEMENT_SIZE + // Base cost for each executed opcode; no opcodes incur a cost less than this, but some may incur more. + constexpr size_t opcode_cost = 100u; // BCHN: OPCODE_COST + // Conditional stack depth limit (max depth of OP_IF and friends) + constexpr size_t max_conditional_stack_depth = 100u; // BCHN: MAX_CONDITIONAL_STACK_DEPTH + // Each sigcheck done by an input adds this amount to the total op cost + constexpr uint64_t sig_check_cost_factor = 26'000u; // BCHN: SIG_CHECK_COST_FACTOR +} + // Policy. constexpr size_t max_null_data_size = 80; diff --git a/include/kth/domain/impl/machine/interpreter.ipp b/include/kth/domain/impl/machine/interpreter.ipp index 8c6a450e6..8db14821f 100644 --- a/include/kth/domain/impl/machine/interpreter.ipp +++ b/include/kth/domain/impl/machine/interpreter.ipp @@ -21,10 +21,18 @@ #include #include + +// TODO: move to a concepts file +template +concept bitwise_op = requires(Op op, uint8_t a, uint8_t b) { + { op(a, b) } -> std::same_as; +}; + + namespace kth::domain::machine { static constexpr -auto op_75 = static_cast(opcode::push_size_75); +auto op_75 = uint8_t(opcode::push_size_75); // Operations (shared). //----------------------------------------------------------------------------- @@ -47,6 +55,7 @@ interpreter::result interpreter::op_reserved(opcode /*unused*/) { inline interpreter::result interpreter::op_push_number(program& program, uint8_t value) { program.push_move({value}); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } @@ -57,9 +66,12 @@ interpreter::result interpreter::op_push_size(program& program, operation const& } program.push_copy(op.data()); + // metrics.TallyPushOp(stack.back().size()); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } +// TODO: std::move the data chunk to the program inline interpreter::result interpreter::op_push_data(program& program, data_chunk const& data, uint32_t size_limit) { if (data.size() > size_limit) { @@ -67,6 +79,7 @@ interpreter::result interpreter::op_push_data(program& program, data_chunk const } program.push_copy(data); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } @@ -76,6 +89,7 @@ interpreter::result interpreter::op_push_data(program& program, data_chunk const inline interpreter::result interpreter::op_if(program& program) { + //TODO: SCRIPT_VERIFY_MINIMALIF auto value = false; if (program.succeeded()) { @@ -93,6 +107,7 @@ interpreter::result interpreter::op_if(program& program) { inline interpreter::result interpreter::op_notif(program& program) { + //TODO: SCRIPT_VERIFY_MINIMALNOTIF auto value = false; if (program.succeeded()) { @@ -154,6 +169,7 @@ interpreter::result interpreter::op_to_alt_stack(program& program) { } program.push_alternate(program.pop()); + // Intentional: no tallying is done to get_metrics().add_op_cost() return error::success; } @@ -164,6 +180,8 @@ interpreter::result interpreter::op_from_alt_stack(program& program) { } program.push_move(program.pop_alternate()); + // metrics.TallyPushOp(stack.back().size()); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } @@ -188,7 +206,12 @@ interpreter::result interpreter::op_dup2(program& program) { auto item0 = program.item(0); program.push_move(std::move(item1)); + // metrics.TallyPushOp(stack.back().size()); + program.get_metrics().add_op_cost(program.top().size()); + program.push_move(std::move(item0)); + // metrics.TallyPushOp(stack.back().size()); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } @@ -203,8 +226,11 @@ interpreter::result interpreter::op_dup3(program& program) { auto item0 = program.item(0); program.push_move(std::move(item2)); + program.get_metrics().add_op_cost(program.top().size()); program.push_move(std::move(item1)); + program.get_metrics().add_op_cost(program.top().size()); program.push_move(std::move(item0)); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } @@ -218,7 +244,9 @@ interpreter::result interpreter::op_over2(program& program) { auto item2 = program.item(2); program.push_move(std::move(item3)); + program.get_metrics().add_op_cost(program.top().size()); program.push_move(std::move(item2)); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } @@ -236,7 +264,9 @@ interpreter::result interpreter::op_rot2(program& program) { program.erase(position_5, position_4 + 1); program.push_move(std::move(copy_5)); + program.get_metrics().add_op_cost(program.top().size()); program.push_move(std::move(copy_4)); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } @@ -248,6 +278,7 @@ interpreter::result interpreter::op_swap2(program& program) { program.swap(3, 1); program.swap(2, 0); + // Intentional: no tallying is done to get_metrics().add_op_cost() return error::success; } @@ -259,6 +290,7 @@ interpreter::result interpreter::op_if_dup(program& program) { if (program.stack_true(false)) { program.duplicate(0); + program.get_metrics().add_op_cost(program.top().size()); } return error::success; @@ -267,6 +299,7 @@ interpreter::result interpreter::op_if_dup(program& program) { inline interpreter::result interpreter::op_depth(program& program) { program.push_move(number(program.size()).data()); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } @@ -277,6 +310,7 @@ interpreter::result interpreter::op_drop(program& program) { } program.pop(); + // No metrics return error::success; } @@ -287,6 +321,7 @@ interpreter::result interpreter::op_dup(program& program) { } program.duplicate(0); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } @@ -297,6 +332,7 @@ interpreter::result interpreter::op_nip(program& program) { } program.erase(program.position(1)); + // No metrics return error::success; } @@ -307,22 +343,26 @@ interpreter::result interpreter::op_over(program& program) { } program.duplicate(1); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } inline interpreter::result interpreter::op_pick(program& program) { + // (xn ... x2 x1 x0 n - xn ... x2 x1 x0 xn) program::stack_iterator position; if ( ! program.pop_position(position)) { return error::op_pick; } program.push_copy(*position); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } inline interpreter::result interpreter::op_roll(program& program) { + // (xn ... x2 x1 x0 n - ... x2 x1 x0 xn) program::stack_iterator position; if ( ! program.pop_position(position)) { return error::op_roll; @@ -330,18 +370,25 @@ interpreter::result interpreter::op_roll(program& program) { auto copy = *position; program.erase(position); + // metrics.TallyOp(n); // erasing in the middle is linear with `n` + program.get_metrics().add_op_cost(program.index(position)); program.push_move(std::move(copy)); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } inline interpreter::result interpreter::op_rot(program& program) { + // (x1 x2 x3 -- x2 x3 x1) + // x2 x1 x3 after first swap + // x2 x3 x1 after second swap if (program.size() < 3) { return error::op_rot; } program.swap(2, 1); program.swap(1, 0); + // Intentional: no tallying is done to get_metrics().add_op_cost() return error::success; } @@ -352,25 +399,257 @@ interpreter::result interpreter::op_swap(program& program) { } program.swap(1, 0); + // Intentional: no tallying is done to get_metrics().add_op_cost() return error::success; } inline interpreter::result interpreter::op_tuck(program& program) { + // (x1 x2 -- x2 x1 x2) if (program.size() < 2) { return error::op_tuck; } auto first = program.pop(); auto second = program.pop(); + program.get_metrics().add_op_cost(first.size()); program.push_copy(first); program.push_move(std::move(second)); program.push_move(std::move(first)); return error::success; } + +// case OP_CAT: { +// // (x1 x2 -- out) +// if (stack.size() < 2) { +// return set_error(serror, ScriptError::INVALID_STACK_OPERATION); +// } +// valtype &vch1 = stacktop(-2); +// const valtype &vch2 = stacktop(-1); +// if (vch1.size() + vch2.size() > maxScriptElementSize) { +// return set_error(serror, ScriptError::PUSH_SIZE); +// } +// vch1.insert(vch1.end(), vch2.begin(), vch2.end()); +// popstack(stack); +// metrics.TallyPushOp(stack.back().size()); +// } break; + +inline +interpreter::result interpreter::op_cat(program& program) { + // (x1 x2 -- out) + if (program.size() < 2) { + return error::op_cat; + } + + auto last = program.pop(); + auto& but_last = program.top(); + + if (but_last.size() + last.size() > program.max_script_element_size()) { + return error::invalid_push_data_size; + } + + but_last.insert(but_last.end(), last.begin(), last.end()); + program.get_metrics().add_op_cost(but_last.size()); + + return error::success; +} + + +// case OP_SPLIT: { +// // (in position -- x1 x2) +// if (stack.size() < 2) { +// return set_error(serror, ScriptError::INVALID_STACK_OPERATION); +// } + +// const valtype &data = stacktop(-2); + +// // Make sure the split point is appropriate. +// int64_t const position = CScriptNum(stacktop(-1), fRequireMinimal, maxIntegerSizeLegacy).getint64(); +// if (position < 0 || uint64_t(position) > data.size()) { +// return set_error(serror, ScriptError::INVALID_SPLIT_RANGE); +// } + +// // Prepare the results in their own buffer as `data` will be invalidated. +// valtype n1(data.begin(), data.begin() + position); +// valtype n2(data.begin() + position, data.end()); + +// // Replace existing stack values by the new values. +// const size_t totalSize = n1.size() + n2.size(); +// stacktop(-2) = std::move(n1); +// stacktop(-1) = std::move(n2); +// metrics.TallyPushOp(totalSize); +// } break; + + +inline +interpreter::result interpreter::op_split(program& program) { + if (program.size() < 2) { + return error::op_split; + } + + auto& pos = program.item(0); // last item + auto& data = program.item(1); // but last item + + number position; + if ( ! position.set_data(pos, program.max_integer_size_legacy())) { + return error::op_split; + } + auto const pos64 = position.int64(); + + if (pos64 < 0 || size_t(pos64) > data.size()) { + return error::op_split; + } + + auto n1 = data_chunk(data.begin(), data.begin() + pos64); + auto n2 = data_chunk(data.begin() + pos64, data.end()); + size_t const total_size = n1.size() + n2.size(); + + data = std::move(n1); + pos = std::move(n2); + + program.get_metrics().add_op_cost(total_size); + + return error::success; +} + +inline +interpreter::result interpreter::op_reverse_bytes(program& program) { + return error::op_reverse_bytes; +} + + +// // +// // Conversion operations +// // +// case OP_NUM2BIN: { +// // (in size -- out) +// if (stack.size() < 2) { +// return set_error(serror, ScriptError::INVALID_STACK_OPERATION); +// } + +// uint64_t const size = CScriptNum(stacktop(-1), fRequireMinimal, maxIntegerSizeLegacy).getint64(); +// if (size > maxScriptElementSize) { +// return set_error(serror, ScriptError::PUSH_SIZE); +// } + +// popstack(stack); +// valtype &rawnum = stacktop(-1); + +// // Try to see if we can fit that number in the number of byte requested. +// ScriptNumType::MinimallyEncode(rawnum); +// if (rawnum.size() > size) { +// // We definitively cannot. +// return set_error(serror, ScriptError::IMPOSSIBLE_ENCODING); +// } + +// // We already have an element of the right size, we don't need to do anything. +// if (rawnum.size() == size) { +// metrics.TallyPushOp(rawnum.size()); +// break; +// } + +// uint8_t signbit = 0x00; +// if (rawnum.size() > 0) { +// signbit = rawnum.back() & 0x80; +// rawnum[rawnum.size() - 1] &= 0x7f; +// } + +// rawnum.reserve(size); +// while (rawnum.size() < size - 1) { +// rawnum.push_back(0x00); +// } + +// rawnum.push_back(signbit); +// metrics.TallyPushOp(rawnum.size()); +// } break; + +inline +interpreter::result interpreter::op_num2bin(program& program) { + if (program.size() < 2) { + return error::op_num2bin; + } + + number size; + if ( ! program.top(size, program.max_integer_size_legacy())) { + return error::op_num2bin_invalid_size; + } + auto const size64 = size.int64(); + if (size64 < 0 || size_t(size64) > program.max_script_element_size()) { + return error::op_num2bin_size_exceeded; + } + + auto& rawnum = program.item(1); // but last item + number::minimally_encode(rawnum); + + // Check if the number can be adjusted to the desired size. + if (rawnum.size() > size64) { + return error::op_num2bin_impossible_encoding; + } + + // If the size is already correct, no more is needed. + if (rawnum.size() == size64) { + program.get_metrics().add_op_cost(rawnum.size()); + return error::success; + } + + // Adjust the size of `rawnum` by adding padding zeros. + uint8_t signbit = 0x00; + if ( ! rawnum.empty() && (rawnum.back() & 0x80)) { + signbit = 0x80; + rawnum.back() &= 0x7f; + } + + rawnum.reserve(size64); + while (rawnum.size() < size64 - 1) { + rawnum.push_back(0x00); + } + + rawnum.push_back(signbit); + + program.get_metrics().add_op_cost(rawnum.size()); + return error::success; +} + + +// case OP_BIN2NUM: { +// // (in -- out) +// if (stack.size() < 1) { +// return set_error(serror, ScriptError::INVALID_STACK_OPERATION); +// } + +// valtype &n = stacktop(-1); +// ScriptNumType::MinimallyEncode(n); +// metrics.TallyPushOp(n.size()); + +// // The resulting number must be a valid number. +// // Note: IsMinimallyEncoded() here is really just checking if the number is in range. +// if ( ! ScriptNumType::IsMinimallyEncoded(n, maxIntegerSize)) { +// return set_error(serror, invalidNumberRangeError); +// } +// } break; + +inline +interpreter::result interpreter::op_bin2num(program& program) { + // (in -- out) + if (program.empty()) { + return error::op_bin2num; + } + + auto& n = program.top(); + number::minimally_encode(n); + program.get_metrics().add_op_cost(n.size()); + + if ( ! number::is_minimally_encoded(n, program.max_integer_size_legacy())) { + return error::op_bin2num_invalid_number_range; + } + + return error::success; +} + inline interpreter::result interpreter::op_size(program& program) { + // (in -- in size) if (program.empty()) { return error::op_size; } @@ -379,68 +658,145 @@ interpreter::result interpreter::op_size(program& program) { auto const size = top.size(); program.push_move(std::move(top)); program.push_move(number(size).data()); + program.get_metrics().add_op_cost(size); return error::success; } +// Disabled +// inline +// interpreter::result interpreter::op_invert(program& program) { +// } + + +template +inline +interpreter::result bitwise_operation_generic(program& program, error::error_code_t error, Op op) { + if (program.size() < 2) { + return error; + } + + auto& vch1 = program.item(1); + auto& vch2 = program.item(0); + + if (vch1.size() != vch2.size()) { + return error; + } + + for (size_t i = 0; i < vch1.size(); ++i) { + vch1[i] = op(vch1[i], vch2[i]); + } + + program.pop(); + program.get_metrics().add_op_cost(vch1.size()); + + return error::success; +} + +inline +interpreter::result interpreter::op_and(program& program) { + return bitwise_operation_generic(program, + error::op_and, + [](uint8_t a, uint8_t b) { return uint8_t(a & b); }); +} + +inline +interpreter::result interpreter::op_or(program& program) { + return bitwise_operation_generic(program, + error::op_or, + [](uint8_t a, uint8_t b) { return uint8_t(a | b); }); +} + +inline +interpreter::result interpreter::op_xor(program& program) { + return bitwise_operation_generic(program, + error::op_xor, + [](uint8_t a, uint8_t b) { return uint8_t(a ^ b); }); +} + inline interpreter::result interpreter::op_equal(program& program) { + // (x1 x2 - bool) if (program.size() < 2) { return error::op_equal; } program.push(program.pop() == program.pop()); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } inline interpreter::result interpreter::op_equal_verify(program& program) { + // (x1 x2 - bool) if (program.size() < 2) { return error::op_equal_verify1; } + auto const res = program.pop() == program.pop(); //res is a bool + // program.get_metrics().add_op_cost(res.size()); - return (program.pop() == program.pop()) ? error::success : error::op_equal_verify2; + // void program::push(bool value) { + // push_move(value ? value_type{number::positive_1} : value_type{}); + // } + program.get_metrics().add_op_cost(res ? 1 : 0); + + return res ? error::success : error::op_equal_verify2; } inline interpreter::result interpreter::op_add1(program& program) { + constexpr auto push_cost_factor = 2; number number; - if ( ! program.pop(number)) { + if ( ! program.pop(number, program.max_integer_size_legacy())) { return error::op_add1; } - number += 1; + // number += 1; + bool const res = number.safe_add(1); + if ( ! res) { + return error::op_add_overflow; + } + program.get_metrics().add_op_cost(number.data().size() * push_cost_factor); program.push_move(number.data()); return error::success; } inline interpreter::result interpreter::op_sub1(program& program) { + constexpr auto push_cost_factor = 2; number number; - if ( ! program.pop(number)) { + if ( ! program.pop(number, program.max_integer_size_legacy())) { return error::op_sub1; } - number -= 1; + // number -= 1; + bool const res = number.safe_sub(1); + if ( ! res) { + return error::op_sub_underflow; + } + program.get_metrics().add_op_cost(number.data().size() * push_cost_factor); program.push_move(number.data()); return error::success; } inline interpreter::result interpreter::op_negate(program& program) { + constexpr auto push_cost_factor = 2; number number; - if ( ! program.pop(number)) { + if ( ! program.pop(number, program.max_integer_size_legacy())) { return error::op_negate; } number = -number; + program.get_metrics().add_op_cost(number.data().size() * push_cost_factor); program.push_move(number.data()); return error::success; } inline interpreter::result interpreter::op_abs(program& program) { + constexpr auto push_cost_factor = 2; number number; - if ( ! program.pop(number)) { + if ( ! program.pop(number, program.max_integer_size_legacy())) { return error::op_abs; } @@ -448,6 +804,7 @@ interpreter::result interpreter::op_abs(program& program) { number = -number; } + program.get_metrics().add_op_cost(number.data().size() * push_cost_factor); program.push_move(number.data()); return error::success; } @@ -455,200 +812,310 @@ interpreter::result interpreter::op_abs(program& program) { inline interpreter::result interpreter::op_not(program& program) { number number; - if ( ! program.pop(number)) { + if ( ! program.pop(number, program.max_integer_size_legacy())) { return error::op_not; } program.push(number.is_false()); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } inline interpreter::result interpreter::op_nonzero(program& program) { number number; - if ( ! program.pop(number)) { + if ( ! program.pop(number, program.max_integer_size_legacy())) { return error::op_nonzero; } program.push(number.is_true()); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } inline interpreter::result interpreter::op_add(program& program) { - number first, second; //NOLINT + constexpr auto push_cost_factor = 2; + number first; + number second; if ( ! program.pop_binary(first, second)) { return error::op_add; } - auto const result = first + second; - program.push_move(result.data()); + // auto const result = first + second; + auto result = number::safe_add(first, second); + if ( ! result) { + return error::op_add_overflow; + } + program.get_metrics().add_op_cost(result->data().size() * push_cost_factor); + program.push_move(result->data()); return error::success; } inline interpreter::result interpreter::op_sub(program& program) { - number first, second; //NOLINT + constexpr auto push_cost_factor = 2; + number first; + number second; if ( ! program.pop_binary(first, second)) { return error::op_sub; } - auto const result = second - first; + // auto const result = second - first; + auto result = number::safe_sub(second, first); + if ( ! result) { + return error::op_sub_underflow; + } + program.get_metrics().add_op_cost(result->data().size() * push_cost_factor); + program.push_move(result->data()); + return error::success; +} + +inline +interpreter::result interpreter::op_mul(program& program) { + constexpr auto push_cost_factor = 2; + number first; + number second; + if ( ! program.pop_binary(first, second)) { + return error::op_mul; + } + + // auto const result = first * second; + auto result = number::safe_mul(first, second); + if ( ! result) { + return error::op_mul_overflow; + } + uint32_t const quadratic_op_cost = first.data().size() * second.data().size(); + program.get_metrics().add_op_cost(quadratic_op_cost); + program.get_metrics().add_op_cost(result->data().size() * push_cost_factor); + program.push_move(result->data()); + return error::success; +} + +inline +interpreter::result interpreter::op_div(program& program) { + constexpr auto push_cost_factor = 2; + number first; + number second; + if ( ! program.pop_binary(first, second)) { + return error::op_div; + } + + if (first == 0) { + return error::op_div_by_zero; + } + + auto result = second / first; + uint32_t const quadratic_op_cost = first.data().size() * second.data().size(); + program.get_metrics().add_op_cost(quadratic_op_cost); program.push_move(result.data()); + program.get_metrics().add_op_cost(result.data().size() * push_cost_factor); + return error::success; +} + +inline +interpreter::result interpreter::op_mod(program& program) { + constexpr auto push_cost_factor = 2; + number first; + number second; + if ( ! program.pop_binary(first, second)) { + return error::op_mod; + } + + if (first == 0) { + return error::op_mod_by_zero; + } + + auto result = second % first; + uint32_t const quadratic_op_cost = first.data().size() * second.data().size(); + program.get_metrics().add_op_cost(quadratic_op_cost); + program.push_move(result.data()); + program.get_metrics().add_op_cost(result.data().size() * push_cost_factor); return error::success; } inline interpreter::result interpreter::op_bool_and(program& program) { - number first, second; //NOLINT + number first; + number second; if ( ! program.pop_binary(first, second)) { return error::op_bool_and; } program.push(first.is_true() && second.is_true()); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } inline interpreter::result interpreter::op_bool_or(program& program) { - number first, second; //NOLINT + number first; + number second; if ( ! program.pop_binary(first, second)) { return error::op_bool_or; } program.push(first.is_true() || second.is_true()); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } inline interpreter::result interpreter::op_num_equal(program& program) { - number first, second; //NOLINT + number first; + number second; if ( ! program.pop_binary(first, second)) { return error::op_num_equal; } program.push(first == second); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } inline interpreter::result interpreter::op_num_equal_verify(program& program) { - number first, second; //NOLINT + number first; + number second; if ( ! program.pop_binary(first, second)) { return error::op_num_equal_verify1; } - - return (first == second) ? error::success : error::op_num_equal_verify2; + auto const res = first == second; + program.get_metrics().add_op_cost(1); + return res ? error::success : error::op_num_equal_verify2; } inline interpreter::result interpreter::op_num_not_equal(program& program) { - number first, second; //NOLINT + number first; + number second; if ( ! program.pop_binary(first, second)) { return error::op_num_not_equal; } program.push(first != second); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } inline interpreter::result interpreter::op_less_than(program& program) { - number first, second; //NOLINT + number first; + number second; if ( ! program.pop_binary(first, second)) { return error::op_less_than; } program.push(second < first); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } inline interpreter::result interpreter::op_greater_than(program& program) { - number first, second; //NOLINT + number first; + number second; if ( ! program.pop_binary(first, second)) { return error::op_greater_than; } program.push(second > first); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } inline interpreter::result interpreter::op_less_than_or_equal(program& program) { - number first, second; //NOLINT + number first; + number second; if ( ! program.pop_binary(first, second)) { return error::op_less_than_or_equal; } program.push(second <= first); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } inline -interpreter::result interpreter::op_greater_than_or_equal( - program& program) { - number first, second; //NOLINT +interpreter::result interpreter::op_greater_than_or_equal(program& program) { + number first; + number second; if ( ! program.pop_binary(first, second)) { return error::op_greater_than_or_equal; } program.push(second >= first); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } inline interpreter::result interpreter::op_min(program& program) { - number first, second; //NOLINT + constexpr auto push_cost_factor = 2; + number first; + number second; if ( ! program.pop_binary(first, second)) { return error::op_min; } program.push_move(second < first ? second.data() : first.data()); + program.get_metrics().add_op_cost(program.top().size() * push_cost_factor); return error::success; } inline interpreter::result interpreter::op_max(program& program) { - number first, second; //NOLINT + constexpr auto push_cost_factor = 2; + number first; + number second; if ( ! program.pop_binary(first, second)) { return error::op_max; } program.push_move(second > first ? second.data() : first.data()); + program.get_metrics().add_op_cost(program.top().size() * push_cost_factor); return error::success; } inline interpreter::result interpreter::op_within(program& program) { - number first, second, third; //NOLINT + // (x min max -- out) + number first; + number second; + number third; if ( ! program.pop_ternary(first, second, third)) { return error::op_within; } program.push(second <= third && third < first); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } inline interpreter::result interpreter::op_ripemd160(program& program) { + // (in -- hash) if (program.empty()) { return error::op_ripemd160; } - + program.get_metrics().add_hash_iterations(program.top().size(), false); program.push_move(ripemd160_hash_chunk(program.pop())); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } inline interpreter::result interpreter::op_sha1(program& program) { + // (in -- hash) if (program.empty()) { return error::op_sha1; } - + program.get_metrics().add_hash_iterations(program.top().size(), false); program.push_move(sha1_hash_chunk(program.pop())); + program.get_metrics().add_op_cost(program.top().size()); return error::success; - } +} inline interpreter::result interpreter::op_sha256(program& program) { @@ -656,7 +1123,9 @@ interpreter::result interpreter::op_sha256(program& program) { return error::op_sha256; } + program.get_metrics().add_hash_iterations(program.top().size(), false); program.push_move(sha256_hash_chunk(program.pop())); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } @@ -666,7 +1135,9 @@ interpreter::result interpreter::op_hash160(program& program) { return error::op_hash160; } + program.get_metrics().add_hash_iterations(program.top().size(), true); program.push_move(ripemd160_hash_chunk(sha256_hash(program.pop()))); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } @@ -676,31 +1147,33 @@ interpreter::result interpreter::op_hash256(program& program) { return error::op_hash256; } + program.get_metrics().add_hash_iterations(program.top().size(), true); program.push_move(sha256_hash_chunk(sha256_hash(program.pop()))); + program.get_metrics().add_op_cost(program.top().size()); return error::success; } inline -interpreter::result interpreter::op_codeseparator(program& program, - operation const& op) { +interpreter::result interpreter::op_codeseparator(program& program, operation const& op) { return program.set_jump_register(op, +1) ? error::success : error::op_code_seperator; } inline -interpreter::result interpreter::op_check_sig_verify(program& program) { +std::pair op_check_sig_common(program& program, interpreter::result err) { + //TODO: SCRIPT_VERIFY_NULLFAIL if (program.size() < 2) { - return error::op_check_sig_verify1; + return {err, 0}; } uint8_t sighash; ec_signature signature; der_signature distinguished; - auto bip66 = chain::script::is_enabled(program.forks(), rule_fork::bip66_rule); + auto const bip66 = chain::script::is_enabled(program.forks(), rule_fork::bip66_rule); -#if ! defined(KTH_CURRENCY_BCH) - auto bip143 = chain::script::is_enabled(program.forks(), bip143_rule); +#if defined(KTH_CURRENCY_BCH) + auto const bip143 = false; #else - auto bip143 = false; + auto const bip143 = chain::script::is_enabled(program.forks(), bip143_rule); #endif auto const public_key = program.pop(); @@ -716,37 +1189,65 @@ interpreter::result interpreter::op_check_sig_verify(program& program) { // BIP62: An empty endorsement is not considered lax encoding. if ( ! parse_endorsement(sighash, distinguished, std::move(endorsement))) { - return error::invalid_signature_encoding; + return {error::invalid_signature_encoding, 0}; } // Parse DER signature into an EC signature. if ( ! parse_signature(signature, distinguished, bip66)) { - return bip66 ? error::invalid_signature_lax_encoding : error::invalid_signature_encoding; + return {bip66 ? error::invalid_signature_lax_encoding : error::invalid_signature_encoding, 0}; } // Version condition preserves independence of bip141 and bip143. auto version = bip143 ? program.version() : script_version::unversioned; - return chain::script::check_signature(signature, sighash, public_key, + auto const [res, size] = chain::script::check_signature(signature, sighash, public_key, script_code, program.transaction(), program.input_index(), - version, program.value()) - ? error::success - : error::incorrect_signature; + version, program.value()); + return {res ? error::success : error::incorrect_signature, size}; } inline interpreter::result interpreter::op_check_sig(program& program) { - auto const verified = op_check_sig_verify(program); + auto const [verified, size] = op_check_sig_common(program, error::op_check_sig); // BIP62: only lax encoding fails the operation. if (verified == error::invalid_signature_lax_encoding) { return error::op_check_sig; -} - + } program.push(verified == error::success); + if (verified == error::success) { + program.get_metrics().add_op_cost(program.top().size()); + program.get_metrics().add_sig_checks(1); + program.get_metrics().add_hash_iterations(size, true); + } return error::success; } +inline +interpreter::result interpreter::op_check_sig_verify(program& program) { + auto const [verified, size] = op_check_sig_common(program, error::op_check_sig_verify1); + program.get_metrics().add_op_cost(program.top().size()); + program.get_metrics().add_sig_checks(1); + program.get_metrics().add_hash_iterations(size, true); + return verified; +} + +inline +interpreter::result op_check_data_common(program& program, bool verify, interpreter::result err) { + // TODO: implement it + return err; +} + +inline +interpreter::result interpreter::op_check_data_sig(program& program) { + return op_check_data_common(program, false, error::op_check_data_sig); +} + +inline +interpreter::result interpreter::op_check_data_sig_verify(program& program) { + return op_check_data_common(program, true, error::op_check_data_sig_verify); +} + inline interpreter::result interpreter::op_check_multisig_verify(program& program) { int32_t key_count; @@ -833,9 +1334,10 @@ interpreter::result interpreter::op_check_multisig_verify(program& program) { while (true) { // Version condition preserves independence of bip141 and bip143. - if (chain::script::check_signature(signature, sighash, *public_key, + auto const [res, size] = chain::script::check_signature(signature, sighash, *public_key, script_code, program.transaction(), program.input_index(), - version, program.value())) { + version, program.value()); + if (res) { break; } @@ -862,8 +1364,7 @@ interpreter::result interpreter::op_check_multisig(program& program) { } inline -interpreter::result interpreter::op_check_locktime_verify( - program& program) { +interpreter::result interpreter::op_check_locktime_verify(program& program) { // BIP65: nop2 subsumed by checklocktimeverify when bip65 fork is active. if ( ! chain::script::is_enabled(program.forks(), rule_fork::bip65_rule)) { return op_nop(opcode::nop2); @@ -894,7 +1395,7 @@ interpreter::result interpreter::op_check_locktime_verify( } // The top stack item is positive, so cast is safe. - auto const locktime = static_cast(stack.int64()); + auto const locktime = uint64_t(stack.int64()); // BIP65: the stack locktime type differs from that of tx. if ((locktime < locktime_threshold) != @@ -907,8 +1408,7 @@ interpreter::result interpreter::op_check_locktime_verify( } inline -interpreter::result interpreter::op_check_sequence_verify( - program& program) { +interpreter::result interpreter::op_check_sequence_verify(program& program) { // BIP112: nop3 subsumed by checksequenceverify when bip112 fork is active. if ( ! chain::script::is_enabled(program.forks(), rule_fork::bip112_rule)) { return op_nop(opcode::nop3); @@ -934,7 +1434,7 @@ interpreter::result interpreter::op_check_sequence_verify( } // The top stack item is positive, so cast is safe. - auto const sequence = static_cast(stack.int64()); + auto const sequence = uint64_t(stack.int64()); // BIP112: the stack sequence is disabled, treat as nop3. if ((sequence & relative_locktime_disabled) != 0) { @@ -966,14 +1466,117 @@ interpreter::result interpreter::op_check_sequence_verify( : error::success; } +inline +interpreter::result interpreter::op_input_index(program& program) { + return error::op_input_index; +} + +inline +interpreter::result interpreter::op_active_bytecode(program& program) { + return error::op_active_bytecode; +} + +inline +interpreter::result interpreter::op_tx_version(program& program) { + return error::op_tx_version; +} + +inline +interpreter::result interpreter::op_tx_input_count(program& program) { + return error::op_tx_input_count; +} + +inline +interpreter::result interpreter::op_tx_output_count(program& program) { + return error::op_tx_output_count; +} + +inline +interpreter::result interpreter::op_tx_locktime(program& program) { + return error::op_tx_locktime; +} + +inline +interpreter::result interpreter::op_utxo_value(program& program) { + return error::op_utxo_value; +} + +inline +interpreter::result interpreter::op_utxo_bytecode(program& program) { + return error::op_utxo_bytecode; +} + +inline +interpreter::result interpreter::op_outpoint_tx_hash(program& program) { + return error::op_outpoint_tx_hash; +} + +inline +interpreter::result interpreter::op_outpoint_index(program& program) { + return error::op_outpoint_index; +} + +inline +interpreter::result interpreter::op_input_bytecode(program& program) { + return error::op_input_bytecode; +} + +inline +interpreter::result interpreter::op_input_sequence_number(program& program) { + return error::op_input_sequence_number; +} + +inline +interpreter::result interpreter::op_output_value(program& program) { + return error::op_output_value; +} + +inline +interpreter::result interpreter::op_output_bytecode(program& program) { + return error::op_output_bytecode; +} + +inline +interpreter::result interpreter::op_utxo_token_category(program& program) { + return error::op_utxo_token_category; +} + +inline +interpreter::result interpreter::op_utxo_token_commitment(program& program) { + return error::op_utxo_token_commitment; +} + +inline +interpreter::result interpreter::op_utxo_token_amount(program& program) { + return error::op_utxo_token_amount; +} + +inline +interpreter::result interpreter::op_output_token_category(program& program) { + return error::op_output_token_category; +} + +inline +interpreter::result interpreter::op_output_token_commitment(program& program) { + return error::op_output_token_commitment; +} + +inline +interpreter::result interpreter::op_output_token_amount(program& program) { + return error::op_output_token_amount; +} + + // It is expected that the compiler will produce a very efficient jump table. inline -interpreter::result interpreter::run_op(operation const& op, - program& program) { +interpreter::result interpreter::run_op(operation const& op, program& program) { auto const code = op.code(); KTH_ASSERT(op.data().empty() || op.is_push()); + program.get_metrics().add_op_cost(kth::may2025::opcode_cost); + switch (op.code()) { + // push value case opcode::push_size_0: case opcode::push_size_1: case opcode::push_size_2: @@ -1051,16 +1654,19 @@ interpreter::result interpreter::run_op(operation const& op, case opcode::push_size_74: case opcode::push_size_75: return op_push_size(program, op); + case opcode::push_one_size: return op_push_data(program, op.data(), max_uint8); case opcode::push_two_size: return op_push_data(program, op.data(), max_uint16); case opcode::push_four_size: return op_push_data(program, op.data(), max_uint32); - case opcode::push_negative_1: - return op_push_number(program, number::negative_1); + case opcode::reserved_80: return op_reserved(code); + + case opcode::push_negative_1: + return op_push_number(program, number::negative_1); case opcode::push_positive_1: return op_push_number(program, number::positive_1); case opcode::push_positive_2: @@ -1093,6 +1699,8 @@ interpreter::result interpreter::run_op(operation const& op, return op_push_number(program, number::positive_15); case opcode::push_positive_16: return op_push_number(program, number::positive_16); + + // control case opcode::nop: return op_nop(code); case opcode::reserved_98: @@ -1113,6 +1721,8 @@ interpreter::result interpreter::run_op(operation const& op, return op_verify(program); case opcode::return_: return op_return(program); + + // stack ops case opcode::toaltstack: return op_to_alt_stack(program); case opcode::fromaltstack: @@ -1151,24 +1761,92 @@ interpreter::result interpreter::run_op(operation const& op, return op_swap(program); case opcode::tuck: return op_tuck(program); - case opcode::disabled_cat: - return op_disabled(code); - case opcode::disabled_substr: - return op_disabled(code); - case opcode::disabled_left: - return op_disabled(code); - case opcode::disabled_right: - return op_disabled(code); + + // splice ops + case opcode::cat: + return op_cat(program); + case opcode::split: // after pythagoras/monolith upgrade (May 2018) + return op_split(program); + case opcode::reverse_bytes: + return op_reverse_bytes(program); + case opcode::num2bin: // after pythagoras/monolith upgrade (May 2018) + return op_num2bin(program); + case opcode::bin2num: // after pythagoras/monolith upgrade (May 2018) + return op_bin2num(program); case opcode::size: return op_size(program); + + // Native Introspection opcodes (Nullary) + case opcode::input_index: + return op_input_index(program); + case opcode::active_bytecode: + return op_active_bytecode(program); + case opcode::tx_version: + return op_tx_version(program); + case opcode::tx_input_count: + return op_tx_input_count(program); + case opcode::tx_output_count: + return op_tx_output_count(program); + case opcode::tx_locktime: + return op_tx_locktime(program); + + // Native Introspection opcodes (Unary) + // case OP_UTXOTOKENCATEGORY: + // case OP_UTXOTOKENCOMMITMENT: + // case OP_UTXOTOKENAMOUNT: + // case OP_OUTPUTTOKENCATEGORY: + // case OP_OUTPUTTOKENCOMMITMENT: + // case OP_OUTPUTTOKENAMOUNT: + // // These require native tokens (upgrade9) + // if ( ! nativeTokens) { + // return set_error(serror, ScriptError::BAD_OPCODE); + // } + // [[fallthrough]]; + // case OP_UTXOVALUE: + // case OP_UTXOBYTECODE: + // case OP_OUTPOINTTXHASH: + // case OP_OUTPOINTINDEX: + // case OP_INPUTBYTECODE: + // case OP_INPUTSEQUENCENUMBER: + // case OP_OUTPUTVALUE: + // case OP_OUTPUTBYTECODE: { + + case opcode::utxo_token_category: + return op_utxo_token_category(program); + case opcode::utxo_token_commitment: + return op_utxo_token_commitment(program); + case opcode::utxo_token_amount: + return op_utxo_token_amount(program); + case opcode::output_token_category: + return op_output_token_category(program); + case opcode::output_token_commitment: + return op_output_token_commitment(program); + case opcode::utxo_value: + return op_utxo_value(program); + case opcode::utxo_bytecode: + return op_utxo_bytecode(program); + case opcode::outpoint_tx_hash: + return op_outpoint_tx_hash(program); + case opcode::outpoint_index: + return op_outpoint_index(program); + case opcode::input_bytecode: + return op_input_bytecode(program); + case opcode::input_sequence_number: + return op_input_sequence_number(program); + case opcode::output_value: + return op_output_value(program); + case opcode::output_bytecode: + return op_output_bytecode(program); + + // bit logic case opcode::disabled_invert: return op_disabled(code); - case opcode::disabled_and: - return op_disabled(code); - case opcode::disabled_or: - return op_disabled(code); - case opcode::disabled_xor: - return op_disabled(code); + case opcode::and_: + return op_and(program); + case opcode::or_: + return op_or(program); + case opcode::xor_: + return op_xor(program); case opcode::equal: return op_equal(program); case opcode::equalverify: @@ -1177,6 +1855,8 @@ interpreter::result interpreter::run_op(operation const& op, return op_reserved(code); case opcode::reserved_138: return op_reserved(code); + + // numeric case opcode::add1: return op_add1(program); case opcode::sub1: @@ -1197,12 +1877,12 @@ interpreter::result interpreter::run_op(operation const& op, return op_add(program); case opcode::sub: return op_sub(program); - case opcode::disabled_mul: - return op_disabled(code); - case opcode::disabled_div: - return op_disabled(code); - case opcode::disabled_mod: - return op_disabled(code); + case opcode::mul: + return op_mul(program); + case opcode::div: + return op_div(program); + case opcode::mod: + return op_mod(program); case opcode::disabled_lshift: return op_disabled(code); case opcode::disabled_rshift: @@ -1229,8 +1909,11 @@ interpreter::result interpreter::run_op(operation const& op, return op_min(program); case opcode::max: return op_max(program); + case opcode::within: return op_within(program); + + // crypto case opcode::ripemd160: return op_ripemd160(program); case opcode::sha1: @@ -1247,10 +1930,18 @@ interpreter::result interpreter::run_op(operation const& op, return op_check_sig(program); case opcode::checksigverify: return op_check_sig_verify(program); + + case opcode::checkdatasig: + return op_check_data_sig(program); + case opcode::checkdatasigverify: + return op_check_data_sig_verify(program); + case opcode::checkmultisig: return op_check_multisig(program); case opcode::checkmultisigverify: return op_check_multisig_verify(program); + + // expansion case opcode::nop1: return op_nop(code); case opcode::checklocktimeverify: @@ -1264,36 +1955,12 @@ interpreter::result interpreter::run_op(operation const& op, case opcode::nop8: case opcode::nop9: case opcode::nop10: + //TODO: SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS return op_nop(code); //TODO(kth): Implement OP_CHECKDATASIG and OP_CHECKDATASIGVERIFY - case opcode::reserved_186: - case opcode::reserved_187: - case opcode::reserved_188: - case opcode::reserved_189: - case opcode::reserved_190: - case opcode::reserved_191: - case opcode::reserved_192: - case opcode::reserved_193: - case opcode::reserved_194: - case opcode::reserved_195: - case opcode::reserved_196: - case opcode::reserved_197: - case opcode::reserved_198: - case opcode::reserved_199: - case opcode::reserved_200: - case opcode::reserved_201: - case opcode::reserved_202: - case opcode::reserved_203: - case opcode::reserved_204: - case opcode::reserved_205: - case opcode::reserved_206: - case opcode::reserved_207: - case opcode::reserved_208: - case opcode::reserved_209: - case opcode::reserved_210: - case opcode::reserved_211: + case opcode::reserved_212: case opcode::reserved_213: case opcode::reserved_214: diff --git a/include/kth/domain/impl/machine/operation.ipp b/include/kth/domain/impl/machine/operation.ipp index c568691d7..915fc4b92 100644 --- a/include/kth/domain/impl/machine/operation.ipp +++ b/include/kth/domain/impl/machine/operation.ipp @@ -21,7 +21,7 @@ inline operation::operation(data_chunk&& uncoded, bool minimal) : code_(opcode_from_data(uncoded, minimal)) , data_(std::move(uncoded)) - , valid_( ! is_oversized()) + , valid_( ! is_oversized(max_push_data_size_legacy)) //TODO: max_push_data_size_legacy change on 2025 May { if ( ! valid_) { reset(); @@ -38,7 +38,7 @@ inline operation::operation(data_chunk const& uncoded, bool minimal) : code_(opcode_from_data(uncoded, minimal)) , data_(uncoded) - , valid_( ! is_oversized()) + , valid_( ! is_oversized(max_push_data_size_legacy)) //TODO: max_push_data_size_legacy change on 2025 May { if ( ! valid_) { reset(); @@ -276,7 +276,7 @@ bool operation::is_positive(opcode code) { // opcode: [80, 98, 137, 138, 186..255] inline bool operation::is_reserved(opcode code) { - constexpr auto op_186 = static_cast(opcode::reserved_186); + constexpr auto op_212 = static_cast(opcode::reserved_212); constexpr auto op_255 = static_cast(opcode::reserved_255); switch (code) { @@ -286,8 +286,8 @@ bool operation::is_reserved(opcode code) { case opcode::reserved_138: return true; default: - auto const value = static_cast(code); - return value >= op_186 && value <= op_255; + auto const value = uint8_t(code); + return value >= op_212 && value <= op_255; } } @@ -301,31 +301,43 @@ bool operation::is_reserved(opcode code) { // this was an unintended consequence of range testing enums. //***************************************************************************** inline -bool operation::is_disabled(opcode code) { +bool operation::is_disabled(opcode code, uint32_t active_forks) { + // SCRIPT_64_BIT_INTEGERS = (1U << 24), + constexpr auto script_64_bit_integers = 1U << 24; // the flag is repeated, it is in Consensus lib. switch (code) { - case opcode::disabled_cat: - case opcode::disabled_substr: - case opcode::disabled_left: - case opcode::disabled_right: case opcode::disabled_invert: - case opcode::disabled_and: - case opcode::disabled_or: - case opcode::disabled_xor: case opcode::disabled_mul2: case opcode::disabled_div2: - case opcode::disabled_mul: - case opcode::disabled_div: - case opcode::disabled_mod: case opcode::disabled_lshift: case opcode::disabled_rshift: case opcode::disabled_verif: case opcode::disabled_vernotif: return true; + case opcode::mul: + return ! is_enabled(active_forks, rule_fork::bch_gauss); default: return false; } } +// 2025 - Still disabled opcodes +// static bool IsOpcodeDisabled(opcodetype opcode, uint32_t flags) { +// switch (opcode) { +// case OP_INVERT: +// case OP_2MUL: +// case OP_2DIV: +// case OP_LSHIFT: +// case OP_RSHIFT: +// // Disabled opcodes. +// return true; +// case OP_MUL: +// return (flags & SCRIPT_64_BIT_INTEGERS) == 0; +// default: +// break; +// } +// return false; +// } + //***************************************************************************** // CONSENSUS: in order to properly treat VERIF and VERNOTIF as disabled (see // is_disabled comments) those codes must not be included here. @@ -377,8 +389,8 @@ bool operation::is_positive() const { } inline -bool operation::is_disabled() const { - return is_disabled(code_); +bool operation::is_disabled(uint32_t active_forks) const { + return is_disabled(code_, active_forks); } inline @@ -392,9 +404,9 @@ bool operation::is_relaxed_push() const { } inline -bool operation::is_oversized() const { +bool operation::is_oversized(size_t max_size) const { // bit.ly/2eSDkOJ - return data_.size() > max_push_data_size; + return data_.size() > max_size; } inline diff --git a/include/kth/domain/impl/machine/program.ipp b/include/kth/domain/impl/machine/program.ipp index 353826ae6..dfc3fd8b6 100644 --- a/include/kth/domain/impl/machine/program.ipp +++ b/include/kth/domain/impl/machine/program.ipp @@ -22,6 +22,18 @@ namespace kth::domain::machine { using script_version = ::kth::infrastructure::machine::script_version; +// Metrics +//----------------------------------------------------------------------------- +inline +metrics& program::get_metrics() { + return metrics_; +} + +inline +metrics const& program::get_metrics() const { + return metrics_; +} + // Constant registers. //----------------------------------------------------------------------------- @@ -36,6 +48,23 @@ uint32_t program::forks() const { return forks_; } +inline +size_t program::max_script_element_size() const { + auto const galois_enabled = chain::script::is_enabled(forks(), rule_fork::bch_galois); + return galois_enabled ? ::kth::may2025::max_push_data_size : max_push_data_size_legacy; +} + +inline +size_t program::max_integer_size_legacy() const { + auto const gauss_enabled = chain::script::is_enabled(forks(), rule_fork::bch_gauss); + return gauss_enabled ? max_number_size_64_bits : max_number_size_32_bits; +} + +inline +bool program::is_chip_vm_limits_enabled() const { + return chain::script::is_enabled(forks(), rule_fork::bch_galois); +} + inline uint32_t program::input_index() const { return input_index_; @@ -99,7 +128,7 @@ bool program::increment_operation_count(operation const& op) { inline bool program::increment_operation_count(int32_t public_keys) { - static auto const max_keys = static_cast(max_script_public_keys); + static auto const max_keys = int32_t(max_script_public_keys); // bit.ly/2d1bsdB if (public_keys < 0 || public_keys > max_keys) { @@ -162,9 +191,10 @@ void program::push_copy(value_type const& item) { //----------------------------------------------------------------------------- // This must be guarded. -inline data_chunk program::pop() { +inline +data_chunk program::pop() { KTH_ASSERT( ! empty()); - auto const value = primary_.back(); + auto value = std::move(primary_.back()); primary_.pop_back(); return value; } @@ -172,7 +202,7 @@ inline data_chunk program::pop() { inline bool program::pop(int32_t& out_value) { number value; - if ( ! pop(value)) { + if ( ! pop(value, max_integer_size_legacy())) { return false; } @@ -181,20 +211,31 @@ bool program::pop(int32_t& out_value) { } inline -bool program::pop(number& out_number, size_t maxiumum_size) { - return !empty() && out_number.set_data(pop(), maxiumum_size); +bool program::pop(int64_t& out_value) { + number value; + if ( ! pop(value, max_integer_size_legacy())) { + return false; + } + + out_value = value.int64(); + return true; +} + +inline +bool program::pop(number& out_number, size_t maximum_size) { + return !empty() && out_number.set_data(pop(), maximum_size); } inline bool program::pop_binary(number& first, number& second) { // The right hand side number is at the top of the stack. - return pop(first) && pop(second); + return pop(first, max_integer_size_legacy()) && pop(second, max_integer_size_legacy()); } inline bool program::pop_ternary(number& first, number& second, number& third) { // The upper bound is at stack top, lower bound next, value next. - return pop(first) && pop(second) && pop(third); + return pop(first, max_integer_size_legacy()) && pop(second, max_integer_size_legacy()) && pop(third, max_integer_size_legacy()); } // Determines if popped value is valid post-pop stack index and returns index. @@ -211,7 +252,7 @@ bool program::pop_position(stack_iterator& out_position) { return false; } - auto const index = static_cast(signed_index); + auto const index = uint32_t(signed_index); if (index >= size()) { return false; @@ -332,11 +373,25 @@ data_stack::value_type& program::item(size_t index) { return *position(index); } +// This must be guarded. inline -bool program::top(number& out_number, size_t maxiumum_size) const { - return !empty() && out_number.set_data(item(0), maxiumum_size); +data_chunk& program::top() { + KTH_ASSERT( ! empty()); + return primary_.back(); } +inline +data_chunk const& program::top() const { + KTH_ASSERT( ! empty()); + return primary_.back(); +} + +inline +bool program::top(number& out_number, size_t maximum_size) const { + return !empty() && out_number.set_data(item(0), maximum_size); +} + + inline program::stack_iterator program::position(size_t index) const { // Subtracting 1 makes the stack indexes zero-based (unlike satoshi). @@ -351,6 +406,11 @@ program::stack_mutable_iterator program::position(size_t index) { return (primary_.end() - 1) - index; } +inline +size_t program::index(stack_iterator const& position) const { + return size() - (position - primary_.begin()); +} + // Pop jump-to-end, push all back, use to construct a script. inline operation::list program::subscript() const { @@ -368,6 +428,12 @@ size_t program::size() const { return primary_.size(); } +inline +size_t program::conditional_stack_size() const { + return condition_.size(); +} + + // Alternate stack. //----------------------------------------------------------------------------- diff --git a/include/kth/domain/machine/interpreter.hpp b/include/kth/domain/machine/interpreter.hpp index 1a320ef9c..f3221e05a 100644 --- a/include/kth/domain/machine/interpreter.hpp +++ b/include/kth/domain/machine/interpreter.hpp @@ -119,9 +119,33 @@ class KD_API interpreter { static result op_tuck(program& program); + static + result op_cat(program& program); + + static + result op_split(program& program); + + static + result op_reverse_bytes(program& program); + + static + result op_num2bin(program& program); + + static + result op_bin2num(program& program); + static result op_size(program& program); + static + result op_and(program& program); + + static + result op_or(program& program); + + static + result op_xor(program& program); + static result op_equal(program& program); @@ -152,6 +176,15 @@ class KD_API interpreter { static result op_sub(program& program); + static + result op_mul(program& program); + + static + result op_div(program& program); + + static + result op_mod(program& program); + static result op_bool_and(program& program); @@ -206,11 +239,17 @@ class KD_API interpreter { static result op_codeseparator(program& program, operation const& op); + static + result op_check_sig(program& program); + static result op_check_sig_verify(program& program); static - result op_check_sig(program& program); + result op_check_data_sig(program& program); + + static + result op_check_data_sig_verify(program& program); static result op_check_multisig_verify(program& program); @@ -224,6 +263,66 @@ class KD_API interpreter { static result op_check_sequence_verify(program& program); + static + result op_input_index(program& program); + + static + result op_active_bytecode(program& program); + + static + result op_tx_version(program& program); + + static + result op_tx_input_count(program& program); + + static + result op_tx_output_count(program& program); + + static + result op_tx_locktime(program& program); + + static + result op_utxo_value(program& program); + + static + result op_utxo_bytecode(program& program); + + static + result op_outpoint_tx_hash(program& program); + + static + result op_outpoint_index(program& program); + + static + result op_input_bytecode(program& program); + + static + result op_input_sequence_number(program& program); + + static + result op_output_value(program& program); + + static + result op_output_bytecode(program& program); + + static + result op_utxo_token_category(program& program); + + static + result op_utxo_token_commitment(program& program); + + static + result op_utxo_token_amount(program& program); + + static + result op_output_token_category(program& program); + + static + result op_output_token_commitment(program& program); + + static + result op_output_token_amount(program& program); + /// Run program script. static code run(program& program); diff --git a/include/kth/domain/machine/metrics.hpp b/include/kth/domain/machine/metrics.hpp new file mode 100644 index 000000000..40d1a6245 --- /dev/null +++ b/include/kth/domain/machine/metrics.hpp @@ -0,0 +1,98 @@ +// Copyright (c) 2016-2024 Knuth Project developers. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef KTH_DOMAIN_MACHINE_METRICS_HPP +#define KTH_DOMAIN_MACHINE_METRICS_HPP + +#include +#include + +#include +#include +#include + +namespace kth::domain::machine { + +inline constexpr +bool is_vm_limits_standard(uint32_t script_flags) { + // This constant is defined in consensus lib, but we also need it here. + constexpr uint32_t verify_flags_enable_vm_limits_standard = (1U << 29); //SCRIPT_VM_LIMITS_STANDARD + return script_flags & verify_flags_enable_vm_limits_standard; +} + +struct KD_API metrics { + using script_limits_opt_t = std::optional; + + // Getters + uint32_t sig_checks() const { + return sig_checks_; + } + + uint64_t op_cost() const { + return op_cost_; + } + + uint64_t hash_digest_iterations() const { + return hash_digest_iterations_; + } + + // Setters + void add_op_cost(uint32_t cost) { + op_cost_ += int64_t(cost); + } + + void add_hash_iterations(uint32_t message_length, bool is_two_round_hash /* set to true iff OP_HASH256 or OP_HASH160 */) { + hash_digest_iterations_ += may2025::calculate_hash_iters(message_length, is_two_round_hash); + } + + void add_sig_checks(int n_checks) { + sig_checks_ += n_checks; + } + + // Checks + bool is_over_op_cost_limit(uint32_t script_flags) const { + return script_limits && composite_op_cost(script_flags) > script_limits->op_cost_limit(); + } + + bool is_over_hash_iters_limit() const { + return script_limits && hash_digest_iterations() > script_limits->hash_iters_limit(); + } + + bool has_valid_script_limits() const { + return script_limits.has_value(); + } + + void set_script_limits(uint32_t script_flags, uint64_t script_sig_size) { + script_limits.emplace(is_vm_limits_standard(script_flags), script_sig_size); + } + + script_limits_opt_t const& get_script_limits() const { + return script_limits; + } + + // Returns the composite value that is: nOpCost + nHashDigestIterators * {192 or 64} + nSigChecks * 26,000 + // Consensus code uses a 64 for the hashing iter cost, standard/relay code uses the more restrictive cost of 192. + uint64_t composite_op_cost(uint32_t script_flags) const { + uint64_t const factor = may2025::hash_iter_op_cost_factor(is_vm_limits_standard(script_flags)); + return op_cost_ // base cost: encompasses ops + pushes, etc + // additional cost: add hash iterations * {192 or 64} + + hash_digest_iterations_ * factor + // additional cost: add sig checks * 26,000 + + uint64_t(sig_checks_) * ::kth::may2025::sig_check_cost_factor; + } + +private: + uint32_t sig_checks_ = 0; + + /** CHIP-2021-05-vm-limits: Targeted Virtual Machine Limits */ + uint64_t op_cost_ = 0; + uint64_t hash_digest_iterations_ = 0; + script_limits_opt_t script_limits; + + uint64_t get_hash_iter_cost_factor(uint32_t script_flags) const; +}; + +} // namespace kth::domain::machine + +#endif // KTH_DOMAIN_MACHINE_METRICS_HPP diff --git a/include/kth/domain/machine/opcode.hpp b/include/kth/domain/machine/opcode.hpp index ed8d3432a..e3af79c56 100644 --- a/include/kth/domain/machine/opcode.hpp +++ b/include/kth/domain/machine/opcode.hpp @@ -30,320 +30,309 @@ enum class opcode : uint8_t { //------------------------------------------------------------------------- // is_relaxed_push, is_push (excluding reserved_80) - push_size_0 = 0, // is_version (pushes [] to the stack, not 0) - push_size_1 = 1, - push_size_2 = 2, - push_size_3 = 3, - push_size_4 = 4, - push_size_5 = 5, - push_size_6 = 6, - push_size_7 = 7, - push_size_8 = 8, - push_size_9 = 9, - push_size_10 = 10, - push_size_11 = 11, - push_size_12 = 12, - push_size_13 = 13, - push_size_14 = 14, - push_size_15 = 15, - push_size_16 = 16, - push_size_17 = 17, - push_size_18 = 18, - push_size_19 = 19, - push_size_20 = 20, - push_size_21 = 21, - push_size_22 = 22, - push_size_23 = 23, - push_size_24 = 24, - push_size_25 = 25, - push_size_26 = 26, - push_size_27 = 27, - push_size_28 = 28, - push_size_29 = 29, - push_size_30 = 30, - push_size_31 = 31, - push_size_32 = 32, - push_size_33 = 33, - push_size_34 = 34, - push_size_35 = 35, - push_size_36 = 36, - push_size_37 = 37, - push_size_38 = 38, - push_size_39 = 39, - push_size_40 = 40, - push_size_41 = 41, - push_size_42 = 42, - push_size_43 = 43, - push_size_44 = 44, - push_size_45 = 45, - push_size_46 = 46, - push_size_47 = 47, - push_size_48 = 48, - push_size_49 = 49, - push_size_50 = 50, - push_size_51 = 51, - push_size_52 = 52, - push_size_53 = 53, - push_size_54 = 54, - push_size_55 = 55, - push_size_56 = 56, - push_size_57 = 57, - push_size_58 = 58, - push_size_59 = 59, - push_size_60 = 60, - push_size_61 = 61, - push_size_62 = 62, - push_size_63 = 63, - push_size_64 = 64, - push_size_65 = 65, - push_size_66 = 66, - push_size_67 = 67, - push_size_68 = 68, - push_size_69 = 69, - push_size_70 = 70, - push_size_71 = 71, - push_size_72 = 72, - push_size_73 = 73, - push_size_74 = 74, - push_size_75 = 75, - push_one_size = 76, - push_two_size = 77, - push_four_size = 78, - push_negative_1 = 79, // is_numeric - reserved_80 = 80, // [reserved] - push_positive_1 = 81, // is_numeric, is_positive, is_version - push_positive_2 = 82, // is_numeric, is_positive, is_version - push_positive_3 = 83, // is_numeric, is_positive, is_version - push_positive_4 = 84, // is_numeric, is_positive, is_version - push_positive_5 = 85, // is_numeric, is_positive, is_version - push_positive_6 = 86, // is_numeric, is_positive, is_version - push_positive_7 = 87, // is_numeric, is_positive, is_version - push_positive_8 = 88, // is_numeric, is_positive, is_version - push_positive_9 = 89, // is_numeric, is_positive, is_version - push_positive_10 = 90, // is_numeric, is_positive, is_version - push_positive_11 = 91, // is_numeric, is_positive, is_version - push_positive_12 = 92, // is_numeric, is_positive, is_version - push_positive_13 = 93, // is_numeric, is_positive, is_version - push_positive_14 = 94, // is_numeric, is_positive, is_version - push_positive_15 = 95, // is_numeric, is_positive, is_version - push_positive_16 = 96, // is_numeric, is_positive, is_version +// push value + push_size_0 = 0x00, // 0 // is_version (pushes [] to the stack, not 0) + push_size_1 = 0x01, // 1 + push_size_2 = 0x02, // 2 + push_size_3 = 0x03, // 3 + push_size_4 = 0x04, // 4 + push_size_5 = 0x05, // 5 + push_size_6 = 0x06, // 6 + push_size_7 = 0x07, // 7 + push_size_8 = 0x08, // 8 + push_size_9 = 0x09, // 9 + push_size_10 = 0x0a, // 10 + push_size_11 = 0x0b, // 11 + push_size_12 = 0x0c, // 12 + push_size_13 = 0x0d, // 13 + push_size_14 = 0x0e, // 14 + push_size_15 = 0x0f, // 15 + push_size_16 = 0x10, // 16 + push_size_17 = 0x11, // 17 + push_size_18 = 0x12, // 18 + push_size_19 = 0x13, // 19 + push_size_20 = 0x14, // 20 + push_size_21 = 0x15, // 21 + push_size_22 = 0x16, // 22 + push_size_23 = 0x17, // 23 + push_size_24 = 0x18, // 24 + push_size_25 = 0x19, // 25 + push_size_26 = 0x1a, // 26 + push_size_27 = 0x1b, // 27 + push_size_28 = 0x1c, // 28 + push_size_29 = 0x1d, // 29 + push_size_30 = 0x1e, // 30 + push_size_31 = 0x1f, // 31 + push_size_32 = 0x20, // 32 + push_size_33 = 0x21, // 33 + push_size_34 = 0x22, // 34 + push_size_35 = 0x23, // 35 + push_size_36 = 0x24, // 36 + push_size_37 = 0x25, // 37 + push_size_38 = 0x26, // 38 + push_size_39 = 0x27, // 39 + push_size_40 = 0x28, // 40 + push_size_41 = 0x29, // 41 + push_size_42 = 0x2a, // 42 + push_size_43 = 0x2b, // 43 + push_size_44 = 0x2c, // 44 + push_size_45 = 0x2d, // 45 + push_size_46 = 0x2e, // 46 + push_size_47 = 0x2f, // 47 + push_size_48 = 0x30, // 48 + push_size_49 = 0x31, // 49 + push_size_50 = 0x32, // 50 + push_size_51 = 0x33, // 51 + push_size_52 = 0x34, // 52 + push_size_53 = 0x35, // 53 + push_size_54 = 0x36, // 54 + push_size_55 = 0x37, // 55 + push_size_56 = 0x38, // 56 + push_size_57 = 0x39, // 57 + push_size_58 = 0x3a, // 58 + push_size_59 = 0x3b, // 59 + push_size_60 = 0x3c, // 60 + push_size_61 = 0x3d, // 61 + push_size_62 = 0x3e, // 62 + push_size_63 = 0x3f, // 63 + push_size_64 = 0x40, // 64 + push_size_65 = 0x41, // 65 + push_size_66 = 0x42, // 66 + push_size_67 = 0x43, // 67 + push_size_68 = 0x44, // 68 + push_size_69 = 0x45, // 69 + push_size_70 = 0x46, // 70 + push_size_71 = 0x47, // 71 + push_size_72 = 0x48, // 72 + push_size_73 = 0x49, // 73 + push_size_74 = 0x4a, // 74 + push_size_75 = 0x4b, // 75 + push_one_size = 0x4c, // 76 + push_two_size = 0x4d, // 77 + push_four_size = 0x4e, // 78 + + push_negative_1 = 0x4f, // 79 is_numeric + reserved_80 = 0x50, // 80 [reserved] + + push_positive_1 = 0x51, // 81, is_numeric, is_positive, is_version + push_positive_2 = 0x52, // 82, is_numeric, is_positive, is_version + push_positive_3 = 0x53, // 83, is_numeric, is_positive, is_version + push_positive_4 = 0x54, // 84, is_numeric, is_positive, is_version + push_positive_5 = 0x55, // 85, is_numeric, is_positive, is_version + push_positive_6 = 0x56, // 86, is_numeric, is_positive, is_version + push_positive_7 = 0x57, // 87, is_numeric, is_positive, is_version + push_positive_8 = 0x58, // 88, is_numeric, is_positive, is_version + push_positive_9 = 0x59, // 89, is_numeric, is_positive, is_version + push_positive_10 = 0x5a, // 90, is_numeric, is_positive, is_version + push_positive_11 = 0x5b, // 91, is_numeric, is_positive, is_version + push_positive_12 = 0x5c, // 92, is_numeric, is_positive, is_version + push_positive_13 = 0x5d, // 93, is_numeric, is_positive, is_version + push_positive_14 = 0x5e, // 94, is_numeric, is_positive, is_version + push_positive_15 = 0x5f, // 95, is_numeric, is_positive, is_version + push_positive_16 = 0x60, // 96, is_numeric, is_positive, is_version //------------------------------------------------------------------------- // is_counted - nop = 97, - reserved_98 = 98, // [ver] - if_ = 99, // is_conditional - notif = 100, // is_conditional - disabled_verif = 101, // is_disabled - disabled_vernotif = 102,// is_disabled - else_ = 103, // is_conditional - endif = 104, // is_conditional - verify = 105, - return_ = 106, - toaltstack = 107, - fromaltstack = 108, - drop2 = 109, - dup2 = 110, - dup3 = 111, - over2 = 112, - rot2 = 113, - swap2 = 114, - ifdup = 115, - depth = 116, - drop = 117, - dup = 118, - nip = 119, - over = 120, - pick = 121, - roll = 122, - rot = 123, - swap = 124, - tuck = 125, - disabled_cat = 126, // is_disabled - disabled_substr = 127, // is_disabled - disabled_left = 128, // is_disabled - disabled_right = 129, // is_disabled - size = 130, - disabled_invert = 131, // is_disabled - disabled_and = 132, // is_disabled - disabled_or = 133, // is_disabled - disabled_xor = 134, // is_disabled - equal = 135, - equalverify = 136, - reserved_137 = 137, // [reserved1] - reserved_138 = 138, // [reserved2] - add1 = 139, - sub1 = 140, - disabled_mul2 = 141, // is_disabled - disabled_div2 = 142, // is_disabled - negate = 143, - abs = 144, - not_ = 145, - nonzero = 146, - add = 147, - sub = 148, - disabled_mul = 149, // is_disabled - disabled_div = 150, // is_disabled - disabled_mod = 151, // is_disabled - disabled_lshift = 152, // is_disabled - disabled_rshift = 153, // is_disabled - booland = 154, - boolor = 155, - numequal = 156, - numequalverify = 157, - numnotequal = 158, - lessthan = 159, - greaterthan = 160, - lessthanorequal = 161, - greaterthanorequal = 162, - min = 163, - max = 164, - within = 165, - ripemd160 = 166, - sha1 = 167, - sha256 = 168, - hash160 = 169, - hash256 = 170, - codeseparator = 171, - checksig = 172, - checksigverify = 173, - checkmultisig = 174, - checkmultisigverify = 175, - - - nop1 = 176, - nop2 = 177, - checklocktimeverify = nop2, - nop3 = 178, - checksequenceverify = nop3, - nop4 = 179, - nop5 = 180, - nop6 = 181, - nop7 = 182, - nop8 = 183, - nop9 = 184, - nop10 = 185, - - // More crypto - checkdatasig = 0xba, - checkdatasigverify = 0xbb, - - // additional byte string operations - reversebytes = 0xbc, - - // Available codepoints - // 0xbd, - // 0xbe, - // 0xbf, - - // Native Introspection opcodes - inputindex = 0xc0, - activebytecode = 0xc1, - txversion = 0xc2, - txinputcount = 0xc3, - txoutputcount = 0xc4, - txlocktime = 0xc5, - utxovalue = 0xc6, - utxobytecode = 0xc7, - outpointtxhash = 0xc8, - outpointindex = 0xc9, - inputbytecode = 0xca, - inputsequencenumber = 0xcb, - outputvalue = 0xcc, - outputbytecode = 0xcd, - - // Native Introspection of tokens (SCRIPT_ENABLE_TOKENS must be set) - utxotokencategory = 0xce, - utxotokencommitment = 0xcf, - utxotokenamount = 0xd0, - outputtokencategory = 0xd1, - outputtokencommitment = 0xd2, - outputtokenamount = 0xd3, - - reserved3 = 0xd4, - reserved4 = 0xd5, - - // The first op_code value after all defined opcodes - first_undefined_op_value, - - // Invalid opcode if executed, but used for special token prefix if at - // position 0 in scriptPubKey. See: primitives/token.h - special_token_prefix = 0xef, - - invalidopcode = 0xff, ///< Not a real OPCODE! - - reserved_186 = 186, - reserved_187 = 187, - - reserved_188 = 188, - reserved_189 = 189, - reserved_190 = 190, - reserved_191 = 191, - reserved_192 = 192, - reserved_193 = 193, - reserved_194 = 194, - reserved_195 = 195, - reserved_196 = 196, - reserved_197 = 197, - reserved_198 = 198, - reserved_199 = 199, - reserved_200 = 200, - reserved_201 = 201, - reserved_202 = 202, - reserved_203 = 203, - reserved_204 = 204, - reserved_205 = 205, - reserved_206 = 206, - reserved_207 = 207, - reserved_208 = 208, - reserved_209 = 209, - reserved_210 = 210, - reserved_211 = 211, - reserved_212 = 212, - reserved_213 = 213, - reserved_214 = 214, - reserved_215 = 215, - reserved_216 = 216, - reserved_217 = 217, - reserved_218 = 218, - reserved_219 = 219, - reserved_220 = 220, - reserved_221 = 221, - reserved_222 = 222, - reserved_223 = 223, - reserved_224 = 224, - reserved_225 = 225, - reserved_226 = 226, - reserved_227 = 227, - reserved_228 = 228, - reserved_229 = 229, - reserved_230 = 230, - reserved_231 = 231, - reserved_232 = 232, - reserved_233 = 233, - reserved_234 = 234, - reserved_235 = 235, - reserved_236 = 236, - reserved_237 = 237, - reserved_238 = 238, - reserved_239 = 239, - reserved_240 = 240, - reserved_241 = 241, - reserved_242 = 242, - reserved_243 = 243, - reserved_244 = 244, - reserved_245 = 245, - reserved_246 = 246, - reserved_247 = 247, - reserved_248 = 248, - reserved_249 = 249, - reserved_250 = 250, - reserved_251 = 251, - reserved_252 = 252, - reserved_253 = 253, - reserved_254 = 254, - reserved_255 = 255 +// control + nop = 0x61, // 97 + reserved_98 = 0x62, // 98 [ver] + if_ = 0x63, // 99 is_conditional + notif = 0x64, // 100 is_conditional + disabled_verif = 0x65, // 101 is_disabled + disabled_vernotif = 0x66, // 102 is_disabled + else_ = 0x67, // 103 is_conditional + endif = 0x68, // 104 is_conditional + verify = 0x69, // 105 + return_ = 0x6a, // 106 + +// stack ops + toaltstack = 0x6b, // 107 + fromaltstack = 0x6c, // 108 + drop2 = 0x6d, // 109 + dup2 = 0x6e, // 110 + dup3 = 0x6f, // 111 + over2 = 0x70, // 112 + rot2 = 0x71, // 113 + swap2 = 0x72, // 114 + ifdup = 0x73, // 115 + depth = 0x74, // 116 + drop = 0x75, // 117 + dup = 0x76, // 118 + nip = 0x77, // 119 + over = 0x78, // 120 + pick = 0x79, // 121 + roll = 0x7a, // 122 + rot = 0x7b, // 123 + swap = 0x7c, // 124 + tuck = 0x7d, // 125 + +// splice ops + cat = 0x7e, // 126 + split = 0x7f, // 127 was called substr before (disabled and re-enabled after pythagoras/monolith upgrade, May 2018) + num2bin = 0x80, // 128 was called left before (disabled and re-enabled after pythagoras/monolith upgrade, May 2018) + bin2num = 0x81, // 129 was called right before (disabled and re-enabled after pythagoras/monolith upgrade, May 2018) + size = 0x82, // 130 + +// bit logic + disabled_invert = 0x83, // 131, is_disabled + and_ = 0x84, // 132, disabled and re-enabled after pythagoras/monolith upgrade, May 2018 + or_ = 0x85, // 133, disabled and re-enabled after pythagoras/monolith upgrade, May 2018 + xor_ = 0x86, // 134, disabled and re-enabled after pythagoras/monolith upgrade, May 2018 + equal = 0x87, // 135 + equalverify = 0x88, // 136 + reserved_137 = 0x89, // 137 [reserved1] + reserved_138 = 0x8a, // 138 [reserved2] + +// numeric + add1 = 0x8b, // 139 + sub1 = 0x8c, // 140 + disabled_mul2 = 0x8d, // 141 is_disabled + disabled_div2 = 0x8e, // 142 is_disabled + negate = 0x8f, // 143 + abs = 0x90, // 144 + not_ = 0x91, // 145 + nonzero = 0x92, // 146 + + add = 0x93, // 147 + sub = 0x94, // 148 + mul = 0x95, // 149, disabled and re-enabled after gauss/upgrade8 upgrade, May 2022 + div = 0x96, // 150, disabled and re-enabled after pythagoras/monolith upgrade, May 2018 + mod = 0x97, // 151, disabled and re-enabled after pythagoras/monolith upgrade, May 2018 + disabled_lshift = 0x98, // 152 is_disabled + disabled_rshift = 0x99, // 153 is_disabled + + booland = 0x9a, // 154 + boolor = 0x9b, // 155 + numequal = 0x9c, // 156 + numequalverify = 0x9d, // 157 + numnotequal = 0x9e, // 158 + lessthan = 0x9f, // 159 + greaterthan = 0xa0, // 160 + lessthanorequal = 0xa1, // 161 + greaterthanorequal = 0xa2, // 162 + min = 0xa3, // 163 + max = 0xa4, // 164 + + within = 0xa5, // 165 + +// crypto + ripemd160 = 0xa6, // 166 + sha1 = 0xa7, // 167 + sha256 = 0xa8, // 168 + hash160 = 0xa9, // 169 + hash256 = 0xaa, // 170 + codeseparator = 0xab, // 171 + checksig = 0xac, // 172 + checksigverify = 0xad, // 173 + checkmultisig = 0xae, // 174 + checkmultisigverify = 0xaf, // 175 + +// expansion + nop1 = 0xb0, // 176 + nop2 = 0xb1, // 177 + checklocktimeverify = nop2, // 177 + nop3 = 0xb2, // 178 + checksequenceverify = nop3, // 178 + nop4 = 0xb3, // 179 + nop5 = 0xb4, // 180 + nop6 = 0xb5, // 181 + nop7 = 0xb6, // 182 + nop8 = 0xb7, // 183 + nop9 = 0xb8, // 184 + nop10 = 0xb9, // 185 + +// more crypto + checkdatasig = 0xba, // 186 + checkdatasigverify = 0xbb, // 187 + +// additional byte string operations + reverse_bytes = 0xbc, // 188 + +// Available codepoints + // 0xbd, // 189 + // 0xbe, // 190 + // 0xbf, // 191 + +// Native Introspection opcodes + input_index = 0xc0, // 192 + active_bytecode = 0xc1, // 193 + tx_version = 0xc2, // 194 + tx_input_count = 0xc3, // 195 + tx_output_count = 0xc4, // 196 + tx_locktime = 0xc5, // 197 + utxo_value = 0xc6, // 198 + utxo_bytecode = 0xc7, // 199 + outpoint_tx_hash = 0xc8, // 200 + outpoint_index = 0xc9, // 201 + input_bytecode = 0xca, // 202 + input_sequence_number = 0xcb, // 203 + output_value = 0xcc, // 204 + output_bytecode = 0xcd, // 205 + +// Native Introspection of tokens (SCRIPT_ENABLE_TOKENS must be set) + utxo_token_category = 0xce, // 206 + utxo_token_commitment = 0xcf, // 207 + utxo_token_amount = 0xd0, // 208 + output_token_category = 0xd1, // 209 + output_token_commitment = 0xd2, // 210 + output_token_amount = 0xd3, // 211 + + reserved_212 = 0xd4, // 212 + reserved_213 = 0xd5, // 213 + reserved_214 = 0xd6, // 214 + +// The first op_code value after all defined opcodes + first_undefined_op_value = reserved_214, // 0xd6 214 + + reserved_215 = 0xd7, // 215 + reserved_216 = 0xd8, // 216 + reserved_217 = 0xd9, // 217 + reserved_218 = 0xda, // 218 + reserved_219 = 0xdb, // 219 + reserved_220 = 0xdc, // 220 + reserved_221 = 0xdd, // 221 + reserved_222 = 0xde, // 222 + reserved_223 = 0xdf, // 223 + reserved_224 = 0xe0, // 224 + reserved_225 = 0xe1, // 225 + reserved_226 = 0xe2, // 226 + reserved_227 = 0xe3, // 227 + reserved_228 = 0xe4, // 228 + reserved_229 = 0xe5, // 229 + reserved_230 = 0xe6, // 230 + reserved_231 = 0xe7, // 231 + reserved_232 = 0xe8, // 232 + reserved_233 = 0xe9, // 233 + reserved_234 = 0xea, // 234 + reserved_235 = 0xeb, // 235 + reserved_236 = 0xec, // 236 + reserved_237 = 0xed, // 237 + reserved_238 = 0xee, // 238 + reserved_239 = 0xef, // 239 + +// Invalid opcode if executed, but used for special token prefix if at +// position 0 in scriptPubKey. See: primitives/token.h + special_token_prefix = reserved_239, // 0xef 239 + + reserved_240 = 0xf0, // 240 + reserved_241 = 0xf1, // 241 + reserved_242 = 0xf2, // 242 + reserved_243 = 0xf3, // 243 + reserved_244 = 0xf4, // 244 + reserved_245 = 0xf5, // 245 + reserved_246 = 0xf6, // 246 + reserved_247 = 0xf7, // 247 + reserved_248 = 0xf8, // 248 + reserved_249 = 0xf9, // 249 + reserved_250 = 0xfa, // 250 + reserved_251 = 0xfb, // 251 + reserved_252 = 0xfc, // 252 + reserved_253 = 0xfd, // 253 + reserved_254 = 0xfe, // 254 + reserved_255 = 0xff, // 255 + + invalidopcode = reserved_255, // 0xff 255 < Not a real OPCODE! }; /// Convert the opcode to a mnemonic string. diff --git a/include/kth/domain/machine/operation.hpp b/include/kth/domain/machine/operation.hpp index cb95a1e6d..a88d8ab72 100644 --- a/include/kth/domain/machine/operation.hpp +++ b/include/kth/domain/machine/operation.hpp @@ -30,7 +30,8 @@ namespace kth::domain::machine { //TODO(fernando): static? constexpr -auto invalid_code = opcode::disabled_xor; +// auto invalid_code = opcode::disabled_xor; +auto invalid_code = opcode::invalidopcode; class KD_API operation { public: @@ -179,7 +180,7 @@ class KD_API operation { bool is_reserved(opcode code); static - bool is_disabled(opcode code); + bool is_disabled(opcode code, uint32_t active_forks); static bool is_conditional(opcode code); @@ -201,7 +202,7 @@ class KD_API operation { bool is_positive() const; [[nodiscard]] - bool is_disabled() const; + bool is_disabled(uint32_t active_forks) const; [[nodiscard]] bool is_conditional() const; @@ -210,7 +211,7 @@ class KD_API operation { bool is_relaxed_push() const; [[nodiscard]] - bool is_oversized() const; + bool is_oversized(size_t max_size) const; [[nodiscard]] bool is_minimal_push() const; diff --git a/include/kth/domain/machine/program.hpp b/include/kth/domain/machine/program.hpp index ff003487a..43f951c0e 100644 --- a/include/kth/domain/machine/program.hpp +++ b/include/kth/domain/machine/program.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -28,12 +29,6 @@ class KD_API program { using value_type = data_stack::value_type; using op_iterator = operation::iterator; - //TODO(fernando): check this comment - // Older libstdc++ does not allow erase with const iterator. - // This is a bug that requires we up the minimum compiler version. - // So presently stack_iterator is a non-const iterator. - ////using stack_iterator = data_stack::const_iterator; - // using stack_iterator = data_stack::iterator; using stack_iterator = data_stack::const_iterator; using stack_mutable_iterator = data_stack::iterator; @@ -59,6 +54,9 @@ class KD_API program { /// Create using copied tx, input, forks, value and moved stack (p2sh run). program(chain::script const& script, program&& x, bool move); + metrics& get_metrics(); + metrics const& get_metrics() const; + /// Constant registers. [[nodiscard]] bool is_valid() const; @@ -66,6 +64,15 @@ class KD_API program { [[nodiscard]] uint32_t forks() const; + [[nodiscard]] + size_t max_script_element_size() const; + + [[nodiscard]] + size_t max_integer_size_legacy() const; + + [[nodiscard]] + bool is_chip_vm_limits_enabled() const; + [[nodiscard]] uint32_t input_index() const; @@ -109,7 +116,8 @@ class KD_API program { /// Primary pop. data_chunk pop(); bool pop(int32_t& out_value); - bool pop(number& out_number, size_t maxiumum_size = max_number_size); + bool pop(int64_t& out_value); + bool pop(number& out_number, size_t maximum_size); bool pop_binary(number& first, number& second); bool pop_ternary(number& first, number& second, number& third); bool pop_position(stack_iterator& out_position); @@ -145,19 +153,27 @@ class KD_API program { value_type& item(size_t index); - bool top(number& out_number, size_t maxiumum_size = max_number_size) const; + data_chunk const& top() const; + data_chunk& top(); + bool top(number& out_number, size_t maximum_size) const; [[nodiscard]] stack_iterator position(size_t index) const; stack_mutable_iterator position(size_t index); + [[nodiscard]] + size_t index(stack_iterator const& position) const; + [[nodiscard]] operation::list subscript() const; [[nodiscard]] size_t size() const; + [[nodiscard]] + size_t conditional_stack_size() const; + // Alternate stack. //------------------------------------------------------------------------- @@ -202,6 +218,8 @@ class KD_API program { data_stack primary_; data_stack alternate_; bool_stack condition_; + + metrics metrics_; }; } // namespace kth::domain::machine diff --git a/include/kth/domain/machine/script_limits.hpp b/include/kth/domain/machine/script_limits.hpp new file mode 100644 index 000000000..803466fc2 --- /dev/null +++ b/include/kth/domain/machine/script_limits.hpp @@ -0,0 +1,93 @@ +// Copyright (c) 2016-2024 Knuth Project developers. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef KTH_DOMAIN_MACHINE_SCRIPT_LIMITS_HPP +#define KTH_DOMAIN_MACHINE_SCRIPT_LIMITS_HPP + +#include +#include + +#include + +namespace kth::domain::machine { + +namespace may2025 { + +// Some constants used by helper code. +namespace detail { + +/// Bonus factor (7x) applied to hash iteration limits for non-standard transactions (block transactions). +constexpr unsigned int hash_iter_bonus_nonstd = 7u; + +/// Multiplier for determining the total operational cost allowance per input byte. +constexpr unsigned int op_cost_per_input_byte = 800u; + +/// Penalty factor for standard transactions, where hash operations cost 3x more. +constexpr unsigned int hash_cost_penalty_std = 3u; + +/// Block size used by all supported hash operations (e.g., OP_HASH160, OP_HASH256). +constexpr unsigned int hash_block_size = 64u; + +/// Fixed serialization overhead in bytes credited to each input script, as specified in the VM Limits CHIP. +constexpr unsigned int input_script_fixed_credit = 41u; + +/// Calculates the maximum hash iteration limit for a given input. +/// The limit depends on whether standard rules are applied and the scriptSig size. +inline constexpr +uint64_t calculate_input_hash_iters_limit(bool standard, uint64_t script_sig_size) noexcept { + auto const factor = standard ? 1u : detail::hash_iter_bonus_nonstd; + uint64_t const result = ((script_sig_size + detail::input_script_fixed_credit) * factor) / 2u; + assert(result >= 0); + return result; +} + +/// Calculates the maximum operational cost for an input based on its scriptSig size. +inline constexpr +uint64_t calculate_input_op_cost_limit(uint64_t script_sig_size) noexcept { + uint64_t const result = (script_sig_size + detail::input_script_fixed_credit) * detail::op_cost_per_input_byte; + assert(result >= 0); + return result; +} + +} // namespace detail + +/// Determines the cost factor for hash iterations based on whether standard rules apply. +/// Returns 64 for non-standard transactions, or 192 for standard transactions. +// See: https://github.com/bitjson/bch-vm-limits/tree/master?tab=readme-ov-file#summary +inline constexpr +uint64_t hash_iter_op_cost_factor(bool standard) noexcept { + return standard ? + detail::hash_block_size * detail::hash_cost_penalty_std : + detail::hash_block_size; +} + +/// Calculates the number of hash iterations for a given message length and hash operation type. +/// Supports one-round and two-round hash operations. +inline constexpr +uint64_t calculate_hash_iters(uint32_t message_length, bool is_two_round_hash) noexcept { + return is_two_round_hash + 1u + ((uint64_t(message_length) + 8u) / detail::hash_block_size); +} + +/// Defines the execution limits for the virtual machine (VM) when running a specific script. +/// These limits are calculated based on the scriptSig size and whether the script is executed +/// in standard or non-standard mode. The class provides a structured way to enforce these limits +/// during script evaluation. +struct KD_API script_limits { + script_limits(bool standard, uint64_t script_sig_size) + : op_cost_limit_{detail::calculate_input_op_cost_limit(script_sig_size)} + , hash_iters_limit_{detail::calculate_input_hash_iters_limit(standard, script_sig_size)} + {} + + uint64_t op_cost_limit() const { return op_cost_limit_; } + uint64_t hash_iters_limit() const { return hash_iters_limit_; } + +private: + uint64_t op_cost_limit_; + uint64_t hash_iters_limit_; +}; + +} // namespace may2025 +} // namespace kth::domain::machine + +#endif // KTH_DOMAIN_MACHINE_SCRIPT_LIMITS_HPP diff --git a/src/chain/script.cpp b/src/chain/script.cpp index 2a17ea9f8..088ca90b4 100644 --- a/src/chain/script.cpp +++ b/src/chain/script.cpp @@ -305,13 +305,13 @@ operation::list const& script::operations() const { //----------------------------------------------------------------------------- inline -hash_digest signature_hash(transaction const& tx, uint32_t sighash_type) { +std::pair signature_hash(transaction const& tx, uint32_t sighash_type) { // There is no rational interpretation of a signature hash for a coinbase. KTH_ASSERT( ! tx.is_coinbase()); auto serialized = tx.to_data(true, false); extend_data(serialized, to_little_endian(sighash_type)); - return bitcoin_hash(serialized); + return {bitcoin_hash(serialized), serialized.size()}; } //***************************************************************************** @@ -339,7 +339,7 @@ uint8_t is_sighash_enum(uint8_t sighash_type, sighash_algorithm value) { } static -hash_digest sign_none(transaction const& tx, uint32_t input_index, script const& script_code, uint8_t sighash_type) { +std::pair sign_none(transaction const& tx, uint32_t input_index, script const& script_code, uint8_t sighash_type) { input::list ins; auto const& inputs = tx.inputs(); auto const any = (sighash_type & sighash_algorithm::anyone_can_pay) != 0; @@ -367,7 +367,7 @@ hash_digest sign_none(transaction const& tx, uint32_t input_index, script const& } static -hash_digest sign_single(transaction const& tx, uint32_t input_index, script const& script_code, uint8_t sighash_type) { +std::pair sign_single(transaction const& tx, uint32_t input_index, script const& script_code, uint8_t sighash_type) { input::list ins; auto const& inputs = tx.inputs(); auto const any = (sighash_type & sighash_algorithm::anyone_can_pay) != 0; @@ -404,7 +404,7 @@ hash_digest sign_single(transaction const& tx, uint32_t input_index, script cons } static -hash_digest sign_all(transaction const& tx, uint32_t input_index, script const& script_code, uint8_t sighash_type) { +std::pair sign_all(transaction const& tx, uint32_t input_index, script const& script_code, uint8_t sighash_type) { input::list ins; auto const& inputs = tx.inputs(); auto const any = (sighash_type & sighash_algorithm::anyone_can_pay) != 0; @@ -448,7 +448,7 @@ script strip_code_seperators(script const& script_code) { } // private/static -hash_digest script::generate_unversioned_signature_hash(transaction const& tx, +std::pair script::generate_unversioned_signature_hash(transaction const& tx, uint32_t input_index, script const& script_code, uint8_t sighash_type) { @@ -458,7 +458,7 @@ hash_digest script::generate_unversioned_signature_hash(transaction const& tx, //********************************************************************* // CONSENSUS: wacky satoshi behavior. //********************************************************************* - return one_hash; + return {one_hash, 0}; // zero hashed bytes } //************************************************************************* @@ -553,7 +553,7 @@ size_t preimage_size(size_t script_size) { } // private/static -hash_digest script::generate_version_0_signature_hash(transaction const& tx, +std::pair script::generate_version_0_signature_hash(transaction const& tx, uint32_t input_index, script const& script_code, uint64_t value, @@ -621,14 +621,14 @@ hash_digest script::generate_version_0_signature_hash(transaction const& tx, ostream.flush(); KTH_ASSERT(data.size() == size); - return bitcoin_hash(data); + return {bitcoin_hash(data), data.size()}; } // Signing (common). //----------------------------------------------------------------------------- // static -hash_digest script::generate_signature_hash(transaction const& tx, +std::pair script::generate_signature_hash(transaction const& tx, uint32_t input_index, script const& script_code, uint8_t sighash_type, @@ -648,7 +648,7 @@ hash_digest script::generate_signature_hash(transaction const& tx, } // static -bool script::check_signature(ec_signature const& signature, +std::pair script::check_signature(ec_signature const& signature, uint8_t sighash_type, data_chunk const& public_key, script const& script_code, @@ -657,15 +657,19 @@ bool script::check_signature(ec_signature const& signature, script_version version, uint64_t value) { if (public_key.empty()) { - return false; + return {false, 0}; } // This always produces a valid signature hash, including one_hash. - auto const sighash = chain::script::generate_signature_hash(tx, - input_index, script_code, sighash_type, version, value); + auto const [sighash, size] = chain::script::generate_signature_hash(tx, + input_index, + script_code, + sighash_type, + version, + value); // Validate the EC signature. - return verify_signature(public_key, sighash, signature); + return {verify_signature(public_key, sighash, signature), size}; } // static @@ -673,7 +677,7 @@ bool script::create_endorsement(endorsement& out, ec_secret const& secret, scrip out.reserve(max_endorsement_size); // This always produces a valid signature hash, including one_hash. - auto const sighash = chain::script::generate_signature_hash(tx, input_index, prevout_script, sighash_type, version, value); + auto const [sighash, size] = chain::script::generate_signature_hash(tx, input_index, prevout_script, sighash_type, version, value); // Create the EC signature and encode as DER. ec_signature signature; @@ -1098,104 +1102,61 @@ bool script::is_unspendable() const { // Validation. //----------------------------------------------------------------------------- -// #if ! defined(KTH_SEGWIT_ENABLED) -// code script::verify(transaction const& tx, uint32_t input_index, uint32_t forks, script const& input_script, script const& prevout_script, uint64_t /*value*/) { -// #else -// code script::verify(transaction const& tx, uint32_t input_index, uint32_t forks, script const& input_script, witness const& input_witness, script const& prevout_script, uint64_t value) { -// #endif -// code ec; - -// // Evaluate input script. -// program input(input_script, tx, input_index, forks); -// if ((ec = input.evaluate())) { -// return ec; -// } +code script::verify(transaction const& tx, uint32_t input_index, uint32_t forks, script const& input_script, script const& prevout_script, uint64_t /*value*/) { + code ec; -// // Evaluate output script using stack result from input script. -// program prevout(prevout_script, input); -// if ((ec = prevout.evaluate())) { -// return ec; -// } + // Evaluate input script. + program input(input_script, tx, input_index, forks); + if ((ec = input.evaluate())) { + return ec; + } -// // This precludes bare witness programs of -0 (undocumented). -// if ( ! prevout.stack_result(false)) { -// return error::stack_false; -// } + // Evaluate output script using stack result from input script. + program prevout(prevout_script, input); + if ((ec = prevout.evaluate())) { + return ec; + } -// #if defined(KTH_SEGWIT_ENABLED) -// bool witnessed; -// // Triggered by output script push of version and witness program (bip141). -// if ((witnessed = prevout_script.is_pay_to_witness(forks))) { -// // The input script must be empty (bip141). -// if ( ! input_script.empty()) { -// return error::dirty_witness; -// } - -// // This is a valid witness script so validate it. -// if ((ec = input_witness.verify(tx, input_index, forks, prevout_script, value))) { -// return ec; -// } -// } else -// #endif -// // p2sh and p2w are mutually exclusive. -// /*else*/ -// if (prevout_script.is_pay_to_script_hash(forks)) { -// if ( ! is_relaxed_push(input_script.operations())) { -// return error::invalid_script_embed; -// } - -// // Embedded script must be at the top of the stack (bip16). -// script embedded_script(input.pop(), false); - -// program embedded(embedded_script, std::move(input), true); -// if ((ec = embedded.evaluate())) { -// return ec; -// } - -// // This precludes embedded witness programs of -0 (undocumented). -// if ( ! embedded.stack_result(false)) { -// return error::stack_false; -// } - -// #if defined(KTH_SEGWIT_ENABLED) -// // Triggered by embedded push of version and witness program (bip141). -// if ((witnessed = embedded_script.is_pay_to_witness(forks))) { -// // The input script must be a push of the embedded_script (bip141). -// if (input_script.size() != 1) { -// return error::dirty_witness; -// } - -// // This is a valid embedded witness script so validate it. -// if ((ec = input_witness.verify(tx, input_index, forks, embedded_script, value))) { -// return ec; -// } -// } -// #endif -// } + // This precludes bare witness programs of -0 (undocumented). + if ( ! prevout.stack_result(false)) { + return error::stack_false; + } -// #if defined(KTH_SEGWIT_ENABLED) -// // Witness must be empty if no bip141 or valid witness program (bip141). -// if ( ! witnessed && !input_witness.empty()) { -// return error::unexpected_witness; -// } -// #endif + if (prevout_script.is_pay_to_script_hash(forks)) { + if ( ! is_relaxed_push(input_script.operations())) { + return error::invalid_script_embed; + } -// return error::success; -// } + // Embedded script must be at the top of the stack (bip16). + script embedded_script(input.pop(), false); -// code script::verify(transaction const& tx, uint32_t input, uint32_t forks) { -// if (input >= tx.inputs().size()) { -// return error::operation_failed; -// } + program embedded(embedded_script, std::move(input), true); + if ((ec = embedded.evaluate())) { + return ec; + } -// auto const& in = tx.inputs()[input]; -// auto const& prevout = in.previous_output().validation.cache; + // This precludes embedded witness programs of -0 (undocumented). + if ( ! embedded.stack_result(false)) { + return error::stack_false; + } + } -// #if ! defined(KTH_SEGWIT_ENABLED) -// return verify(tx, input, forks, in.script(), prevout.script(), prevout.value()); -// #else -// return verify(tx, input, forks, in.script(), in.witness(), prevout.script(), prevout.value()); -// #endif -// } + return error::success; +} + +code script::verify(transaction const& tx, uint32_t input, uint32_t forks) { + if (input >= tx.inputs().size()) { + return error::operation_failed; + } + + auto const& in = tx.inputs()[input]; + auto const& prevout = in.previous_output().validation.cache; + +#if ! defined(KTH_SEGWIT_ENABLED) + return verify(tx, input, forks, in.script(), prevout.script(), prevout.value()); +#else + return verify(tx, input, forks, in.script(), in.witness(), prevout.script(), prevout.value()); +#endif +} } // namespace kth::domain::chain diff --git a/src/chain/script_basis.cpp b/src/chain/script_basis.cpp index 25f25c500..e4339b6bb 100644 --- a/src/chain/script_basis.cpp +++ b/src/chain/script_basis.cpp @@ -243,7 +243,7 @@ size_t preimage_size(size_t script_size) { } // private/static -hash_digest script_basis::generate_version_0_signature_hash(transaction const& tx, +std::pair script_basis::generate_version_0_signature_hash(transaction const& tx, uint32_t input_index, script_basis const& script_code, uint64_t value, @@ -310,7 +310,7 @@ hash_digest script_basis::generate_version_0_signature_hash(transaction const& t ostream.flush(); KTH_ASSERT(data.size() == size); - return bitcoin_hash(data); + return {bitcoin_hash(data), data.size()}; } // Utilities (static). diff --git a/src/machine/interpreter.cpp b/src/machine/interpreter.cpp index fdec83722..0641b8256 100644 --- a/src/machine/interpreter.cpp +++ b/src/machine/interpreter.cpp @@ -18,12 +18,30 @@ code interpreter::run(program& program) { return error::invalid_script; } + // BCHN code: + // bool const chipVmLimitsEnabled = (flags & SCRIPT_ENABLE_MAY2025) != 0; + // size_t const maxScriptElementSize = chipVmLimitsEnabled ? may2025::MAX_SCRIPT_ELEMENT_SIZE + // : MAX_SCRIPT_ELEMENT_SIZE_LEGACY; + + // if (chipVmLimitsEnabled && !metrics.HasValidScriptLimits() && context) { + // // Calculate metrics "scriptLimits", if not already passed-in, and if we have a `context` object + // // from which to get the scriptSig size. + // metrics.SetScriptLimits(flags, context->scriptSig().size()); + // } + + // TODO: flags vs forks + // if (program.is_chip_vm_limits_enabled() && !program.get_metrics().has_valid_script_limits() && context) { + // // Calculate metrics "scriptLimits", if not already passed-in, and if we have a `context` object + // // from which to get the scriptSig size. + // program.get_metrics().set_script_limits(program.get_flags(), context->scriptSig().size()); + // } + for (auto const& op : program) { - if (op.is_oversized()) { + if (op.is_oversized(program.max_script_element_size())) { return error::invalid_push_data_size; } - if (op.is_disabled()) { + if (op.is_disabled(program.forks())) { return error::op_disabled; } @@ -39,6 +57,40 @@ code interpreter::run(program& program) { if (program.is_stack_overflow()) { return error::invalid_stack_size; } + + // Enforce May 2025 VM limits + if (program.is_chip_vm_limits_enabled()) { + // // Check that this opcode did not cause us to exceed opCost and/or hashIters limits. + // // Note: `metrics` may lack a valid "scriptLimits" object in rare cases (tests only), in which case + // // the below two limit checks are always going to return false. + + // if (metrics.IsOverOpCostLimit(flags)) { + // return set_error(serror, ScriptError::OP_COST); + // } + // if (metrics.IsOverHashItersLimit()) { + // return set_error(serror, ScriptError::TOO_MANY_HASH_ITERS); + // } + + // // Conditional stack may not exceed depth of 100 + // if (vfExec.size() > may2025::MAX_CONDITIONAL_STACK_DEPTH) { + // return set_error(serror, ScriptError::CONDITIONAL_STACK_DEPTH); + // } + + + // TODO: flags vs forks + // if (program.get_metrics().is_over_op_cost_limit(program.get_flags())) { + // return error::op_cost; + // } + + if (program.get_metrics().is_over_hash_iters_limit()) { + return error::too_many_hash_iters; + } + + // Conditional stack may not exceed depth of 100 + if (program.conditional_stack_size() > ::kth::may2025::max_conditional_stack_depth) { + return error::conditional_stack_depth; + } + } } } diff --git a/src/machine/opcode.cpp b/src/machine/opcode.cpp index 757602579..f49176bbc 100644 --- a/src/machine/opcode.cpp +++ b/src/machine/opcode.cpp @@ -190,23 +190,23 @@ std::string opcode_to_string(opcode value, uint32_t active_forks) { return "swap"; case opcode::tuck: return "tuck"; - case opcode::disabled_cat: + case opcode::cat: return "cat"; - case opcode::disabled_substr: - return "substr"; - case opcode::disabled_left: - return "left"; - case opcode::disabled_right: - return "right"; + case opcode::split: + return "split"; + case opcode::num2bin: + return "num2bin"; + case opcode::bin2num: + return "bin2num"; case opcode::size: return "size"; case opcode::disabled_invert: return "invert"; - case opcode::disabled_and: + case opcode::and_: return "and"; - case opcode::disabled_or: + case opcode::or_: return "or"; - case opcode::disabled_xor: + case opcode::xor_: return "xor"; case opcode::equal: return "equal"; @@ -236,11 +236,11 @@ std::string opcode_to_string(opcode value, uint32_t active_forks) { return "add"; case opcode::sub: return "sub"; - case opcode::disabled_mul: + case opcode::mul: return "mul"; - case opcode::disabled_div: + case opcode::div: return "div"; - case opcode::disabled_mod: + case opcode::mod: return "mod"; case opcode::disabled_lshift: return "lshift"; @@ -315,34 +315,61 @@ std::string opcode_to_string(opcode value, uint32_t active_forks) { case opcode::nop10: return "nop10"; - //TODO(kth): Implement OP_CHECKDATASIG and OP_CHECKDATASIGVERIFY - - case opcode::reserved_186: - case opcode::reserved_187: - case opcode::reserved_188: - case opcode::reserved_189: - case opcode::reserved_190: - case opcode::reserved_191: - case opcode::reserved_192: - case opcode::reserved_193: - case opcode::reserved_194: - case opcode::reserved_195: - case opcode::reserved_196: - case opcode::reserved_197: - case opcode::reserved_198: - case opcode::reserved_199: - case opcode::reserved_200: - case opcode::reserved_201: - case opcode::reserved_202: - case opcode::reserved_203: - case opcode::reserved_204: - case opcode::reserved_205: - case opcode::reserved_206: - case opcode::reserved_207: - case opcode::reserved_208: - case opcode::reserved_209: - case opcode::reserved_210: - case opcode::reserved_211: +// more crypto + case opcode::checkdatasig: + return "checkdatasig"; + case opcode::checkdatasigverify: + return "checkdatasigverify"; + +// additional byte string operations + case opcode::reverse_bytes: + return "reverse_bytes"; + + +// Native Introspection opcodes + case opcode::input_index: + return "input_index"; + case opcode::active_bytecode: + return "active_bytecode"; + case opcode::tx_version: + return "tx_version"; + case opcode::tx_input_count: + return "tx_input_count"; + case opcode::tx_output_count: + return "tx_output_count"; + case opcode::tx_locktime: + return "tx_locktime"; + case opcode::utxo_value: + return "utxo_value"; + case opcode::utxo_bytecode: + return "utxo_bytecode"; + case opcode::outpoint_tx_hash: + return "outpoint_tx_hash"; + case opcode::outpoint_index: + return "outpoint_index"; + case opcode::input_bytecode: + return "input_bytecode"; + case opcode::input_sequence_number: + return "input_sequence_number"; + case opcode::output_value: + return "output_value"; + case opcode::output_bytecode: + return "output_bytecode"; + +// Native Introspection of tokens (SCRIPT_ENABLE_TOKENS must be set) + case opcode::utxo_token_category: + return "utxo_token_category"; + case opcode::utxo_token_commitment: + return "utxo_token_commitment"; + case opcode::utxo_token_amount: + return "utxo_token_amount"; + case opcode::output_token_category: + return "output_token_category"; + case opcode::output_token_commitment: + return "output_token_commitment"; + case opcode::output_token_amount: + return "output_token_amount"; + case opcode::reserved_212: case opcode::reserved_213: case opcode::reserved_214: @@ -525,19 +552,22 @@ bool opcode_from_string(opcode& out_code, std::string const& value) { //NO RETURN_IF_OPCODE("rot", rot); RETURN_IF_OPCODE("swap", swap); RETURN_IF_OPCODE("tuck", tuck); - RETURN_IF_OPCODE("cat", disabled_cat); - RETURN_IF_OPCODE("substr", disabled_substr); - RETURN_IF_OPCODE("left", disabled_left); - RETURN_IF_OPCODE("right", disabled_right); + + RETURN_IF_OPCODE("cat", cat); + RETURN_IF_OPCODE("split", split); // was called substr before (disabled and re-enabled after pythagoras/monolith upgrade, May 2018) + RETURN_IF_OPCODE("num2bin", num2bin); // was called left before (disabled and re-enabled after pythagoras/monolith upgrade, May 2018) + RETURN_IF_OPCODE("bin2num", bin2num); // was called right before (disabled and re-enabled after pythagoras/monolith upgrade, May 2018) RETURN_IF_OPCODE("size", size); + RETURN_IF_OPCODE("invert", disabled_invert); - RETURN_IF_OPCODE("and", disabled_and); - RETURN_IF_OPCODE("or", disabled_or); - RETURN_IF_OPCODE("xor", disabled_xor); + RETURN_IF_OPCODE("and", and_); // disabled and re-enabled after pythagoras/monolith upgrade, May 2018 + RETURN_IF_OPCODE("or", or_); // disabled and re-enabled after pythagoras/monolith upgrade, May 2018 + RETURN_IF_OPCODE("xor", xor_); // disabled and re-enabled after pythagoras/monolith upgrade, May 2018 RETURN_IF_OPCODE("equal", equal); RETURN_IF_OPCODE("equalverify", equalverify); RETURN_IF_OPCODE_OR_ALIAS("reserved_137", "reserved1", reserved_137); RETURN_IF_OPCODE_OR_ALIAS("reserved_138", "reserved2", reserved_138); + RETURN_IF_OPCODE_OR_ALIAS("add1", "1add", add1); RETURN_IF_OPCODE_OR_ALIAS("sub1", "1sub", sub1); RETURN_IF_OPCODE_OR_ALIAS("mul2", "2mul", disabled_mul2); @@ -546,13 +576,15 @@ bool opcode_from_string(opcode& out_code, std::string const& value) { //NO RETURN_IF_OPCODE("abs", abs); RETURN_IF_OPCODE("not", not_); RETURN_IF_OPCODE_OR_ALIAS("nonzero", "0notequal", nonzero); + RETURN_IF_OPCODE("add", add); RETURN_IF_OPCODE("sub", sub); - RETURN_IF_OPCODE("mul", disabled_mul); - RETURN_IF_OPCODE("div", disabled_div); - RETURN_IF_OPCODE("mod", disabled_mod); + RETURN_IF_OPCODE("mul", mul); + RETURN_IF_OPCODE("div", div); + RETURN_IF_OPCODE("mod", mod); RETURN_IF_OPCODE("lshift", disabled_lshift); RETURN_IF_OPCODE("rshift", disabled_rshift); + RETURN_IF_OPCODE("booland", booland); RETURN_IF_OPCODE("boolor", boolor); RETURN_IF_OPCODE("numequal", numequal); @@ -564,17 +596,21 @@ bool opcode_from_string(opcode& out_code, std::string const& value) { //NO RETURN_IF_OPCODE("greaterthanorequal", greaterthanorequal); RETURN_IF_OPCODE("min", min); RETURN_IF_OPCODE("max", max); + RETURN_IF_OPCODE("within", within); + RETURN_IF_OPCODE("ripemd160", ripemd160); RETURN_IF_OPCODE("sha1", sha1); RETURN_IF_OPCODE("sha256", sha256); RETURN_IF_OPCODE("hash160", hash160); RETURN_IF_OPCODE("hash256", hash256); + RETURN_IF_OPCODE("codeseparator", codeseparator); RETURN_IF_OPCODE("checksig", checksig); RETURN_IF_OPCODE("checksigverify", checksigverify); RETURN_IF_OPCODE("checkmultisig", checkmultisig); RETURN_IF_OPCODE("checkmultisigverify", checkmultisigverify); + RETURN_IF_OPCODE("nop1", nop1); RETURN_IF_OPCODE_OR_ALIAS("checklocktimeverify", "nop2", checklocktimeverify); RETURN_IF_OPCODE_OR_ALIAS("checksequenceverify", "nop3", checksequenceverify); @@ -585,32 +621,38 @@ bool opcode_from_string(opcode& out_code, std::string const& value) { //NO RETURN_IF_OPCODE("nop8", nop8); RETURN_IF_OPCODE("nop9", nop9); RETURN_IF_OPCODE("nop10", nop10); - RETURN_IF_OPCODE("reserved_186", reserved_186); - RETURN_IF_OPCODE("reserved_187", reserved_187); - RETURN_IF_OPCODE("reserved_188", reserved_188); - RETURN_IF_OPCODE("reserved_189", reserved_189); - RETURN_IF_OPCODE("reserved_190", reserved_190); - RETURN_IF_OPCODE("reserved_191", reserved_191); - RETURN_IF_OPCODE("reserved_192", reserved_192); - RETURN_IF_OPCODE("reserved_193", reserved_193); - RETURN_IF_OPCODE("reserved_194", reserved_194); - RETURN_IF_OPCODE("reserved_195", reserved_195); - RETURN_IF_OPCODE("reserved_196", reserved_196); - RETURN_IF_OPCODE("reserved_197", reserved_197); - RETURN_IF_OPCODE("reserved_198", reserved_198); - RETURN_IF_OPCODE("reserved_199", reserved_199); - RETURN_IF_OPCODE("reserved_200", reserved_200); - RETURN_IF_OPCODE("reserved_201", reserved_201); - RETURN_IF_OPCODE("reserved_202", reserved_202); - RETURN_IF_OPCODE("reserved_203", reserved_203); - RETURN_IF_OPCODE("reserved_204", reserved_204); - RETURN_IF_OPCODE("reserved_205", reserved_205); - RETURN_IF_OPCODE("reserved_206", reserved_206); - RETURN_IF_OPCODE("reserved_207", reserved_207); - RETURN_IF_OPCODE("reserved_208", reserved_208); - RETURN_IF_OPCODE("reserved_209", reserved_209); - RETURN_IF_OPCODE("reserved_210", reserved_210); - RETURN_IF_OPCODE("reserved_211", reserved_211); + +// more crypto + RETURN_IF_OPCODE("checkdatasig", checkdatasig); + RETURN_IF_OPCODE("checkdatasigverify", checkdatasigverify); + +// additional byte string operations + RETURN_IF_OPCODE("reverse_bytes", reverse_bytes); + +// Native Introspection opcodes + RETURN_IF_OPCODE("input_index", input_index); + RETURN_IF_OPCODE("active_bytecode", active_bytecode); + RETURN_IF_OPCODE("tx_version", tx_version); + RETURN_IF_OPCODE("tx_input_count", tx_input_count); + RETURN_IF_OPCODE("tx_output_count", tx_output_count); + RETURN_IF_OPCODE("tx_locktime", tx_locktime); + RETURN_IF_OPCODE("utxo_value", utxo_value); + RETURN_IF_OPCODE("utxo_bytecode", utxo_bytecode); + RETURN_IF_OPCODE("outpoint_tx_hash", outpoint_tx_hash); + RETURN_IF_OPCODE("outpoint_index", outpoint_index); + RETURN_IF_OPCODE("input_bytecode", input_bytecode); + RETURN_IF_OPCODE("input_sequence_number", input_sequence_number); + RETURN_IF_OPCODE("output_value", output_value); + RETURN_IF_OPCODE("output_bytecode", output_bytecode); + +// Native Introspection of tokens (SCRIPT_ENABLE_TOKENS must be set) + RETURN_IF_OPCODE("utxo_token_category", utxo_token_category); + RETURN_IF_OPCODE("utxo_token_commitment", utxo_token_commitment); + RETURN_IF_OPCODE("utxo_token_amount", utxo_token_amount); + RETURN_IF_OPCODE("output_token_category", output_token_category); + RETURN_IF_OPCODE("output_token_commitment", output_token_commitment); + RETURN_IF_OPCODE("output_token_amount", output_token_amount); + RETURN_IF_OPCODE("reserved_212", reserved_212); RETURN_IF_OPCODE("reserved_213", reserved_213); RETURN_IF_OPCODE("reserved_214", reserved_214); diff --git a/test/machine/operation.cpp b/test/machine/operation.cpp index 1aa72ec93..3b21668a8 100644 --- a/test/machine/operation.cpp +++ b/test/machine/operation.cpp @@ -17,7 +17,8 @@ TEST_CASE("operation constructor 1 always returns default initialized", "[ope REQUIRE( ! instance.is_valid()); REQUIRE(instance.data().empty()); - REQUIRE(instance.code() == opcode::disabled_xor); + // REQUIRE(instance.code() == opcode::disabled_xor); + REQUIRE(instance.code() == opcode::invalidopcode); } TEST_CASE("operation constructor 2 valid input returns input initialized", "[operation]") {