diff --git a/src/bench/verify_script.cpp b/src/bench/verify_script.cpp index 4cac1c17ad..faecc361fd 100644 --- a/src/bench/verify_script.cpp +++ b/src/bench/verify_script.cpp @@ -87,7 +87,7 @@ static void VerifyScriptBench(benchmark::State& state) txCredit.vout[0].scriptPubKey, &txSpend.vin[0].scriptWitness, flags, - MutableTransactionSignatureChecker(&txSpend, 0, txCredit.vout[0].nValue), + MutableTransactionSignatureChecker(&txSpend, 0, txCredit.vout[0].nValue, MissingDataBehavior::ASSERT_FAIL), &err); assert(err == SCRIPT_ERR_OK); assert(success); diff --git a/src/core_read.cpp b/src/core_read.cpp index 0b54728266..6c70b688f4 100644 --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -199,6 +199,7 @@ int ParseSighashString(const UniValue& sighash) int hash_type = SIGHASH_ALL; if (!sighash.isNull()) { static std::map map_sighash_values = { + {std::string("DEFAULT"), int(SIGHASH_DEFAULT)}, {std::string("ALL"), int(SIGHASH_ALL)}, {std::string("ALL|ANYONECANPAY"), int(SIGHASH_ALL|SIGHASH_ANYONECANPAY)}, {std::string("NONE"), int(SIGHASH_NONE)}, diff --git a/src/defi-tx.cpp b/src/defi-tx.cpp index 7ccc45c834..da20915977 100644 --- a/src/defi-tx.cpp +++ b/src/defi-tx.cpp @@ -503,11 +503,12 @@ static void MutateTxDelOutput(CMutableTransaction& tx, const std::string& strOut tx.vout.erase(tx.vout.begin() + outIdx); } -static const unsigned int N_SIGHASH_OPTS = 6; +static const unsigned int N_SIGHASH_OPTS = 7; static const struct { const char *flagStr; int flags; } sighashOptions[N_SIGHASH_OPTS] = { + {"DEFAULT", SIGHASH_DEFAULT}, {"ALL", SIGHASH_ALL}, {"NONE", SIGHASH_NONE}, {"SINGLE", SIGHASH_SINGLE}, diff --git a/src/key.cpp b/src/key.cpp index 147f23bc72..c16b0d07c7 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -7,10 +7,13 @@ #include #include +#include #include #include +#include #include +#include static secp256k1_context* secp256k1_context_sign = nullptr; @@ -226,6 +229,12 @@ bool CKey::Sign(const uint256 &hash, std::vector& vchSig, bool gr assert(ret); secp256k1_ecdsa_signature_serialize_der(secp256k1_context_sign, vchSig.data(), &nSigLen, &sig); vchSig.resize(nSigLen); + // Additional verification step to prevent using a potentially corrupted signature + secp256k1_pubkey pk; + ret = secp256k1_ec_pubkey_create(secp256k1_context_sign, &pk, begin()); + assert(ret); + ret = secp256k1_ecdsa_verify(GetVerifyContext(), &sig, hash.begin(), &pk); + assert(ret); return true; } @@ -248,16 +257,49 @@ bool CKey::SignCompact(const uint256 &hash, std::vector& vchSig) return false; vchSig.resize(CPubKey::COMPACT_SIGNATURE_SIZE); int rec = -1; - secp256k1_ecdsa_recoverable_signature sig; - int ret = secp256k1_ecdsa_sign_recoverable(secp256k1_context_sign, &sig, hash.begin(), begin(), secp256k1_nonce_function_rfc6979, nullptr); + secp256k1_ecdsa_recoverable_signature rsig; + int ret = secp256k1_ecdsa_sign_recoverable(secp256k1_context_sign, &rsig, hash.begin(), begin(), secp256k1_nonce_function_rfc6979, nullptr); assert(ret); - ret = secp256k1_ecdsa_recoverable_signature_serialize_compact(secp256k1_context_sign, &vchSig[1], &rec, &sig); + ret = secp256k1_ecdsa_recoverable_signature_serialize_compact(secp256k1_context_sign, &vchSig[1], &rec, &rsig); assert(ret); assert(rec != -1); vchSig[0] = 27 + rec + (fCompressed ? 4 : 0); + // Additional verification step to prevent using a potentially corrupted signature + secp256k1_pubkey epk, rpk; + ret = secp256k1_ec_pubkey_create(secp256k1_context_sign, &epk, begin()); + assert(ret); + ret = secp256k1_ecdsa_recover(GetVerifyContext(), &rpk, &rsig, hash.begin()); + assert(ret); + ret = secp256k1_ec_pubkey_cmp(GetVerifyContext(), &epk, &rpk); + assert(ret == 0); return true; } +bool CKey::SignSchnorr(const uint256& hash, Span sig, const uint256* merkle_root, const uint256& aux) const +{ + assert(sig.size() == 64); + secp256k1_keypair keypair; + if (!secp256k1_keypair_create(secp256k1_context_sign, &keypair, begin())) return false; + if (merkle_root) { + secp256k1_xonly_pubkey pubkey; + if (!secp256k1_keypair_xonly_pub(secp256k1_context_sign, &pubkey, nullptr, &keypair)) return false; + unsigned char pubkey_bytes[32]; + if (!secp256k1_xonly_pubkey_serialize(secp256k1_context_sign, pubkey_bytes, &pubkey)) return false; + uint256 tweak = XOnlyPubKey(pubkey_bytes).ComputeTapTweakHash(merkle_root->IsNull() ? nullptr : merkle_root); + if (!secp256k1_keypair_xonly_tweak_add(GetVerifyContext(), &keypair, tweak.data())) return false; + } + bool ret = secp256k1_schnorrsig_sign32(secp256k1_context_sign, sig.data(), hash.data(), &keypair, aux.data()); + if (ret) { + // Additional verification step to prevent using a potentially corrupted signature + secp256k1_xonly_pubkey pubkey_verify; + ret = secp256k1_keypair_xonly_pub(GetVerifyContext(), &pubkey_verify, nullptr, &keypair); + ret &= secp256k1_schnorrsig_verify(GetVerifyContext(), sig.data(), hash.begin(), 32, &pubkey_verify); + } + if (!ret) memory_cleanse(sig.data(), sig.size()); + memory_cleanse(&keypair, sizeof(keypair)); + return ret; +} + bool CKey::Load(const CPrivKey &privkey, const CPubKey &vchPubKey, bool fSkipCheck=false) { if (!ec_privkey_import_der(secp256k1_context_sign, (unsigned char*)begin(), privkey.data(), privkey.size())) return false; diff --git a/src/key.h b/src/key.h index 0857fa6639..39337fccc4 100644 --- a/src/key.h +++ b/src/key.h @@ -130,6 +130,18 @@ class CKey */ bool SignCompact(const uint256& hash, std::vector& vchSig) const; + /** + * Create a BIP-340 Schnorr signature, for the xonly-pubkey corresponding to *this, + * optionally tweaked by *merkle_root. Additional nonce entropy is provided through + * aux. + * + * When merkle_root is not nullptr, this results in a signature with a modified key as + * specified in BIP341: + * - If merkle_root->IsNull(): key + H_TapTweak(pubkey)*G + * - Otherwise: key + H_TapTweak(pubkey || *merkle_root) + */ + bool SignSchnorr(const uint256& hash, Span sig, const uint256* merkle_root, const uint256& aux) const; + //! Derive BIP32 child key. bool Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const; diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp index 171ec07862..8c8d4c2097 100644 --- a/src/node/psbt.cpp +++ b/src/node/psbt.cpp @@ -23,6 +23,8 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) result.inputs.resize(psbtx.tx->vin.size()); + const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); + for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { PSBTInput& input = psbtx.inputs[i]; PSBTInputAnalysis& input_analysis = result.inputs[i]; @@ -46,7 +48,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) // Figure out what is missing SignatureData outdata; - bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, &outdata); + bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, 1, &outdata); // Things are missing if (!complete) { @@ -97,7 +99,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) PSBTInput& input = psbtx.inputs[i]; Coin newcoin; - if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, nullptr, true) || !psbtx.GetInputUTXO(newcoin.out, i)) { + if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, nullptr, 1) || !psbtx.GetInputUTXO(newcoin.out, i)) { success = false; break; } else { diff --git a/src/outputtype.cpp b/src/outputtype.cpp index f3fae903c6..c858e49e11 100644 --- a/src/outputtype.cpp +++ b/src/outputtype.cpp @@ -18,6 +18,7 @@ static const std::string OUTPUT_TYPE_STRING_LEGACY = "legacy"; static const std::string OUTPUT_TYPE_STRING_P2SH_SEGWIT = "p2sh-segwit"; static const std::string OUTPUT_TYPE_STRING_BECH32 = "bech32"; +static const std::string OUTPUT_TYPE_STRING_BECH32M = "bech32m"; static const std::string OUTPUT_TYPE_STRING_ERC55 = "erc55"; static const std::string OUTPUT_TYPE_STRING_ETH = "eth"; @@ -32,6 +33,9 @@ bool ParseOutputType(const std::string& type, OutputType& output_type) } else if (type == OUTPUT_TYPE_STRING_BECH32) { output_type = OutputType::BECH32; return true; + } else if (type == OUTPUT_TYPE_STRING_BECH32M) { + output_type = OutputType::BECH32M; + return true; } else if (type == OUTPUT_TYPE_STRING_ERC55 || type == OUTPUT_TYPE_STRING_ETH) { output_type = OutputType::ERC55; return true; @@ -45,6 +49,7 @@ const std::string& FormatOutputType(OutputType type) case OutputType::LEGACY: return OUTPUT_TYPE_STRING_LEGACY; case OutputType::P2SH_SEGWIT: return OUTPUT_TYPE_STRING_P2SH_SEGWIT; case OutputType::BECH32: return OUTPUT_TYPE_STRING_BECH32; + case OutputType::BECH32M: return OUTPUT_TYPE_STRING_BECH32M; case OutputType::ERC55: return OUTPUT_TYPE_STRING_ERC55; default: assert(false); } @@ -72,7 +77,11 @@ CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type) } return WitnessV16EthHash(pubkeyCopy); } - default: assert(false); + case OutputType::BECH32M: { + return WitnessV1Taproot(XOnlyPubKey{key}); + } + default: + assert(false); } } @@ -110,6 +119,8 @@ CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore, return ScriptHash(witprog); } } - default: assert(false); + case OutputType::BECH32M: // This function should not be used for BECH32M, so let it assert + default: + assert(false); } } diff --git a/src/outputtype.h b/src/outputtype.h index df6a2b16b4..4012e2e5eb 100644 --- a/src/outputtype.h +++ b/src/outputtype.h @@ -17,6 +17,7 @@ enum class OutputType { LEGACY, P2SH_SEGWIT, BECH32, + BECH32M, ERC55, /** diff --git a/src/psbt.cpp b/src/psbt.cpp index 46dcfda9d2..cd7f632ade 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -66,9 +66,12 @@ bool PartiallySignedTransaction::AddOutput(const CTxOut& txout, const PSBTOutput bool PartiallySignedTransaction::GetInputUTXO(CTxOut& utxo, int input_index) const { - PSBTInput input = inputs[input_index]; + const PSBTInput& input = inputs[input_index]; int prevout_index = tx->vin[input_index].prevout.n; if (input.non_witness_utxo) { + if (input.non_witness_utxo->GetHash() != tx->vin[input_index].prevout.hash) { + return false; + } utxo = input.non_witness_utxo->vout[prevout_index]; } else if (!input.witness_utxo.IsNull()) { utxo = input.witness_utxo; /// @todo tokens: extend with correct txout version (and/or tokenid) when implemented @@ -236,7 +239,24 @@ void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransactio psbt_out.FromSignatureData(sigdata); } -bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash, SignatureData* out_sigdata, bool use_dummy) +PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt) +{ + const CMutableTransaction& tx = *psbt.tx; + bool have_all_spent_outputs = true; + std::vector utxos(tx.vin.size()); + for (size_t idx = 0; idx < tx.vin.size(); ++idx) { + if (!psbt.GetInputUTXO(utxos[idx], idx)) have_all_spent_outputs = false; + } + PrecomputedTransactionData txdata; + if (have_all_spent_outputs) { + txdata.Init(tx, std::move(utxos), true); + } else { + txdata.Init(tx, {}, true); + } + return txdata; +} + +bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash, SignatureData* out_sigdata) { PSBTInput& input = psbt.inputs.at(index); const CMutableTransaction& tx = *psbt.tx; @@ -278,10 +298,10 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& sigdata.witness = false; bool sig_complete; - if (use_dummy) { + if (txdata == nullptr) { sig_complete = ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, utxo.scriptPubKey, sigdata); } else { - MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, sighash); + MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, txdata, sighash); sig_complete = ProduceSignature(provider, creator, utxo.scriptPubKey, sigdata); } // Verify that a witness signature was produced in case one was required. @@ -312,8 +332,9 @@ bool FinalizePSBT(PartiallySignedTransaction& psbtx) // PartiallySignedTransaction did not understand them), this will combine them into a final // script. bool complete = true; + const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { - complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, SIGHASH_ALL); + complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, SIGHASH_ALL); } return complete; diff --git a/src/psbt.h b/src/psbt.h index f9fcf9d9c4..35c533bc14 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -568,11 +568,18 @@ enum class PSBTRole { std::string PSBTRoleName(PSBTRole role); +/** Compute a PrecomputedTransactionData object from a psbt. */ +PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt); + /** Checks whether a PSBTInput is already signed. */ bool PSBTInputSigned(const PSBTInput& input); -/** Signs a PSBTInput, verifying that all provided data matches what is being signed. */ -bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool use_dummy = false); +/** Signs a PSBTInput, verifying that all provided data matches what is being signed. + * + * txdata should be the output of PrecomputePSBTData (which can be shared across + * multiple SignPSBTInput calls). If it is nullptr, a dummy signature will be created. + **/ +bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr); /** Updates a PSBTOutput with information from provider. * diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 433494f302..9dfa9f7d72 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -385,3 +385,7 @@ ECCVerifyHandle::~ECCVerifyHandle() secp256k1_context_verify = nullptr; } } + +const secp256k1_context* GetVerifyContext() { + return secp256k1_context_verify; +} diff --git a/src/pubkey.h b/src/pubkey.h index 2cf60c6cad..729fe0f396 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -255,6 +255,10 @@ class XOnlyPubKey * fail. */ bool IsFullyValid() const; + /** Test whether this is the 0 key (the result of default construction). This implies + * !IsFullyValid(). */ + bool IsNull() const { return m_keydata.IsNull(); } + /** Construct an x-only pubkey from exactly 32 bytes. */ XOnlyPubKey(Span bytes); @@ -353,4 +357,10 @@ class ECCVerifyHandle ~ECCVerifyHandle(); }; +typedef struct secp256k1_context_struct secp256k1_context; + +/** Access to the internal secp256k1 context used for verification. Only intended to be used + * by key.cpp. */ +const secp256k1_context* GetVerifyContext(); + #endif // DEFI_PUBKEY_H diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 247cfda006..f041ce960c 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -698,6 +698,7 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) }, }, {"sighashtype", RPCArg::Type::STR, /* default */ "ALL", "The signature hash type. Must be one of:\n" + " \"DEFAULT\"\n" " \"ALL\"\n" " \"NONE\"\n" " \"SINGLE\"\n" @@ -1520,6 +1521,7 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request) } // Fill the inputs + const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { PSBTInput& input = psbtx.inputs.at(i); @@ -1536,7 +1538,7 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request) // Update script/keypath information using descriptor data. // Note that SignPSBTInput does a lot more than just constructing ECDSA signatures // we don't actually care about those here, in fact. - SignPSBTInput(public_provider, psbtx, i, /* sighash_type */ 1); + SignPSBTInput(public_provider, psbtx, i, &txdata, /* sighash_type */ 1); } // Update script/keypath information using descriptor data. diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index c355481010..dfe66ecf1e 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -410,10 +410,33 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival const CScript& prevPubKey = coin->second.out.scriptPubKey; const CAmount& amount = coin->second.out.nValue; + // Use CTransaction for the constant parts of the + // transaction to avoid rehashing. + const CTransaction txConst(mtx); + + PrecomputedTransactionData txdata; + std::vector spent_outputs; + spent_outputs.resize(mtx.vin.size()); + bool have_all_spent_outputs = true; + for (unsigned int i = 0; i < mtx.vin.size(); i++) { + CTxIn& txin = mtx.vin[i]; + auto coin = coins.find(txin.prevout); + if (coin == coins.end() || coin->second.IsSpent()) { + have_all_spent_outputs = false; + } else { + spent_outputs[i] = CTxOut(coin->second.out.nValue, coin->second.out.scriptPubKey); + } + } + if (have_all_spent_outputs) { + txdata.Init(txConst, std::move(spent_outputs), true); + } else { + txdata.Init(txConst, {}, true); + } + SignatureData sigdata = DataFromTransaction(mtx, i, coin->second.out); // Only sign SIGHASH_SINGLE if there's a corresponding output: if (!fHashSingle || (i < mtx.vout.size())) { - ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, nHashType), prevPubKey, sigdata); + ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, &txdata, nHashType), prevPubKey, sigdata); } UpdateInput(txin, sigdata); @@ -424,7 +447,7 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival } ScriptError serror = SCRIPT_ERR_OK; - if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) { + if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount, txdata, MissingDataBehavior::FAIL), &serror)) { if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) { // Unable to sign input and verification failed (possible attempt to partially sign). TxInErrorToJSON(txin, vErrors, "Unable to sign input, invalid stack size (possibly missing key)"); diff --git a/src/script/deficonsensus.cpp b/src/script/deficonsensus.cpp index ecfcd7dc7f..adbb9502c0 100644 --- a/src/script/deficonsensus.cpp +++ b/src/script/deficonsensus.cpp @@ -95,7 +95,7 @@ static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptP set_error(err, deficonsensus_ERR_OK); PrecomputedTransactionData txdata(tx); - return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), &tx.vin[nIn].scriptWitness, flags, TransactionSignatureChecker(&tx, nIn, amount, txdata), nullptr); + return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), &tx.vin[nIn].scriptWitness, flags, TransactionSignatureChecker(&tx, nIn, amount, txdata, MissingDataBehavior::FAIL), nullptr); } catch (const std::exception&) { return set_error(err, deficonsensus_ERR_TX_DESERIALIZE); // Error deserializing } diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index fcd7b2bd5d..acfcaec8d4 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -556,17 +556,22 @@ class DescriptorImpl : public Descriptor std::optional GetOutputType() const override { return std::nullopt; } }; -static std::optional OutputTypeFromDestination(const CTxDestination& dest) { +std::optional OutputTypeFromDestination(const CTxDestination& dest) { if (std::holds_alternative(dest) || std::holds_alternative(dest)) { return OutputType::LEGACY; } if (std::holds_alternative(dest) || - std::holds_alternative(dest) || - std::holds_alternative(dest) || - std::holds_alternative(dest)) { + std::holds_alternative(dest)) { return OutputType::BECH32; } + if (std::holds_alternative(dest) || + std::holds_alternative(dest)) { + return OutputType::BECH32M; + } + if (std::holds_alternative(dest)) { + return OutputType::ERC55; + } return std::nullopt; } @@ -749,7 +754,9 @@ class WSHDescriptor final : public DescriptorImpl XOnlyPubKey xpk(keys[0]); if (!xpk.IsFullyValid()) return {}; builder.Finalize(xpk); - return Vector(GetScriptForDestination(builder.GetOutput())); + WitnessV1Taproot output = builder.GetOutput(); + out.tr_spenddata[output].Merge(builder.GetSpendData()); + return Vector(GetScriptForDestination(output)); } bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, bool priv) const override { @@ -778,7 +785,7 @@ class WSHDescriptor final : public DescriptorImpl { assert(m_subdescriptor_args.size() == m_depths.size()); } - std::optional GetOutputType() const override { return OutputType::BECH32; } + std::optional GetOutputType() const override { return OutputType::BECH32M; } }; //////////////////////////////////////////////////////////////////////////// diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 2ac49fbbd1..36570b0bc3 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1363,7 +1363,7 @@ uint256 GetSpentScriptsSHA256(const std::vector& outputs_spent) } // namespace template -void PrecomputedTransactionData::Init(const T& txTo, std::vector&& spent_outputs) +void PrecomputedTransactionData::Init(const T& txTo, std::vector&& spent_outputs, bool force) { assert(!m_spent_outputs_ready); @@ -1374,9 +1374,9 @@ void PrecomputedTransactionData::Init(const T& txTo, std::vector&& spent } // Determine which precomputation-impacting features this transaction uses. - bool uses_bip143_segwit = false; - bool uses_bip341_taproot = false; - for (size_t inpos = 0; inpos < txTo.vin.size(); ++inpos) { + bool uses_bip143_segwit = force; + bool uses_bip341_taproot = force; + for (size_t inpos = 0; inpos < txTo.vin.size() && !(uses_bip143_segwit && uses_bip341_taproot); ++inpos) { if (!txTo.vin[inpos].scriptWitness.IsNull()) { if (m_spent_outputs_ready && m_spent_outputs[inpos].scriptPubKey.size() == 2 + WITNESS_V1_TAPROOT_SIZE && m_spent_outputs[inpos].scriptPubKey[0] == OP_1) { @@ -1421,8 +1421,8 @@ PrecomputedTransactionData::PrecomputedTransactionData(const T& txTo) } // explicit instantiation -template void PrecomputedTransactionData::Init(const CTransaction& txTo, std::vector&& spent_outputs); -template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo, std::vector&& spent_outputs); +template void PrecomputedTransactionData::Init(const CTransaction& txTo, std::vector&& spent_outputs, bool force); +template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo, std::vector&& spent_outputs, bool force); template PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo); template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo); @@ -1430,8 +1430,21 @@ static const CHashWriter HASHER_TAPSIGHASH = TaggedHash("TapSighash"); const CHashWriter HASHER_TAPLEAF = TaggedHash("TapLeaf"); const CHashWriter HASHER_TAPBRANCH = TaggedHash("TapBranch"); +static bool HandleMissingData(MissingDataBehavior mdb) +{ + switch (mdb) { + case MissingDataBehavior::ASSERT_FAIL: + assert(!"Missing data"); + break; + case MissingDataBehavior::FAIL: + return false; + } + assert(!"Unknown MissingDataBehavior value"); +} + + template -bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, const PrecomputedTransactionData& cache) +bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, const PrecomputedTransactionData& cache, MissingDataBehavior mdb) { uint8_t ext_flag, key_version; switch (sigversion) { @@ -1451,7 +1464,9 @@ bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata assert(false); } assert(in_pos < tx_to.vin.size()); - assert(cache.m_bip341_taproot_ready && cache.m_spent_outputs_ready); + if (!(cache.m_bip341_taproot_ready && cache.m_spent_outputs_ready)) { + return HandleMissingData(mdb); + } CHashWriter ss = HASHER_TAPSIGHASH; @@ -1611,6 +1626,9 @@ bool GenericTransactionSignatureChecker::CheckECDSASignature(const std::vecto int nHashType = vchSig.back(); vchSig.pop_back(); + // Witness sighashes need the amount. + if (sigversion == SigVersion::WITNESS_V0 && amount < 0) return HandleMissingData(m_mdb); + uint256 sighash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion, this->txdata); if (!VerifyECDSASignature(vchSig, pubkey, sighash)) @@ -1639,8 +1657,8 @@ bool GenericTransactionSignatureChecker::CheckSchnorrSignature(Spantxdata); - if (!SignatureHashSchnorr(sighash, execdata, *txTo, nIn, hashtype, sigversion, *this->txdata)) { + if (!this->txdata) return HandleMissingData(m_mdb); + if (!SignatureHashSchnorr(sighash, execdata, *txTo, nIn, hashtype, sigversion, *this->txdata, m_mdb)) { return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_HASHTYPE); } if (!VerifySchnorrSignature(sig, pubkey, sighash)) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG); diff --git a/src/script/interpreter.h b/src/script/interpreter.h index 5f94f0b06e..8885d11c16 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -170,7 +170,7 @@ struct PrecomputedTransactionData PrecomputedTransactionData() = default; template - void Init(const T& tx, std::vector&& spent_outputs); + void Init(const T& tx, std::vector&& spent_outputs, bool force = false); template explicit PrecomputedTransactionData(const T& tx); @@ -256,11 +256,24 @@ class BaseSignatureChecker virtual ~BaseSignatureChecker() {} }; +/** Enum to specify what *TransactionSignatureChecker's behavior should be + * when dealing with missing transaction data. + */ +enum class MissingDataBehavior +{ + ASSERT_FAIL, //!< Abort execution through assertion failure (for consensus code) + FAIL, //!< Just act as if the signature was invalid +}; + +template +bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, const PrecomputedTransactionData& cache, MissingDataBehavior mdb); + template class GenericTransactionSignatureChecker : public BaseSignatureChecker { private: const T* txTo; + const MissingDataBehavior m_mdb; unsigned int nIn; const CAmount amount; const PrecomputedTransactionData* txdata; @@ -270,8 +283,8 @@ class GenericTransactionSignatureChecker : public BaseSignatureChecker virtual bool VerifySchnorrSignature(Span sig, const XOnlyPubKey& pubkey, const uint256& sighash) const; public: - GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(nullptr) {} - GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {} + GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, MissingDataBehavior mdb) : txTo(txToIn), m_mdb(mdb), nIn(nInIn), amount(amountIn), txdata(nullptr) {} + GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn, MissingDataBehavior mdb) : txTo(txToIn), m_mdb(mdb), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {} bool CheckECDSASignature(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override; bool CheckSchnorrSignature(Span sig, Span pubkey, SigVersion sigversion, const ScriptExecutionData& execdata, ScriptError* serror = nullptr) const override; bool CheckLockTime(const CScriptNum& nLockTime) const override; diff --git a/src/script/sigcache.h b/src/script/sigcache.h index 4b292b5c3a..afbd4353ec 100644 --- a/src/script/sigcache.h +++ b/src/script/sigcache.h @@ -47,7 +47,7 @@ class CachingTransactionSignatureChecker : public TransactionSignatureChecker bool store; public: - CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, bool storeIn, PrecomputedTransactionData& txdataIn) : TransactionSignatureChecker(txToIn, nInIn, amountIn, txdataIn), store(storeIn) {} + CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, bool storeIn, PrecomputedTransactionData& txdataIn) : TransactionSignatureChecker(txToIn, nInIn, amountIn, txdataIn, MissingDataBehavior::ASSERT_FAIL), store(storeIn) {} bool VerifyECDSASignature(const std::vector& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const override; bool VerifySchnorrSignature(Span sig, const XOnlyPubKey& pubkey, const uint256& sighash) const override; diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 07e345cc55..3201baa92a 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -11,14 +11,28 @@ #include