diff --git a/src/pubkey.cpp b/src/pubkey.cpp index a4ca9a170a9..f33a5c2d4a2 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -273,6 +273,40 @@ std::optional> XOnlyPubKey::CreateTapTweak(const ui return ret; } +bool XOnlyPubKey::CheckDoubleTweak(const XOnlyPubKey& naked_key, const std::vector& data, const uint256* merkle_root) const +{ + int parity; + secp256k1_xonly_pubkey internal_xonly; + if (data.empty()) { + // No data tweak; the internal key is the naked key + if (!secp256k1_xonly_pubkey_parse(secp256k1_context_static, &internal_xonly, naked_key.data())) return false; + } else { + // Compute the sha256 of naked_key || data + uint256 data_tweak = (HashWriter{} << naked_key << MakeUCharSpan(data)).GetSHA256(); + secp256k1_xonly_pubkey naked_key_parsed; + if (!secp256k1_xonly_pubkey_parse(secp256k1_context_static, &naked_key_parsed, naked_key.data())) return false; + secp256k1_pubkey internal; + if (!secp256k1_xonly_pubkey_tweak_add(secp256k1_context_static, &internal, &naked_key_parsed, data_tweak.data())) return false; + if (!secp256k1_xonly_pubkey_from_pubkey(secp256k1_context_static, &internal_xonly, &parity, &internal)) return false; + } + secp256k1_xonly_pubkey expected_xonly; + if (!secp256k1_xonly_pubkey_parse(secp256k1_context_static, &expected_xonly, m_keydata.data())) return false; + if (merkle_root != nullptr) { + // Compute the taptweak based on merkle_root + unsigned char pubkey_bytes[32]; + secp256k1_xonly_pubkey_serialize(secp256k1_context_static, pubkey_bytes, &internal_xonly); + XOnlyPubKey internal_key = XOnlyPubKey(pubkey_bytes); + uint256 tweak = internal_key.ComputeTapTweakHash(merkle_root); + secp256k1_pubkey result; + if (!secp256k1_xonly_pubkey_tweak_add(secp256k1_context_static, &result, &internal_xonly, tweak.begin())) return false; + secp256k1_xonly_pubkey result_xonly; + if (!secp256k1_xonly_pubkey_from_pubkey(secp256k1_context_static, &result_xonly, &parity, &result)) return false; + return secp256k1_xonly_pubkey_cmp(secp256k1_context_static, &result_xonly, &expected_xonly) == 0; + } else { + // If merkle_root is nullptr, compare internal_xonly with expected_xonly + return secp256k1_xonly_pubkey_cmp(secp256k1_context_static, &internal_xonly, &expected_xonly) == 0; + } +} bool CPubKey::Verify(const uint256 &hash, const std::vector& vchSig) const { if (!IsValid()) diff --git a/src/pubkey.h b/src/pubkey.h index cbc827dc606..384b611a073 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -282,6 +282,12 @@ public: /** Construct a Taproot tweaked output point with this point as internal key. */ std::optional> CreateTapTweak(const uint256* merkle_root) const; + /** Verify that this key is obtained from the x-only pubkey `naked` after applying in sequence: + * - the tweak with `data` (this tweak is skipped if `data` is empty); + * - the taptweak with `merkle_root` (unless it's null). + */ + bool CheckDoubleTweak(const XOnlyPubKey& naked_key, const std::vector& data, const uint256* merkle_root) const; + /** Returns a list of CKeyIDs for the CPubKeys that could have been used to create this XOnlyPubKey. * This is needed for key lookups since keys are indexed by CKeyID. */ diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 2ff63118aff..341973d2e82 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -14,6 +14,15 @@ typedef std::vector valtype; +//! Flag to mark an OP_CHECKCONTRACVERIFY as referring to an input. +const int CCV_MODE_CHECK_INPUT = -1; +//! Flag to specify that an OP_CHECKCONTRACVERIFY which refers to an output, with the default amount logic. +const int CCV_MODE_CHECK_OUTPUT = 0; +//! Flag to specify that an OP_CHECKCONTRACVERIFY which refers to an output does not check the output amount. +const int CCV_MODE_CHECK_OUTPUT_IGNORE_AMOUNT = 1; +//! Flag to specify that an OP_CHECKCONTRACVERIFY referring to an output deducts the amount of its output from the current input amount for future calls. +const int CCV_MODE_CHECK_OUTPUT_DEDUCT_AMOUNT = 2; + namespace { inline bool set_success(ScriptError* ret) @@ -1101,6 +1110,51 @@ bool EvalScript(std::vector >& stack, const CScript& } break; + case OP_CHECKCONTRACTVERIFY: + { + // OP_CHECKCONTRACTVERIFY is only available in Tapscript + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + + // we expect at least the flag to be on the stack + if (stack.empty()) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + // initially, read only a single parameter at the top of stack + int flags = CScriptNum(stacktop(-1), fRequireMinimal).getint(); + if (flags < -1 || flags > CCV_MODE_CHECK_OUTPUT_DEDUCT_AMOUNT) { + // undefined values of the flags; keep OP_SUCCESS behavior + // in order to enable future upgrades via soft-fork + stack = { {1} }; + return set_success(serror); + } + + // all currently defined versions require exactly 5 stack elements + + // (data index pk taptree flags -- ) + if (stack.size() < 5) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + valtype& data = stacktop(-5); + int index = CScriptNum(stacktop(-4), fRequireMinimal).getint(); + valtype& pk = stacktop(-3); + valtype& taptree = stacktop(-2); + + if (!pk.empty() && pk != std::vector{0x81} && pk.size() != 32) { + return set_error(serror, SCRIPT_ERR_CHECKCONTRACTVERIFY_WRONG_ARGS); + } + + if (!checker.CheckContract(flags, index, pk, data, taptree, execdata, serror, tx_exec_data)) { + return false; // serror is set + } + + popstack(stack); + popstack(stack); + popstack(stack); + popstack(stack); + popstack(stack); + } + break; + case OP_CHECKMULTISIG: case OP_CHECKMULTISIGVERIFY: { @@ -1781,6 +1835,116 @@ bool GenericTransactionSignatureChecker::CheckSequence(const CScriptNum& nSeq return true; } +template +bool GenericTransactionSignatureChecker::CheckContract(int mode, int index, const std::vector& pubkey, const std::vector& data, const std::vector& taptree, ScriptExecutionData& ScriptExecutionData, ScriptError* serror, TransactionExecutionData* tx_exec_data) const +{ + assert(ScriptExecutionData.m_internal_key.has_value()); + assert(ScriptExecutionData.m_taproot_merkle_root.has_value()); + assert(tx_exec_data != nullptr); + + if (!(txdata->m_bip341_taproot_ready && txdata->m_spent_outputs_ready)) { + return HandleMissingData(m_mdb); + } + + bool use_current_taptree = taptree.size() == 1 && taptree.data()[0] == 0x81; + bool use_current_pubkey = pubkey.size() == 1 && pubkey.data()[0] == 0x81; + + uint256 merkle_tree; + const uint256 *merkle_tree_ptr = nullptr; + if (taptree.empty()) { + // no taptweak, leave nullptr + } else if (use_current_taptree) { + merkle_tree_ptr = &ScriptExecutionData.m_taproot_merkle_root.value(); + } else if (taptree.size() == 32) { + merkle_tree = uint256(taptree); + merkle_tree_ptr = &merkle_tree; + } else { + return set_error(serror, SCRIPT_ERR_CHECKCONTRACTVERIFY_WRONG_ARGS); + } + + XOnlyPubKey initialXOnlyKey; + if (use_current_pubkey) { + initialXOnlyKey = ScriptExecutionData.m_internal_key.value(); + } else if (pubkey.empty()) { + initialXOnlyKey = XOnlyPubKey::NUMS_H; + } else { + initialXOnlyKey = XOnlyPubKey{std::span{pubkey.data(), pubkey.data() + 32}}; + } + + if (index == -1) { + index = nIn; + } + + auto indexLimit = (mode == CCV_MODE_CHECK_INPUT ? txTo->vin.size() : txTo->vout.size()); + if (index < 0 || index >= static_cast(indexLimit)) { + return set_error(serror, SCRIPT_ERR_CHECKCONTRACTVERIFY_OUT_OF_BOUNDS); + } + + CScript scriptPubKey = (mode == CCV_MODE_CHECK_INPUT) ? txdata->m_spent_outputs[index].scriptPubKey : txTo->vout.at(index).scriptPubKey; + + if (scriptPubKey.size() != 1 + 1 + 32 || scriptPubKey[0] != OP_1 || scriptPubKey[1] != 32) { + return set_error(serror, SCRIPT_ERR_CHECKCONTRACTVERIFY_WRONG_ARGS); + } + + const XOnlyPubKey finalXOnlyKey{std::span{scriptPubKey.data() + 2, scriptPubKey.data() + 34}}; + + if (!finalXOnlyKey.CheckDoubleTweak(initialXOnlyKey, data, merkle_tree_ptr)) { + return set_error(serror, SCRIPT_ERR_CHECKCONTRACTVERIFY_MISMATCH); + } + + + if (!ScriptExecutionData.m_ccv_amount_init) { + ScriptExecutionData.m_ccv_amount = amount; + ScriptExecutionData.m_ccv_amount_init = true; + } + + { + LOCK(tx_exec_data->m_mutex); + if (tx_exec_data->m_error.has_value()) { + // an error related to tx_exec_data already occurred while validating the transaction + return set_error(serror, tx_exec_data->m_error.value()); + } + + switch (mode) { + case CCV_MODE_CHECK_OUTPUT: + if (tx_exec_data->m_ccv_output_checked_deduct[index]) { + tx_exec_data->m_error = SCRIPT_ERR_CHECKCONTRACTVERIFY_WRONG_AMOUNT; + return set_error(serror, SCRIPT_ERR_CHECKCONTRACTVERIFY_WRONG_AMOUNT); + } + tx_exec_data->m_ccv_output_checked_default[index] = true; + + tx_exec_data->m_ccv_output_min_amount[index] += ScriptExecutionData.m_ccv_amount; + ScriptExecutionData.m_ccv_amount = 0; + if (txTo->vout[index].nValue < tx_exec_data->m_ccv_output_min_amount[index]) { + tx_exec_data->m_error = SCRIPT_ERR_CHECKCONTRACTVERIFY_WRONG_AMOUNT; + return set_error(serror, SCRIPT_ERR_CHECKCONTRACTVERIFY_WRONG_AMOUNT); + } + break; + case CCV_MODE_CHECK_OUTPUT_IGNORE_AMOUNT: + // amount checking is disabled + break; + case CCV_MODE_CHECK_OUTPUT_DEDUCT_AMOUNT: + if (tx_exec_data->m_ccv_output_checked_default[index] || tx_exec_data->m_ccv_output_checked_deduct[index]) { + tx_exec_data->m_error = SCRIPT_ERR_CHECKCONTRACTVERIFY_WRONG_AMOUNT; + return set_error(serror, SCRIPT_ERR_CHECKCONTRACTVERIFY_WRONG_AMOUNT); + } + tx_exec_data->m_ccv_output_checked_deduct[index] = true; + // subtract amount from input + if (txTo->vout[index].nValue > ScriptExecutionData.m_ccv_amount) { + tx_exec_data->m_error = SCRIPT_ERR_CHECKCONTRACTVERIFY_WRONG_AMOUNT; + return set_error(serror, SCRIPT_ERR_CHECKCONTRACTVERIFY_WRONG_AMOUNT); + } + ScriptExecutionData.m_ccv_amount -= txTo->vout[index].nValue; + + break; + default: + break; + } + } + + return true; +} + // explicit instantiation template class GenericTransactionSignatureChecker; template class GenericTransactionSignatureChecker; @@ -1789,6 +1953,8 @@ static bool ExecuteWitnessScript(const std::span& stack_span, con { std::vector stack{stack_span.begin(), stack_span.end()}; + const bool is_checkcontractverify_active = (flags & SCRIPT_VERIFY_CHECKCONTRACTVERIFY); + if (sigversion == SigVersion::TAPSCRIPT) { // OP_SUCCESSx processing overrides everything, including stack element size limits CScript::const_iterator pc = exec_script.begin(); @@ -1799,7 +1965,9 @@ static bool ExecuteWitnessScript(const std::span& stack_span, con return set_error(serror, SCRIPT_ERR_BAD_OPCODE); } // New opcodes will be listed here. May use a different sigversion to modify existing opcodes. - if (IsOpSuccess(opcode)) { + if (is_checkcontractverify_active && opcode == OP_CHECKCONTRACTVERIFY) { + continue; + } else if (IsOpSuccess(opcode)) { if (flags & SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS) { return set_error(serror, SCRIPT_ERR_DISCOURAGE_OP_SUCCESS); } @@ -1856,18 +2024,19 @@ uint256 ComputeTaprootMerkleRoot(std::span control, const u return k; } -static bool VerifyTaprootCommitment(const std::vector& control, const std::vector& program, const uint256& tapleaf_hash) +static bool VerifyTaprootCommitment(const std::vector& control, const std::vector& program, const uint256& tapleaf_hash, std::optional& internal_key, std::optional& merkle_root) { assert(control.size() >= TAPROOT_CONTROL_BASE_SIZE); assert(program.size() >= uint256::size()); //! The internal pubkey (x-only, so no Y coordinate parity). const XOnlyPubKey p{std::span{control}.subspan(1, TAPROOT_CONTROL_BASE_SIZE - 1)}; + internal_key = p; //! The output pubkey (taken from the scriptPubKey). const XOnlyPubKey q{program}; // Compute the Merkle root from the leaf and the provided path. - const uint256 merkle_root = ComputeTaprootMerkleRoot(control, tapleaf_hash); + merkle_root = ComputeTaprootMerkleRoot(control, tapleaf_hash); // Verify that the output pubkey matches the tweaked internal pubkey, after correcting for parity. - return q.CheckTapTweak(p, merkle_root, control[0] & 1); + return q.CheckTapTweak(p, merkle_root.value(), control[0] & 1); } static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector& program, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror, bool is_p2sh, TransactionExecutionData* tx_exec_data = nullptr) @@ -1927,7 +2096,7 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, return set_error(serror, SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE); } execdata.m_tapleaf_hash = ComputeTapleafHash(control[0] & TAPROOT_LEAF_MASK, script); - if (!VerifyTaprootCommitment(control, program, execdata.m_tapleaf_hash)) { + if (!VerifyTaprootCommitment(control, program, execdata.m_tapleaf_hash, execdata.m_internal_key, execdata.m_taproot_merkle_root)) { return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH); } execdata.m_tapleaf_hash_init = true; diff --git a/src/script/interpreter.h b/src/script/interpreter.h index 8fe1a415b26..19b32c060cd 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -9,6 +9,7 @@ #include #include #include +#include #include