diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 434f835fbb..a909e86830 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -173,12 +173,12 @@ XOnlyPubKey::XOnlyPubKey(Span bytes) std::copy(bytes.begin(), bytes.end(), m_keydata.begin()); } -bool XOnlyPubKey::VerifySchnorr(const uint256& msg, Span sigbytes) const +bool XOnlyPubKey::VerifySchnorr(const Span msg, Span sigbytes) const { assert(sigbytes.size() == 64); secp256k1_xonly_pubkey pubkey; if (!secp256k1_xonly_pubkey_parse(secp256k1_context_verify, &pubkey, m_keydata.data())) return false; - return secp256k1_schnorrsig_verify(secp256k1_context_verify, sigbytes.data(), msg.begin(), 32, &pubkey); + return secp256k1_schnorrsig_verify(secp256k1_context_verify, sigbytes.data(), msg.data(), msg.size(), &pubkey); } bool XOnlyPubKey::CheckPayToContract(const XOnlyPubKey& base, const uint256& hash, bool parity) const @@ -188,6 +188,18 @@ bool XOnlyPubKey::CheckPayToContract(const XOnlyPubKey& base, const uint256& has return secp256k1_xonly_pubkey_tweak_add_check(secp256k1_context_verify, m_keydata.begin(), parity, &base_point, hash.begin()); } +bool CPubKey::TweakMulVerify(const CPubKey& untweaked, const uint256& tweak) const +{ + assert(this->IsCompressed()); + secp256k1_pubkey pk; + if (!secp256k1_ec_pubkey_parse(secp256k1_context_verify, &pk, untweaked.data(), untweaked.size())) return false; + if (!secp256k1_ec_pubkey_tweak_mul(secp256k1_context_verify, &pk, tweak.data())) return false; + unsigned char out_pk[CPubKey::COMPRESSED_SIZE]; + size_t out_len = CPubKey::COMPRESSED_SIZE; + if (!secp256k1_ec_pubkey_serialize(secp256k1_context_verify, out_pk, &out_len, &pk, SECP256K1_EC_COMPRESSED)) return false; + return *this == CPubKey(out_pk, out_pk + out_len); +} + bool CPubKey::Verify(const uint256 &hash, const std::vector& vchSig) const { if (!IsValid()) return false; diff --git a/src/pubkey.h b/src/pubkey.h index c81012e59d..15583f0df7 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -206,6 +206,9 @@ public: //! Derive BIP32 child pubkey. bool Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc, std::vector* tweak = nullptr /* ELEMENTS: vector of key tweak values that are filled out if non-null */) const; + + //! Verify that when this public key is tweaked with tweak, the result is res + bool TweakMulVerify(const CPubKey& res, const uint256& tweak) const; }; class XOnlyPubKey @@ -221,7 +224,7 @@ public: * * sigbytes must be exactly 64 bytes. */ - bool VerifySchnorr(const uint256& msg, Span sigbytes) const; + bool VerifySchnorr(const Span msg, Span sigbytes) const; bool CheckPayToContract(const XOnlyPubKey& base, const uint256& hash, bool parity) const; const unsigned char& operator[](int pos) const { return *(m_keydata.begin() + pos); } diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 9eab9ef70b..62e2d9df86 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -433,6 +433,17 @@ public: }; } +// Check the script has sufficient sigops budget for checksig(crypto) operation +inline bool update_validation_weight(ScriptExecutionData& execdata, ScriptError* serror) +{ + assert(execdata.m_validation_weight_left_init); + execdata.m_validation_weight_left -= VALIDATION_WEIGHT_PER_SIGOP_PASSED; + if (execdata.m_validation_weight_left < 0) { + return set_error(serror, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT); + } + return true; +} + static bool EvalChecksigPreTapscript(const valtype& vchSig, const valtype& vchPubKey, CScript::const_iterator pbegincodehash, CScript::const_iterator pend, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, bool& fSuccess) { assert(sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0); @@ -459,6 +470,47 @@ static bool EvalChecksigPreTapscript(const valtype& vchSig, const valtype& vchPu return true; } +static bool EvalTapScriptCheckSigFromStack(const valtype& sig, const valtype& vchPubKey, ScriptExecutionData& execdata, unsigned int flags, const valtype& msg, SigVersion sigversion, ScriptError* serror, bool& success) +{ + // This code follows the behaviour of EvalCheckSigTapscript + assert(sigversion == SigVersion::TAPSCRIPT); + + /* + * The following validation sequence is consensus critical. Please note how -- + * upgradable public key versions precede other rules; + * the script execution fails when using empty signature with invalid public key; + * the script execution fails when using non-empty invalid signature. + */ + success = !sig.empty(); + if (success) { + // Implement the sigops/witnesssize ratio test. + // Passing with an upgradable public key version is also counted. + if (!update_validation_weight(execdata, serror)) return false; // serror is set + } + if (vchPubKey.size() == 0) { + return set_error(serror, SCRIPT_ERR_PUBKEYTYPE); + } else if (vchPubKey.size() == 32) { + if (success) { + if (sig.size() != 64) + return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_SIZE); + const XOnlyPubKey pubkey{vchPubKey}; + if (!pubkey.VerifySchnorr(msg, sig)) + return set_error(serror, SCRIPT_ERR_SCHNORR_SIG); + } + } else { + /* + * New public key version softforks should be defined before this `else` block. + * Generally, the new code should not do anything but failing the script execution. To avoid + * consensus bugs, it should not modify any existing values (including `success`). + */ + if ((flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE) != 0) { + return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE); + } + } + + return true; +} + static bool EvalChecksigTapscript(const valtype& sig, const valtype& pubkey, ScriptExecutionData& execdata, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, bool& success) { assert(sigversion == SigVersion::TAPSCRIPT); @@ -473,11 +525,7 @@ static bool EvalChecksigTapscript(const valtype& sig, const valtype& pubkey, Scr if (success) { // Implement the sigops/witnesssize ratio test. // Passing with an upgradable public key version is also counted. - assert(execdata.m_validation_weight_left_init); - execdata.m_validation_weight_left -= VALIDATION_WEIGHT_PER_SIGOP_PASSED; - if (execdata.m_validation_weight_left < 0) { - return set_error(serror, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT); - } + if (!update_validation_weight(execdata, serror)) return false; // serror is set } if (pubkey.size() == 0) { return set_error(serror, SCRIPT_ERR_PUBKEYTYPE); @@ -519,6 +567,11 @@ static bool EvalChecksig(const valtype& sig, const valtype& pubkey, CScript::con assert(false); } +static const CHashWriter HASHER_TAPLEAF_ELEMENTS = TaggedHash("TapLeaf/elements"); +static const CHashWriter HASHER_TAPBRANCH_ELEMENTS = TaggedHash("TapBranch/elements"); +static const CHashWriter HASHER_TAPTWEAK_ELEMENTS = TaggedHash("TapTweak/elements"); +static const CHashWriter HASHER_TAPSIGHASH_ELEMENTS = TaggedHash("TapSighash/elements"); + bool EvalScript(std::vector >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror) { static const CScriptNum bnZero(0); @@ -1664,29 +1717,40 @@ bool EvalScript(std::vector >& stack, const CScript& valtype& vchSig = stacktop(-3); valtype& vchData = stacktop(-2); valtype& vchPubKey = stacktop(-1); + bool fSuccess; + // Different semantics for CHECKSIGFROMSTACK for taproot and pre-taproot + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) + { + // Sigs from stack have no hash byte ever + if (!CheckSignatureEncoding(vchSig, (flags | SCRIPT_NO_SIGHASH_BYTE), serror) || !CheckPubKeyEncoding(vchPubKey, flags, sigversion, serror)) { + //serror is set + return false; + } - // Sigs from stack have no hash byte ever - if (!CheckSignatureEncoding(vchSig, (flags | SCRIPT_NO_SIGHASH_BYTE), serror) || !CheckPubKeyEncoding(vchPubKey, flags, sigversion, serror)) { - //serror is set - return false; + valtype vchHash(CSHA256::OUTPUT_SIZE); + CSHA256().Write(vchData.data(), vchData.size()).Finalize(vchHash.data()); + uint256 hash(vchHash); + + CPubKey pubkey(vchPubKey); + fSuccess = pubkey.Verify(hash, vchSig); + // CHECKSIGFROMSTACK in pre-tapscript cannot be failed. + if (!fSuccess) + return set_error(serror, SCRIPT_ERR_CHECKSIGVERIFY); + } else { + // New BIP 340 semantics for CHECKSIGFROMSTACK + if (!EvalTapScriptCheckSigFromStack(vchSig, vchPubKey, execdata, flags, vchData, sigversion, serror, fSuccess)) return false; } - - valtype vchHash(CSHA256::OUTPUT_SIZE); - CSHA256().Write(vchData.data(), vchData.size()).Finalize(vchHash.data()); - uint256 hash(vchHash); - - CPubKey pubkey(vchPubKey); - bool fSuccess = pubkey.Verify(hash, vchSig); - popstack(stack); popstack(stack); popstack(stack); stack.push_back(fSuccess ? vchTrue : vchFalse); if (opcode == OP_CHECKSIGFROMSTACKVERIFY) - popstack(stack); - - if (!fSuccess) - return set_error(serror, SCRIPT_ERR_CHECKSIGVERIFY); + { + if (fSuccess) + popstack(stack); + else + return set_error(serror, SCRIPT_ERR_CHECKSIGVERIFY); + } } break; @@ -2118,6 +2182,58 @@ bool EvalScript(std::vector >& stack, const CScript& push8_le(stack, static_cast(num)); } break; + case OP_ECMULSCALARVERIFY: + { + // OP_ECMULSCALARVERIFY is available post tapscript + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + + valtype& vchRes = stacktop(-3); + valtype& vchGenerator = stacktop(-2); + valtype& vchScalar = stacktop(-1); + + CPubKey pk(vchGenerator); + CPubKey res(vchRes); + if (!pk.IsCompressed() || !res.IsCompressed()) + return set_error(serror, SCRIPT_ERR_PUBKEYTYPE); + + if (!update_validation_weight(execdata, serror)) return false; // serror is set + + if (vchScalar.size() != 32 || !res.TweakMulVerify(pk, uint256(vchScalar))) + return set_error(serror, SCRIPT_ERR_ECMULTVERIFYFAIL); + + popstack(stack); + popstack(stack); + popstack(stack); + } + break; + + //crypto opcodes + case OP_TWEAKVERIFY: + { + // OP_TWEAKVERIFY is available post tapscript + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + + valtype& vchTweakedKey = stacktop(-3); + valtype& vchTweak = stacktop(-2); + valtype& vchInternalKey = stacktop(-1); + + if (vchTweakedKey.size() != CPubKey::COMPRESSED_SIZE || (vchTweakedKey[0] != 0x02 && vchTweakedKey[0] != 0x03) + || vchInternalKey.size() != 32 || vchTweak.size() != 32) + return set_error(serror, SCRIPT_ERR_PUBKEYTYPE); + + if (!update_validation_weight(execdata, serror)) return false; // serror is set + + const XOnlyPubKey tweakedXOnlyKey{Span{vchTweakedKey.data() + 1, vchTweakedKey.data() + CPubKey::COMPRESSED_SIZE}}; + const uint256 tweak(vchTweak); + const XOnlyPubKey internalKey{vchInternalKey}; + if (!tweakedXOnlyKey.CheckPayToContract(internalKey, tweak, vchTweakedKey[0] & 1)) + return set_error(serror, SCRIPT_ERR_ECMULTVERIFYFAIL); + + popstack(stack); + popstack(stack); + popstack(stack); + } + break; default: return set_error(serror, SCRIPT_ERR_BAD_OPCODE); @@ -2490,12 +2606,6 @@ template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo, template PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo); template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo); - -static const CHashWriter HASHER_TAPLEAF_ELEMENTS = TaggedHash("TapLeaf/elements"); -static const CHashWriter HASHER_TAPBRANCH_ELEMENTS = TaggedHash("TapBranch/elements"); -static const CHashWriter HASHER_TAPTWEAK_ELEMENTS = TaggedHash("TapTweak/elements"); -static const CHashWriter HASHER_TAPSIGHASH_ELEMENTS = TaggedHash("TapSighash/elements"); - PrecomputedTransactionData::PrecomputedTransactionData(const uint256& hash_genesis_block) : m_tapsighash_hasher(CHashWriter(HASHER_TAPSIGHASH_ELEMENTS) << hash_genesis_block << hash_genesis_block) {} diff --git a/src/script/script.cpp b/src/script/script.cpp index 7df5d6776b..632a29569f 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -181,6 +181,11 @@ std::string GetOpName(opcodetype opcode) case OP_SCRIPTNUMTOLE64 : return "OP_SCRIPTNUMTOLE64"; case OP_LE64TOSCRIPTNUM : return "OP_LE64TOSCRIPTNUM"; case OP_LE32TOLE64 : return "OP_LE32TOLE64"; + + // Crypto opcodes + case OP_ECMULSCALARVERIFY : return "OP_ECMULSCALARVERIFY"; + case OP_TWEAKVERIFY : return "OP_TWEAKVERIFY"; + case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE"; default: diff --git a/src/script/script.h b/src/script/script.h index cb6159cc2a..efff5fcfaf 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -259,11 +259,15 @@ enum opcodetype OP_LE64TOSCRIPTNUM = 0xe1, OP_LE32TOLE64 = 0xe2, + // Crypto opcodes + OP_ECMULSCALARVERIFY = 0xe3, + OP_TWEAKVERIFY = 0xe4, + OP_INVALIDOPCODE = 0xff, }; // Maximum value that an opcode can be -static const unsigned int MAX_OPCODE = OP_LE32TOLE64; // 0xe6 +static const unsigned int MAX_OPCODE = OP_TWEAKVERIFY; // 0xe4 std::string GetOpName(opcodetype opcode); diff --git a/src/script/script_error.cpp b/src/script/script_error.cpp index 89981c480a..438e86f2f2 100644 --- a/src/script/script_error.cpp +++ b/src/script/script_error.cpp @@ -127,6 +127,8 @@ std::string ScriptErrorString(const ScriptError serror) return "Arithmetic opcodes expect 8 bytes operands"; case SCRIPT_ERR_ARITHMETIC64: return "Arithmetic opcode error"; + case SCRIPT_ERR_ECMULTVERIFYFAIL: + return "EC scalar mult verify fail"; case SCRIPT_ERR_UNKNOWN_ERROR: case SCRIPT_ERR_ERROR_COUNT: default: break; diff --git a/src/script/script_error.h b/src/script/script_error.h index f702fbff1b..349c071bbb 100644 --- a/src/script/script_error.h +++ b/src/script/script_error.h @@ -95,10 +95,11 @@ typedef enum ScriptError_t SCRIPT_ERR_INTROSPECT_CONTEXT_UNAVAILABLE, SCRIPT_ERR_INTROSPECT_INDEX_OUT_OF_BOUNDS, SCRIPT_ERR_EXPECTED_8BYTES, - SCRIPT_ERR_ARITHMETIC64 + SCRIPT_ERR_ARITHMETIC64, + SCRIPT_ERR_ECMULTVERIFYFAIL } ScriptError; -#define SCRIPT_ERR_LAST SCRIPT_ERR_ARITHMETIC64 +#define SCRIPT_ERR_LAST SCRIPT_ERR_ECMULTVERIFYFAIL std::string ScriptErrorString(const ScriptError error);